israel-dryer / ttkbootstrap

A supercharged theme extension for tkinter that enables on-demand modern flat style themes inspired by Bootstrap.
MIT License
1.87k stars 374 forks source link

Messagebox show in the center #249

Open youstanzr opened 2 years ago

youstanzr commented 2 years ago

Is your feature request related to a problem? Please describe.

It seemed to me a little bit confusing when you're dealing with large app screen and the app suddenly stops cause some entry got wrong error for example and you did not notice the Messagebox showing up at the top left corner of your app.

Describe the solution you'd like

I did a wrapper around Messagebox to show it in the center of the screen and thought it might be useful to have this in ttkbootstrap.dialogs directly as a boolean parameter passed to the show message static method like below

from ttkbootstrap.dialogs import dialogs
# option 1
dialogs.Messagebox.ok(title='title', message='message', is_center=True)
# option 2
dialogs.Messagebox.ok(title='title', message='message', position=CENTER)

Describe alternatives you've considered

Here is the wrapper that I created:

class Alert:
    @staticmethod
    def show_ok(parent, title, message):
        pos = (int(parent.winfo_rootx()+0.5*parent.winfo_width())-150, int(parent.winfo_rooty()+0.33*parent.winfo_height()))
        dialogs.Messagebox.ok(position=pos, title=title, message=message)

    @staticmethod
    def show_warning(parent, title, message):
        pos = (int(parent.winfo_rootx()+0.5*parent.winfo_width())-150, int(parent.winfo_rooty()+0.33*parent.winfo_height()))
        dialogs.Messagebox.show_warning(position=pos, title=title, message=message)

Additional context

No response

funchan24 commented 2 years ago

@Is there any way to center the message box, can't get the real size of message box. error like

_tkinter.TclError: bad window path name ".!mesaagebox"@israel-dryer

israel-dryer commented 2 years ago

@funchan24, @youstanzr, if you want to show the popup over a particular widget or parent window, make sure you give the dialog a parent (see example below). If you do this, it will display on top of the window or widget you are initiating the dialog for.

I'll consider adding some kind of positioning in the future to the constructor. One reason I do not automatically center the windows is because tkinter does not handle multiple screens well, and it was putting it in between two screens.

import ttkbootstrap as ttk
from ttkbootstrap.dialogs import Messagebox

window = ttk.Window()

btn = ttk.Button(window, text='Show message')
btn.configure(command=lambda: Messagebox.show_info(parent=btn, message="Hello"))
btn.pack(padx=20, pady=20)

window.mainloop()
funchan24 commented 2 years ago

@israel-dryer I really want to know if I can get the size of the MessageDialog so that I can center it by myself. I have only one screen. I got a error _tkinter.TclError: bad window path name ".!messagedialog" with below code.

from ttkbootstrap import *
from ttkbootstrap.dialogs import MessageDialog

root = Window()
m = MessageDialog('test', parent=root)
width, height = m.winfo_width(), m.winfo_height()
m.show()
root.mainloop()
israel-dryer commented 2 years ago

@israel-dryer I really want to know if I can get the size of the MessageDialog so that I can center it by myself. I have only one screen. I got a error _tkinter.TclError: bad window path name ".!messagedialog" with below code.

from ttkbootstrap import *
from ttkbootstrap.dialogs import MessageDialog

root = Window()
m = MessageDialog('test', parent=root)
width, height = m.winfo_width(), m.winfo_height()
m.show()
root.mainloop()

@youstanzr, Hmm... apparently I did add a position parameter to the show method. I had forgotten that I did that... so you could technically pass an (x, y) coordinate to anchor the messagebox.

@funchan24, you can't get the position of the messagebox as it is now because it is not actually created until you call the show method. This allows you to keep calling it as needed without it having to exist in the background when not used. However, you could override the show method and access the ._toplevel property to use the position methods you are using now by inserting the following into your show method:

root.update_idletasks()
width = self._toplevel.winfo_width()
height = self._toplevel.winfo_height()

Notice that you need to call update_idletasks if you try to get the width and height before actually showing the widget because it hasn't actually been drawn yet.

NovusOperandi commented 6 months ago

I was having trouble getting a solution for this to work w/ the combo of update_idletasks() and self._toplevel.winfo_width / height, so I ended up going a different route. This override of MessageDialog / Dialog._locate() worked really well though, and instead uses values derived from self._toplevel.winfo_reqwidth / reqheight. P.S. A big 'thank you' to israel-dryer for making the awesome GUI-enhancement module of ttkbootstrap.

        # DEFINE METHOD THAT DETERMINES WHERE ON SCREEN
    # TO PLACE MessageDialog INSTANCE (by default), IF NO EXPLICIT
    # VALUES ARE PASSED FOR 'POSITION' COORDINATES.
    #
    # MessageDialog is a child class of the Dialog class,
    # both of which are defined in the ttkbootstrap module.
    # The method below is an override of the Dialog._locate()
    # method (which is inherited by the MessageDialog class).
    #
    # By default, the original version of this method would place
    # the Dialog instance in the upper-left corner of the parent 
    # widget. This override sets the default Dialog instance placement
    # in the center of the widget instead.
    def _locate(self):

        # IF PARENT OBJECT WAS PASSED...
        if self._parent != None:

            # Define values for parent widget width & height.
            parent_width = self._parent.winfo_width()
            parent_height = self._parent.winfo_height()

            # Define values for center x & y coordinates of 
            # parent widget.
            parent_center_x = math.trunc(self._parent.winfo_x() + (parent_width / 2))
            parent_center_y = math.trunc(self._parent.winfo_y() + (parent_height / 2))

            # Define values for MessageDialog instance width & height
            # based on its _toplevel instance.
            widget_width = self._toplevel.winfo_reqwidth()
            widget_height = self._toplevel.winfo_reqheight()

            # Calculate the center_x & center_y coordinates where 
            # the MessageDialog will need to be situated in order 
            # to be placed at the center of its parent widget.
            center_x = math.trunc(parent_center_x - (widget_width / 2))
            center_y = math.trunc(parent_center_y - (widget_height / 2))

            # Then, set the x & y coordinates used 
            # by the geometry() method below to use 
            # center_x & center_y instead.
            x = center_x
            y = center_y
        #
        # ELSE NO PARENT OBJECT WAS PASSED...
        else:

            # Define values as in original method,
            # using top-left x & y coordinates of 
            # root Tk instance.
            x = self._toplevel.master.winfo_rootx()
            y = self._toplevel.master.winfo_rooty()

        # SET X & Y COORDINATES FOR MessageDialog INSTANCE
        # PLACEMENT.
        self._toplevel.geometry(f"+{x}+{y}")