wxPython: Set Which Display the Frame is on

The other day, I saw an interesting question in the wxPython IRC channel. They were asking if there was a way to set which display their application would appear on. Robin Dunn, the creator of wxPython, gave the questioner some pointers, but I decided to go ahead and write up a quick tutorial on the topic.

The wxPython toolkit actually has all the bits and pieces you need for this sort of thing. The first step is getting the combined screen size. What I mean by this is asking wxPython what it thinks is the total size of the screen. This would be the total width and height of all your displays combined. You can get this by calling wx.DisplaySize(), which returns a tuple. If you would like to get individual display resolutions, then you have to call wx.Display and pass in the index of the display. So if you have two displays, then the first display’s resolution could be acquired like this:

index = 0
display = wx.Display(index)
geo = display.GetGeometry()

Let’s write up a quick little application that has a single button that will just switch which display the application is on.

import wx

class MyPanel(wx.Panel):
    
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.frame = parent
        
        mainsizer = wx.BoxSizer(wx.VERTICAL)
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        
        btn = wx.Button(self, label='Switch Display')
        btn.Bind(wx.EVT_BUTTON, self.switch_displays)
        
        sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
        mainsizer.AddStretchSpacer(prop=1)
        mainsizer.Add(sizer, 0, wx.ALL|wx.CENTER, 5)
        mainsizer.AddStretchSpacer(prop=1)
        self.SetSizer(mainsizer)
        
    def switch_displays(self, event):
        combined_screen_size = wx.DisplaySize()
        for index in range(wx.Display.GetCount()):
            display = wx.Display(index)
            geo = display.GetGeometry()
            print(geo)
            
        current_w, current_h = self.frame.GetPosition()
        
        screen_one = wx.Display(0)
        _, _, screen_one_w, screen_one_h = screen_one.GetGeometry()
        screen_two = wx.Display(1)
        _, _, screen_two_w, screen_two_h = screen_two.GetGeometry()
        
        if current_w > combined_screen_size[0] / 2:
            # probably on second screen
            self.frame.SetPosition((int(screen_one_w / 2),
                                   int(screen_one_h / 2)))
        else:
            self.frame.SetPosition((int(screen_one_w + (screen_two_w / 2)),
                                   int(screen_two_h / 2)))
        
class MainFrame(wx.Frame):
    
    def __init__(self):
        wx.Frame.__init__(self, None, title='Display Change')
        panel = MyPanel(self)
        self.Show()
        
if __name__ == '__main__':
    app = wx.App(False)
    frame = MainFrame()
    app.MainLoop()

Here we create two classes. The first contains almost all the code and defines the button and its event handler. The other class is for creating the frame itself. The event handler is where all the fun is though, so let’s look at that. For context, I happen to have two monitors of the same make, model and orientation.

def switch_displays(self, event):
    combined_screen_size = wx.DisplaySize()
    for index in range(wx.Display.GetCount()):
        display = wx.Display(index)
        geo = display.GetGeometry()
        print(geo)

    x, y = self.frame.GetPosition()

    screen_one = wx.Display(0)
    _, _, screen_one_w, screen_one_h = screen_one.GetGeometry()
    screen_two = wx.Display(1)
    _, _, screen_two_w, screen_two_h = screen_two.GetGeometry()

    if x > combined_screen_size[0] / 2:
        # probably on second screen
        self.frame.SetPosition((int(screen_one_w / 2),
                                    int(screen_one_h / 2)))
    else:
        self.frame.SetPosition((int(screen_one_w + (screen_two_w / 2)),
                                    int(screen_two_h / 2)))

Here we pull out the total resolution of both monitors. Then for demonstration purposes, we loop over the displays and print out their geometries. You can comment those lines out as they don’t do anything other than help with debugging.

Then we get the frame’s current position by calling its GetPosition method. Next we extract the two display’s resolutions via a call to each of the display object’s GetGeometry method. Next we check to see if the X coordinate for the frame is greater than the combined width of the displays divided by two. Since I know both of my monitors are the same resolution and orientation, I know this will work. Anyway, if it is greater, then we attempt to move the application to the opposite monitor by calling SetPosition.


Wrapping Up

You should give this code a try and see if it works on your multiple display setup. If it doesn’t, you may need to adjust the math a bit or try to figure out where your OS thinks your monitors are so you can fix the code accordingly.


Additional Reading