Detecting window change of screen

I have users with multiple monitors, one might be retina and one not. My app draws retina-compatible images, but that’s only invoked when the window refreshes. I don’t have a retina Mac to test, but I’m told that moving my app’s windows between such monitors, therefore, results in the wrong image being drawn (until the window redraws). I see that there an API for notifications when a window changes screen

NSWindowDidChangeScreenNotification

Is this the way to go? If so, how does one set up to receive such a notification? I know very little about delegates (if that’s indeed the method to use), so any specific advice is much appreciated. If there’s another, simpler solution, that would be appreciated as well.

Hi Jonathan -
I don’t have multiple monitors or a retina screen to test - but I put together a quick window subclass which should respond to the NSWindowDidChangeScreenNotification. It creates a dynamic window subclass and adds a handler for the notification which is then dispatched to the window and an event is raised which you can respond to.

If someone with multiple monitors can test to confirm that it raises the event as expected that would be great.

https://www.dropbox.com/s/qtvm3wawhm6aq2u/window%20changed%20screen%20window.xojo_binary_project

Wow, thanks, that’s very kind of you. I’ll try incorporating this in my code – I’ve got one user who has already volunteered to test it.

Hopefully it will work - I have never tried implementing any of the NSWindow notifications but this is the method I use to implement functions in other NS classes. Unfortunately the method signature of the notifications is not provided by the apple docs so I’m not sure if I have that part of the implementation correct.

Jonathan,

Just tested your app on my new Mac Pro with multiple monitors. No event was triggered. My main screen is 2560 x 1440 which I suppose would be considered a retina type screen. My second screen is full HD 1920 x 1080. I put a msgbox message line in the event handler but it didn’t fire.

Cheers
Grant

Actually, that’s Jason’s app. :slight_smile: Thank you for testing.

Ok, it looks like I must have had the wrong method signature. Let me see if I can’t find the correct signature in the apple docs.

Ok I have updated the project. The new version works for other notifications sent to the window so it should now work for the changed screen notification, but I am unable to test that. If someone with multiple screens can download and run the project then move the window onto another screen and let me know if a msgbox appears that would be great.

https://www.dropbox.com/s/qtvm3wawhm6aq2u/window%20changed%20screen%20window.xojo_binary_project

I have good news (and maybe bad news). The bad news is that I did a bit of googling (or binging) and found a page where the poster said that the NSWindowDidChangeScreenNotification event fired when the screen resolution was changed, not necessarily when the window changed screens. The good news is that he’s right, and the notification indeed fires when I use System Preferences to change the screen resolution. Whether it will fire when the window moves between screens of different resolutions needs to be tested.

I’ve also come up with what may be a simpler approach. I’m going to retrieve and store the scalingfactor when the window is opened. When the window receives a Moved event it will check to see if the new scalingFactor is different from the stored value. If so, it will issue a self.refresh and save the new value. I’ll report back if this works.

Just wondering, without a two screens system to test : when a window is on the second screen, what is the value of its left property ? From what I saw, one moves a window from one screen to another the same as if the two screens where adjacent.

So if I want a window to show on the second screen, I would for instance set the left property to, say, 2400 when screen(0) is 1920 pix wide ?

If this is the case, it should provide an easy way to detect window change.

If left resets when changing screens relative to the current screen(1) border, then there is no way to place a window on the second screen by code, and that’s plain wrong. But I may be asking the wrong questions ?

Could anybody using a two screens system shed some light ? Thank you in advance.

I have 3 screens
Arranged like

      +--------------------+ +---------------------+ +----------------------+
      |            2       | |         0           | |        1             |
      +--------------------+ +---------------------+ +----------------------+

on os x screen 0 is the one with the menu bar (not sure what this will do under 10.9 as I didnt test that yet but 10.9 puts a menu bar on each screen)
the left for each screen is
0 - 0
1 - 1440
2 - -1920 (yes negative since screen 2 is physically & logically left of screen 0)

