alejandroautalan / pygubu

A simple GUI builder for the python tkinter module
MIT License
2.01k stars 213 forks source link

Center pygubu window on screen #259

Closed LalFX closed 2 years ago

LalFX commented 2 years ago

Is there any way to center a pygubu window in the middle of the os screen? At the moment it opens up at random locations and I could not find anything in my search to center the ui in the middle of the screen. Any help will be appreciated.

alejandroautalan commented 2 years ago

Hello @LalFX, A quick example that comes to mind is the following:

#!/usr/bin/python3
import pathlib
import pygubu

PROJECT_PATH = pathlib.Path(__file__).parent
PROJECT_UI = PROJECT_PATH / "centered_demo.ui"

class CenteredDemoApp:
    def __init__(self, master=None):
        self.builder = builder = pygubu.Builder()
        builder.add_resource_path(PROJECT_PATH)
        builder.add_from_file(PROJECT_UI)
        self.mainwindow = builder.get_object("topmain", master)
        builder.connect_callbacks(self)

        # center window on screen after creation
        self.mainwindow.after(200, self.center_window)

    def center_window(self):
        """Center a toplevel on screen."""
        toplevel = self.mainwindow
        height = toplevel.winfo_height()
        width = toplevel.winfo_width()
        x_coord = int(toplevel.winfo_screenwidth() / 2 - width / 2)
        y_coord = int(toplevel.winfo_screenheight() / 2 - height / 2)
        geom = f'{width}x{height}+{x_coord}+{y_coord}'
        toplevel.geometry(geom)

    def run(self):
        self.mainwindow.mainloop()

if __name__ == "__main__":
    app = CenteredDemoApp()
    app.run()
<?xml version='1.0' encoding='utf-8'?>
<interface version="1.2">
  <object class="tk.Toplevel" id="topmain">
    <property name="geometry">320x240</property>
    <property name="height">200</property>
    <property name="width">200</property>
    <child>
      <object class="ttk.Frame" id="fmain">
        <property name="height">200</property>
        <property name="width">200</property>
        <layout manager="pack">
          <property name="expand">true</property>
          <property name="side">top</property>
        </layout>
        <child>
          <object class="ttk.Label" id="label1">
            <property name="font">{Helvetica} 24 {}</property>
            <property name="text" translatable="yes">Center Me</property>
            <layout manager="pack">
              <property name="side">top</property>
            </layout>
          </object>
        </child>
      </object>
    </child>
  </object>
</interface>

Let me know if this helps. Regards Alejandro A.

LalFX commented 2 years ago

That works great! Thanks. However, there seems to be a blip where the window appears on one end of the screen and then realigns itself. Is there anyway to avoid the blip?

I reduced the time limit after the "self.mainwindow.after" to 0, but there is still a blip that happens.

alejandroautalan commented 2 years ago

Hello, What OS are you running? What python version?

Another option could be:

#!/usr/bin/python3
import pathlib
import pygubu

PROJECT_PATH = pathlib.Path(__file__).parent
PROJECT_UI = PROJECT_PATH / "centered_demo.ui"

class CenteredDemoApp:
    def __init__(self, master=None):
        self.builder = builder = pygubu.Builder()
        builder.add_resource_path(PROJECT_PATH)
        builder.add_from_file(PROJECT_UI)
        self.mainwindow = builder.get_object("topmain", master)
        builder.connect_callbacks(self)

        # center window on screen after creation
        self._first_init = True
        self.mainwindow.bind('<Map>', self.center_window)

    def center_window(self, event):
        if self._first_init:
            print('centering window...')
            toplevel = self.mainwindow
            height = toplevel.winfo_height()
            width = toplevel.winfo_width()
            x_coord = int(toplevel.winfo_screenwidth() / 2 - width / 2)
            y_coord = int(toplevel.winfo_screenheight() / 2 - height / 2)
            geom = f'{width}x{height}+{x_coord}+{y_coord}'
            toplevel.geometry(geom)
            self._first_init = False

    def run(self):
        self.mainwindow.mainloop()

if __name__ == "__main__":
    app = CenteredDemoApp()
    app.run()

Regards Alejandro A.

jrezai commented 2 years ago

Hello, I'm actually working on a new pull request that will add a new option to center the preview window. I will submit it sometime this week. I just need to test it.

LalFX commented 2 years ago

Hi alejandroautalan: Thanks for the rapid response. This version on the sample is perfect. However, on my UI (which has more elements - its still blips for a millisecond. I got the same sort of effect when I reduced the "after" time on the earlier code to 0 seconds.

Jrezai: That is brilliant! Looking forward to the revised version. If you can add a Splash image option - that would be the icing on the cake!

My OS is Window 10 (21H1) Python: 3.10.1

jrezai commented 2 years ago

@LalFX As I read your question more closely, I realize now you're looking to center the window in your own project. My pull request will be for centering the preview window in Pygubu Designer, not the end-user's final project.

Centering a window can be a little bit complicated depending on which OS you have. For example, in Linux (which is what I use mostly), if I use: root.winfo_screenwidth(), it will give me the width of my 2 monitors combined. Each of my monitors has a width of 1920 pixels (wide), so that function will return: 3840 (1920 x 2), which is not what I expect. In Windows, however, it will return 1920. I'm not sure what MacOS will do.

To get around this, I'll be using the screeninfo module which will return the width of each monitor separately. So I'm afraid my pull request won't be exactly 100% what you're looking for, but I hope the info I provided you helped.

LalFX commented 2 years ago

Thanks for the info provided I managed to get the main window in the middle of the screen. Is there any way to center the second level windows into the middle of the screen as well?

I managed to create an about window following some of the examples, however, tweaking the earlier code did not get me to position the window in the middle of the screen. This is what I tired and while it opens up the second window, it does not position it anywhere.

def call_about(self):
    builder2 = Builder()
    builder2.add_from_file(PROJECT_UI)
    top2 = Toplevel(self.mainwindow)
    frame2 = builder2.get_object('frm_about', top2)
    builder2.connect_callbacks(self)
    height = frame2.winfo_height()
    width = frame2.winfo_width()
    x_coord = int(frame2.winfo_screenwidth() / 2 - width / 2)
    y_coord = int(frame2.winfo_screenheight() / 2 - height / 2)
    geom = f'{width}x{height}+{x_coord}+{y_coord}'
    frame2.bind('<Map>', geom)
LalFX commented 2 years ago

For anyone who was stuck trying to sort this issue out. The following code seems to work. I made a mistake of trying to tie the frame to the 'geometry' tag. I hard coded the size of the about pop-up as it seemed the most efficient way to do it. Change that based on your requirement.

def call_about(self):
    builder2 = Builder()
    builder2.add_from_file(PROJECT_UI)
    top2 = Toplevel(self.mainwindow)
    frame2 = builder2.get_object('frm_about', top2)
    builder2.connect_callbacks(self)
    height = 220
    width = 220
    x_coord = int((top2.winfo_screenwidth()/2) - (width/2))
    y_coord = int((top2.winfo_screenheight()/2) - (height/2))
    geom = f'{width}x{height}+{x_coord}+{y_coord}'
    top2.geometry(geom)