Fenster im Vordergrund aber ohne den Fokus zu stehlen

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • Fenster im Vordergrund aber ohne den Fokus zu stehlen

    Hallo zusammen,

    Es geht um die Irrwege von fokussierten Fenstern im Zusammenhang mit Spaces und so weiter ... ;) Ich hole etwas aus, weil das was ich beschreibe ziemliche Standardfunktionalität ist und bestimmt mehr Leute nach mir danach suchen. Im Internet gibt es erstaunlich wenig zu finden bisher...

    Zum Thema:

    Ich habe eine Eieruhr-App programmiert, die einen Alarm nach Ablauf eines Timers aufploppen lässt. Es ist eine reine Menubar-App, also ohne Icon im Dock.

    Den Timer lasse ich im AppDelegate runterlaufen und mit Ablauf ploppt mein Alarmfenster auf.

    Zum Anzeigen des NSWindow verwende ich folgenden Code:

    Quellcode

    1. NSApp.activate(ignoringOtherApps: true)
    2. myView.window?.makeKeyAndOrderFront(self)

    Das führt dann zu folgendem Problem: Wenn der Nutzer gerade in einer Vollbild-App unterwegs ist, wird er "gewaltsam" auf den ersten Desktop zurückgeholt, wo dann das Alarm-Fenster fokussiert angezeigt wird.
    Das ist UX-mäßig natürlich Mist. Abhilfe erhält man, wenn man die folgenden beiden Zeilen hinzufügt:

    Quellcode

    1. NSApp.activate(ignoringOtherApps: true)
    2. myView.window?.makeKeyAndOrderFront(self)
    3. myView.window?.collectionBehavior = .canJoinAllSpaces
    4. myView.window?.level = NSWindow.Level.tornOffMenu

    Dieser Code ist schon ganz gut. Wenn der Nutzer in einer Vollbild-App unterwegs ist, legt sich mein Alarm-Fenster über diese Vollbild-App. An dieser Stelle sei gesagt, dass mein Window keine Border hat. Es ist gut möglich, dass andere Window-Eigenschaften hier ein anderes Verhalten hervorrufen würden. Dieses habe ich aber nicht weiter erforscht.

    Ein UX-Problem bleibt bestehen für dass ich auch nach Stunden der Suche keine Lösung finde: Wenn ein Nutzer gerade am Schreiben eines Texts ist, wenn der Alarm aufploppt, dann klaut mein Alarm-Window dem zuletzt aktiven Window den Fokus und der Nutzer kann nicht weiterschreiben bevor er mein Fenster weggeklickt hat. Das bekomme ich einfach nicht gelöst. Das Fenster soll im Vordergrund angezeigt werden aber OHNE den Fokus zu klauen. Es ist dann nur mit der Maus bedienbar, aber das ist in meinem Fall völlig in Ordnung. Es verschwindet nach 2 Sekunden von alleine.
    Ich habe dazu bereits mit der Eigenschaft canBecomeKey von NSWindow experimentiert, jedoch ohne positivem Ergebnis. Wenn man canBecomeKey=false setzt, führt das lediglich dazu, dass in meinem Fenster keine Hotkeys angenommen werden. Das zuletzt aktive Fenster verliert aber trotzdem seinen Fokus. Ich vermute es könnte mit "makeKeyAndOrderFront" zusammenhängen? Das brauche ich aber um das Fenster überhaupt in den Vordergrund zu bekommen...

    Hat jemand eine Idee?

    Bin gespannt, danke.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von Stephan87 ()

  • Stephan87 schrieb:

    Zum Anzeigen des NSWindow verwende ich folgenden Code:

    Quellcode

    1. NSApp.activate(ignoringOtherApps: true)
    2. myView.window?.makeKeyAndOrderFront(self)
    Auch auf die Gefahr, mich fürchterlich zu blamieren, aber warum nutzt Du nicht nur orderFront: statt makeKeyAndOrderFront? Oder orderFrontRegardless? Damit müsste nach meinem Verständnis (und dieser Doku) das Fenster doch angezeigt werden, ohne den Focus zu verändern...

    Ein Nachbauen zum Testen ist mir jetzt zu schwierig, Du kannst das schneller mal ausprobieren... :)

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Grüß dich - danke für den Impuls!

    Also: Wenn ich nur orderFront verwende, dann ändert sich nichts am Verhalten.
    Ich habe weiter nachgeforscht. Ich denke der Fokusentzug kommt nicht von makeKeyAndOrderFront sondern von folgendem Aufruf:


    Quellcode

    1. NSApp.activate(ignoringOtherApps: true)

    Wenn ich das weglasse, oder wenn ich "ignoringOtherApps:false" nutze, dann verliert die aktive App zwar nicht mehr ihren Fokus, aber von meinem Fenster ist auch weit und breit nichts zu sehen ;)
    Stand jetzt wirkt es als würde NSApp.activate mit ignoringOtherApps: true benötigt um mein Fenster überhaupt anzuzeigen. Der Fokusverlust geht damit aber automatisch einher. Ist vielleicht eine Zwickmühle.
  • Stephan87 schrieb:

    Quellcode

    1. NSApp.activate(ignoringOtherApps: true)
    Wenn ich das weglasse, oder wenn ich "ignoringOtherApps:false" nutze, dann verliert die aktive App zwar nicht mehr ihren Fokus, aber von meinem Fenster ist auch weit und breit nichts zu sehen ;)
    An der Stelle würde ich mal nachhaken: Ich verstehe noch nicht, warum die App ein activate benötigen sollte, wenn Du ein Fenster per orderFrontRegardless nach vorne holst. Sorry, als ich damit mal "spielte", ging es um keine reine Menubar-App...

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Hallo,

    Ich habe neue Erkenntnisse...

    Ich habe folgendes gefunden: stackoverflow.com/questions/46…t-stealing-focus-on-macos

    Hierin wird beschrieben wie man mit Hilfe eines NSPanel (subclass von NSWindow) ein Fenster erzeugt, dass angezeigt wird ohne den Fokus zu stehlen.
    Das funktioniert soweit.

    Wofür es aber keine Lösung gibt ist dieses Fenster auszublenden, wenn es den Fokus verliert. Wenn man die Option "Hide on deactivate" auf dem Storyboard verwenden will lässt sich das Fenster nicht mehr anzeigen sobald es einmal weggeklickt wurde (weil man es erst wieder mit NSAppActivate aktivieren müsste, was dann aber wieder den Fokus stehlen würde.

    Ich gebe an dieser Stelle auf weil ich mittlerweile nicht mehr durchblicke und weil es so wirkt als gebe es im Internet aktuell keine Lösung.

    Good luck an den nächsten ;)
  • Stephan87 schrieb:

    Ich gebe an dieser Stelle auf weil ich mittlerweile nicht mehr durchblicke und weil es so wirkt als gebe es im Internet aktuell keine Lösung.
    Ich war neugierig und habe - auf Basis eines alten Test-Projektes - mal einen Versuch gestartet .. es funktioniert eigentlich wie vermutet:
    • Ich erstelle eine Menubar-Item, der ein kleines Menu anzeigt.
    • Mit Auswahl des Eintrags "Show Window" startet ein 10 sec. Timer, damit ich den Fokus auf andere Apps setzen kann. Eigentlich unnötig, da die Auswahl dieses Eintrags nicht den aktuellen Fokus verändert, aber da war mir am Anfang nicht klar.
    • Nach 10 Sekunden wird ein leeres, statisches Fenster ohne TitleBar angezeigt (daher muss man die App über's MenuItem beenden). Der aktuelle Fokus bleibt bestehen, der Trick ist orderFrontRegardless.
    Hier einmal mein minimalistischer AppDelegate - wie gesagt nur quick & dirty als Test. Objective-C, aber das sollte keine Hürde darstellen.

    Quellcode

    1. #import "AppDelegate.h"
    2. @interface AppDelegate () <NSMenuDelegate>
    3. @property (strong, nonatomic) NSStatusItem *statusItem;
    4. @property (strong, nonatomic) NSMenu *mainMenu;
    5. @property (strong, nonatomic) NSWindow *popupWindow;
    6. @property (strong, nonatomic) NSTimer *timer;
    7. @end
    8. @implementation AppDelegate
    9. - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
    10. {
    11. self.mainMenu = [NSMenu new];
    12. self.mainMenu.delegate = self;
    13. NSStatusBar *systemStatusBar = [NSStatusBar systemStatusBar];
    14. self.statusItem = [systemStatusBar statusItemWithLength:NSSquareStatusItemLength];
    15. self.statusItem.button.image = [NSImage imageNamed:@"StatusBarButtonImage"];
    16. self.statusItem.menu = self.mainMenu;
    17. [self setupMenu];
    18. }
    19. - (void)menuWillOpen:(NSMenu *)menu
    20. {
    21. if (menu == self.mainMenu)
    22. {
    23. [self setupMenu];
    24. }
    25. }
    26. - (void)setupMenu
    27. {
    28. [self.mainMenu removeAllItems];
    29. [self.mainMenu addItemWithTitle:@"Show Window" action:@selector(displayWindow:) keyEquivalent:@"n"];
    30. [self.mainMenu addItemWithTitle:@"Quit" action:@selector(quitAction:) keyEquivalent:@"q"];
    31. }
    32. - (void)displayWindow:(id)sender
    33. {
    34. self.timer = [NSTimer timerWithTimeInterval:10.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:NO];
    35. [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
    36. }
    37. - (void)timerFired:(NSTimer *)timer
    38. {
    39. NSLog(@"Timer fired.");
    40. CGRect windowRect = CGRectMake(200.0f, 100.0f, 400.0f, 200.0f);
    41. NSWindowStyleMask windowStyleMask = NSWindowStyleMaskBorderless;
    42. self.popupWindow = [[NSWindow alloc] initWithContentRect:windowRect styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:NO];
    43. [self.popupWindow orderFrontRegardless];
    44. }
    45. - (void)quitAction:(id)sender
    46. {
    47. [NSApp terminate:sender];
    48. }
    49. @end
    Alles anzeigen
    HTH, Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Hi,

    Find ich cool, dass du dir das Thema anschaust!

    Wir sind jetzt auf dem genau dem gleichen Stand:

    Versuch jetzt mal bitte bei deinem Popupwindow die Eigenschaft "Hide on deactivate" zu aktivieren.
    Das führt dann dazu, dass das Popup verschwindet, sobald der Nutzer in der App darunter einen Klick macht (= gewünschtes Verhalten, vergleiche mit Spotlight, da ist das genauso)

    Sobald es einmal ausgeblendet wurde, lässt es sich bei mir nicht mehr anzeigen. OrderFrontRegardless funktioniert nicht mehr. Ein Aufruf von NSApp.activate zeigt das Fenster wieder an, nimmt dem zuletzt aktiven aber den Fokus.

    Ich müsste also entweder einen Workaround für NSApp.activate finden oder für die Option "Hide on deactivate"...
  • Ich bin in eine andere Richtung gegangen und es funktioniert meines Erachtens: Statt das Fenster irgendwie so zu konfigurieren, dass es sich bei einem "fremden" Klick versteckt, lausche ich einfach auf entsprechende Events und schliesse das Fenster programmatisch selber. Ich glaube, das andere Vorgehen führt schnell in Sackgassen, da die App ja gerade nicht aktiv / key sein soll...

    Füge meinem Beispiel ein NSEvent-Property monitoredEventHandler hinzu und ändere die Methode timerFired: wie folgt:

    Quellcode

    1. - (void)timerFired:(NSTimer *)timer
    2. {
    3. NSLog(@"Timer fired.");
    4. CGRect windowRect = CGRectMake(200.0f, 100.0f, 400.0f, 200.0f);
    5. NSWindowStyleMask windowStyleMask = NSWindowStyleMaskBorderless;
    6. self.popupWindow = [[NSWindow alloc] initWithContentRect:windowRect styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:NO];
    7. self.popupWindow.collectionBehavior = NSWindowCollectionBehaviorCanJoinAllSpaces;
    8. [self.popupWindow orderFrontRegardless];
    9. self.monitoredEventHandler = [NSEvent addGlobalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown handler:^(NSEvent *caughtEvent)
    10. {
    11. NSLog(@"Caught Event: %@", caughtEvent);
    12. [self.popupWindow close];
    13. [NSEvent removeMonitor:self.monitoredEventHandler];
    14. }];
    15. }
    Alles anzeigen
    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Oh! Das meinte ich nicht. Sondern: NSUserNotification (habe jetzt doch noch einmal
    nach dem richtigen Klassennamen gesucht; ich hatte das in einer alten App genutzt
    und jetzt gesehen, dass das deprecated ist: offenbar ist das UserNotification Framework
    aktuell (UNNotification*)).

    Diese werden doch auch von den Erinnerungen, der Mail-App etc. verwendet, um den
    Anwender auf etwas aufmerksam zu machen, ohne dass seine aktuelle App den Fokus
    verliert.