[quote=102355:@Norman Palardy]I have 3 screens
Arranged like

      +--------------------+ +---------------------+ +----------------------+
      |            2       | |         0           | |        1             |
      +--------------------+ +---------------------+ +----------------------+

on os x screen 0 is the one with the menu bar (not sure what this will do under 10.9 as I didnt test that yet but 10.9 puts a menu bar on each screen)
the left for each screen is
0 - 0
1 - 1440
2 - -1920 (yes negative since screen 2 is physically & logically left of screen 0)[/quote]

Yes !

Thank you Norman !

So Jonathan now has got a simple way to detect screen change without resorting to declares : after a move, the left of the window will either be more than screen(0) width, or less than zero. If more, screen(1), if less its width, screen(2).

Great :slight_smile:

well thats in my case
I could arrange them logically as

      +--------------------+ +---------------------+ +----------------------+
      |            0       | |         1           | |        2             |
      +--------------------+ +---------------------+ +----------------------+

0 left = 0
1 left = 1920
2 left = 3360

      +--------------------+ +---------------------+ +----------------------+
      |            0       | |         2           | |        1             |
      +--------------------+ +---------------------+ +----------------------+

0 left = 0
1 left = 3360
2 left = 1920

      +--------------------+ +---------------------+ +----------------------+
      |            1       | |         0           | |        2             |
      +--------------------+ +---------------------+ +----------------------+

0 left = 0
1 left = -1920
2 left = 1440

      +--------------------+ +---------------------+ +----------------------+
      |            1       | |         2           | |        0             |
      +--------------------+ +---------------------+ +----------------------+

0 left = 0
1 left = -3360
2 left = -1440

      +--------------------+ +---------------------+ +----------------------+
      |            2       | |         0           | |        1             |
      +--------------------+ +---------------------+ +----------------------+

0 left = 0
1 left = 1440
2 left = -1920

      +--------------------+ +---------------------+ +----------------------+
      |            2       | |         1           | |        0             |
      +--------------------+ +---------------------+ +----------------------+

0 left = 0
1 left = -1440
2 left = -3360

very simply without physically moving them just changing a system preference in the displays preferences panel

I think my method works well. HOWEVER, my beta testers tell me that the window doesn’t redraw to reflect the retina status of the monitor. My question now is: is self.refresh what I should call if the scaleFactor changes? I’m doing that, but the window appearance isn’t changing to reflect the new scaleFactor. Something else I should be doing?

[quote=102218:@Jason King]Ok I have updated the project. The new version works for other notifications sent to the window so it should now work for the changed screen notification, but I am unable to test that. If someone with multiple screens can download and run the project then move the window onto another screen and let me know if a msgbox appears that would be great.

https://www.dropbox.com/s/qtvm3wawhm6aq2u/window%20changed%20screen%20window.xojo_binary_project[/quote]

The updated app worked, the event fired when I moved the mainwindow onto my other screen and fired again when dragged back again.

Cheers
Grant

[quote=102367:@Norman Palardy]well thats in my case
I could arrange them logically as

[/quote]

Then one would have to compare screen(x).left to know the position of the screen. In a way, each screen is like a super window object…

And I can do this while you app is running :stuck_out_tongue:
I’m almost happy again with 3 monitors

[quote=102512:@Norman Palardy]And I can do this while you app is running :stuck_out_tongue:
I’m almost happy again with 3 monitors[/quote]

Almost happy ? Would 4 be better ?

I’d like 3 27" screens but not sure my MBP can drive that even if its 3 Thunderbolts
And the pocket book would be a lot lighter with 3 thunderbolts @ 999 each
So I have a 24" on HDMI, the built in MBP 15" and a 27" running on DVI
It works :stuck_out_tongue:

I had the same issue with Retina, the way how I solved it was to replace all my Retina code after researching how Objective-C applications deal with Retina.

The basic principle is to use NSImage, which can be done with MacOSLib, MBS or my own RetinaKit.