简体   繁体   English

Python Tk:如何创建自定义消息框

[英]Python Tk : How to create a custom message box

Please give me advice.请给我建议。

Custom message box requirements include:自定义消息框要求包括:

  1. Be modal (Realized)模态(已实现)

    While the message box is displayed, the message box is on the front and other TK objects cannot be operated.消息框显示时,消息框在前面,不能操作其他TK对象。

  2. Wait for the return value of the message box等待消息框的返回值

    I want to wait for the message box input before moving on to the next action (display / hide widget etc.).我想在继续下一个操作(显示/隐藏小部件等)之前等待消息框输入。

  3. I want to continue processing the application(Realized)我想继续处理申请(已实现)

    The original Tk object wants to continue regular processing with the after method.原来的 Tk 对象想用 after 方法继续常规处理。

  4. I want to adopt my own design(Realized)我想采用我自己的设计(实现)

    In order to unify the design.为了统一设计。

-Execution result- -执行结果-

If you close the message box, an error is displayed.如果关闭消息框,则会显示错误。 It will not proceed to the second line in the Btn_Messagebox_clicked method until self.MainWindow_obj is closed.在 self.MainWindow_obj 关闭之前,它不会进入 Btn_Messagebox_clicked 方法中的第二行。 I don't know why I get an error.我不知道为什么我收到错误。 Also, if you close the message box, you won't know why it won't come back.此外,如果您关闭消息框,您将不知道为什么它不会回来。

invalid command name "2291801753672dialog_mouse_release"
    while executing
"2291801753672dialog_mouse_release 105 1 ?? ?? ?? 264 103442140 ?? 22 6 ?? 0 ?? ?? .!frame3.!button2 5 438 422 ??"
    invoked from within
"if {"[2291801753672dialog_mouse_release 105 1 ?? ?? ?? 264 103442140 ?? 22 6 ?? 0 ?? ?? .!frame3.!button2 5 438 422 ??]" == "break"} break"
    (command bound to event)

-code- -代码-

import tkinter as tk
from tkinter import ttk

from PIL import Image,ImageTk,ImageDraw,ImageFont

class CustomDialog(object):
    def __init__(self):

        self.title_bar_color = '#8FAADC'
        self.item_ground_color = 'whitesmoke'
        self.background_color = '#D9D9D9'
        self.select_bar_color = '#BDD7EE'

        self.isDrag_DlgMotion = False
        self.drag_dx = 0
        self.drag_dy = 0
    
    def dialog_left_click(self,event):
        dialog_x=self.dev_dialog.winfo_rootx()
        dialog_y=self.dev_dialog.winfo_rooty()
        point_x=self.dev_dialog.winfo_pointerx()
        point_y=self.dev_dialog.winfo_pointery()

        dx = point_x - dialog_x
        dy = point_y - dialog_y

        if (dx >= 0 and dx <= self.title_bar_width) and (dy >= 0 and dy <= self.title_bar_height):
            self.drag_dx = dx
            self.drag_dy = dy
            self.isDrag_DlgMotion = True
        return

    def dialog_mouse_move_on(self,event):
        if self.isDrag_DlgMotion:
            X = event.x_root - self.drag_dx
            Y = event.y_root - self.drag_dy
            self.dev_dialog.geometry('+{0}+{1}'.format(X, Y))
            pass
        return

    def dialog_mouse_release(self,event):
        if self.isDrag_DlgMotion:
            self.isDrag_DlgMotion = False
        return

class CommonMessageBoxDialog(CustomDialog):
    def __init__(self,title,message,state,parent = None):
        self.return_state = None

        if not isinstance(title,str) or not isinstance(message,str) or not isinstance(state,int):
            return
        if state < 1 or state > 3 :
            return

        root = ttk.tkinter.Tk()

        #root = tk.Toplevel(parent)
        #root.overrideredirect(True)

        super().__init__()
        
        self.box_state = state
        self.box_message = message
        self.box_title = title

        W = 0
        H = 1

        self.dlg_size = [400,200]

        self.title_bar_width = self.dlg_size[W]
        self.title_bar_height = 40

        self.btn_bar_height = 42

        self.btn_32x32_size = 42

        self.row_height = 28
        self.btn_row_height = 32

        self.frm_space = 10

        self.parent = parent
        self.CreateDialog(root)

        root.wait_window(root)
        #root.mainloop()
    
    def CreateDialog(self,root):

        W = 0
        H = 1

        if self.parent != None:
            self.parent.update_idletasks()
            ww=self.parent.winfo_screenwidth()
            wh=self.parent.winfo_screenheight()
            x=self.parent.winfo_rootx()
            y=self.parent.winfo_rooty()

            parent_w = self.parent.winfo_width()
            parent_h = self.parent.winfo_height()
            parent_x = self.parent.winfo_x()
            parent_y = self.parent.winfo_y()
        else:
            root.update_idletasks()
            ww=root.winfo_screenwidth()
            wh=root.winfo_screenheight()
            x=root.winfo_rootx()
            y=root.winfo_rooty()

            parent_w = root.winfo_width()
            parent_h = root.winfo_height()
            parent_x = root.winfo_x()
            parent_y = root.winfo_y()


        self.dev_dialog = root
        dialog = self.dev_dialog
        dialog.overrideredirect(True)

        dlg_x = int((parent_x+parent_w) - (self.dlg_size[W]/2))
        dlg_y = int((parent_y+parent_h) - (self.dlg_size[H]/2))

        if dlg_x < 0 : dlg_x = 0 
        if dlg_y < 0 : dlg_y = 0 

        dialog.geometry('{}x{}+{}+{}'.format(self.dlg_size[W],self.dlg_size[H],dlg_x,dlg_y))


        self.Title_Bar = tk.Frame(
            dialog,            
            relief='flat',
            bg = self.title_bar_color ,
            )
        self.Title_Label = tk.Label(
            self.Title_Bar,
            bg = self.title_bar_color ,
            text = self.box_title,
        )

        dialog.bind('<Button-1>', self.dialog_left_click)
        dialog.bind('<B1-Motion>', self.dialog_mouse_move_on)
        dialog.bind('<ButtonRelease-1>',self.dialog_mouse_release)

        self.MsgArea_frame = tk.Frame(
            dialog,            
            relief='flat',
            bg = self.select_bar_color,
            )
        self.message_frame = tk.Frame(
            self.MsgArea_frame,
            relief='flat',
            bg = self.item_ground_color ,
            )
        self.label_message = tk.Label(
            self.message_frame,
            bg = self.item_ground_color ,
            text = self.box_message,
        )

        self.BtnArea_frame = tk.Frame(
            dialog,            
            relief='flat',
            bg = self.item_ground_color,
            )
            
        self.btn_ok = tk.Button(
            self.BtnArea_frame,            
            bg = self.item_ground_color,
            text = 'OK',
            command = lambda:self.btn_msgbox_clicked(1),
            )
            
        self.btn_yes = tk.Button(
            self.BtnArea_frame,            
            bg = self.item_ground_color,
            text = 'YES',
            command = lambda:self.btn_msgbox_clicked(1),
            )
            
        self.btn_no = tk.Button(
            self.BtnArea_frame,            
            bg = self.item_ground_color,
            text = 'NO',
            command = lambda:self.btn_msgbox_clicked(2),
            )
            
        self.btn_cancel = tk.Button(
            self.BtnArea_frame,            
            bg = self.item_ground_color,
            text = 'CANCEL',
            command = lambda:self.btn_msgbox_clicked(3),
            )
                
        frm_space = self.frm_space
        msg_frm_w = 4
        btn_fram_h = 36

        message_area_h = self.dlg_size[H] - self.title_bar_height - frm_space *2 - btn_fram_h

        # Frame
        self.Title_Bar.place(
            x = 0, y = 0, 
            width = self.title_bar_width, height = self.title_bar_height
            )
        self.MsgArea_frame.place(
            x = frm_space, y = self.title_bar_height + frm_space, 
            width = self.title_bar_width - frm_space*2, height = message_area_h
            )
        self.BtnArea_frame.place(
            x = 0, y = self.title_bar_height + frm_space + message_area_h, 
            width = self.title_bar_width, height = btn_fram_h
            )
        
        self.Title_Label.grid(row = 0, column = 1,sticky = tk.W+tk.N+tk.S)
        self.Title_Bar.columnconfigure(0,minsize = self.frm_space)
        self.Title_Bar.rowconfigure(0,minsize = self.title_bar_height)
        
        self.MsgArea_frame.columnconfigure(0,minsize = self.frm_space)
        self.MsgArea_frame.rowconfigure(0,minsize = message_area_h)
        
        self.BtnArea_frame.rowconfigure(0,minsize = btn_fram_h)

        self.message_frame.place(
            x = msg_frm_w, y = msg_frm_w,
            width = self.title_bar_width - frm_space*2 - msg_frm_w*2, height = message_area_h - msg_frm_w*2,
            )

        # self.message_frame
        self.label_message.grid(row = 0, column = 1,sticky = tk.W+tk.N+tk.S)

        if self.box_state == 1:
            self.btn_ok.place(
                x = (self.title_bar_width/2) - 80/2 , y = btn_fram_h/2 - 24/2,
                )            
        if self.box_state == 2:
            self.btn_yes.place(
                x = (self.title_bar_width/2) - (80 + frm_space) , y = btn_fram_h/2 - 24/2,
                )            
            self.btn_no.place(
                x = (self.title_bar_width/2) + frm_space , y = btn_fram_h/2 - 24/2,
                )            
        if self.box_state == 3:
            self.btn_yes.place(
                x = (self.title_bar_width/2) - (80*1.5 + frm_space*2) , y = btn_fram_h/2 - 24/2,
                )            
            self.btn_no.place(
                x = (self.title_bar_width/2) - 80/2 , y = btn_fram_h/2 - 24/2,
                )            
            self.btn_cancel.place(
                x = (self.title_bar_width/2) + 80/2 + frm_space*2 , y = btn_fram_h/2 - 24/2,
                )            

        #dialog.grab_set()
        dialog.grab_set_global()

    def btn_msgbox_clicked(self,state):
        self.return_state = state
        self.dev_dialog.grab_release()
        self.dev_dialog.destroy()

    def get_return_state(self):
        return self.return_state

class CreateScreen(object):
    def __init__(self):
        self.cnt = 0
        W = 0
        H = 1
        self.dlg_size = [400,200]

        geo_string = '{}x{}'.format(self.dlg_size[W],self.dlg_size[H])

        self.MainWindow_obj = ttk.tkinter.Tk()
        self.MainWindow_obj.geometry(geo_string) 

        self.CntSting = tk.StringVar()
        self.CntSting.set('...')

        Label_Conter_text = tk.Label(
            self.MainWindow_obj,
            textvariable = self.CntSting,
        )

        self.MsgSting = tk.StringVar()
        self.MsgSting.set(str(self.cnt))

        Label_Message_text = tk.Label(
            self.MainWindow_obj,
            textvariable = self.MsgSting,
        )

        Btn_Messagebox = tk.Button(
            self.MainWindow_obj,
            text = 'Push',
            command = self.Btn_Messagebox_clicked
        )
        Label_Conter_text.pack()
        Label_Message_text.pack()
        Btn_Messagebox.pack()

        self.MainWindow_obj.after(1000,self.loop_msg)

        self.MainWindow_obj.mainloop()

    def Btn_Messagebox_clicked(self):
        self.dlg = CommonMessageBoxDialog(title='Test',message='Do you remember ?',state=3,parent =self.MainWindow_obj)        
        ret = self.dlg.get_return_state()

        if ret == 1:
            self.MsgSting.set('Yes')
        if ret == 2:
            self.MsgSting.set('No')
        if ret == 3:
            self.MsgSting.set('Cancel')
        
        return

    def loop_msg(self):
        self.cnt += 1
        self.MsgSting.set(str(self.cnt))

        self.MainWindow_obj.after(1000,self.loop_msg)

if __name__ == '__main__':
    screen_obj = CreateScreen()
 

I solved it myself.我自己解决了。

I'm binding mouse events for custom dialog navigation.我正在为自定义对话框导航绑定鼠标事件。 Therefore, after Destroy was called, the dialog with the mouse release event discarded was notified and an error occurred.因此,在调用Destroy 后,会通知丢弃鼠标释放事件的对话框并发生错误。

To solve this, I made self.isDestroy, and in the btn_msgbox_clicked method, changed self.isDestroy to True and connected the dialog_mouse_release method to the destroy method.为了解决这个问题,我制作了self.isDestroy,在btn_msgbox_clicked方法中,将self.isDestroy改为True,将dialog_mouse_release方法连接到destroy方法。

#! /usr/bin/env python3
# -*- coding: utf-8 -*-

import tkinter as tk
from tkinter import ttk

from tkinter import messagebox

from PIL import Image,ImageTk,ImageDraw,ImageFont

import datetime

class CustomDialog(object):
    def __init__(self):

        self.title_bar_color = '#8FAADC'
        self.item_ground_color = 'whitesmoke'
        self.background_color = '#D9D9D9'
        self.select_bar_color = '#BDD7EE'

        self.isDrag_DlgMotion = False
        self.drag_dx = 0
        self.drag_dy = 0

    def dialog_left_click(self,event):
        dialog_x=self.dev_dialog.winfo_rootx()
        dialog_y=self.dev_dialog.winfo_rooty()
        point_x=self.dev_dialog.winfo_pointerx()
        point_y=self.dev_dialog.winfo_pointery()

        dx = point_x - dialog_x
        dy = point_y - dialog_y

        if (dx >= 0 and dx <= self.title_bar_width) and (dy >= 0 and dy <= self.title_bar_height):
            self.drag_dx = dx
            self.drag_dy = dy
            self.isDrag_DlgMotion = True
        return

    def dialog_mouse_move_on(self,event):
        if self.isDrag_DlgMotion:
            X = event.x_root - self.drag_dx
            Y = event.y_root - self.drag_dy
            self.dev_dialog.geometry('+{0}+{1}'.format(X, Y))
            pass
        return

    def dialog_mouse_release(self,event):
        if self.isDrag_DlgMotion:
            self.isDrag_DlgMotion = False
        return

class CommonMessageBoxDialog(CustomDialog):
    def __init__(self,title,message,state,parent = None):
        self.return_state = None
        self.isDestroy = False

        if not isinstance(title,str) or not isinstance(message,str) or not isinstance(state,int):
            return
        if state < 1 or state > 3 :
            return

        root = ttk.tkinter.Tk()

        #root = tk.Toplevel(parent)
        #root.overrideredirect(True)

        super().__init__()

        self.box_state = state
        self.box_message = message
        self.box_title = title

        W = 0
        H = 1

        self.dlg_size = [400,200]

        self.title_bar_width = self.dlg_size[W]
        self.title_bar_height = 40

        self.btn_bar_height = 42

        self.btn_32x32_size = 42

        self.row_height = 28
        self.btn_row_height = 32

        self.frm_space = 10

        self.parent = parent
        self.CreateDialog(root)

        root.wait_window(root)

        #root.mainloop()

    def CreateDialog(self,root):

        W = 0
        H = 1

        if self.parent != None:
            self.parent.update_idletasks()
            ww=self.parent.winfo_screenwidth()
            wh=self.parent.winfo_screenheight()
            x=self.parent.winfo_rootx()
            y=self.parent.winfo_rooty()

            parent_w = self.parent.winfo_width()
            parent_h = self.parent.winfo_height()
            parent_x = self.parent.winfo_x()
            parent_y = self.parent.winfo_y()
        else:
            root.update_idletasks()
            ww=root.winfo_screenwidth()
            wh=root.winfo_screenheight()
            x=root.winfo_rootx()
            y=root.winfo_rooty()

            parent_w = root.winfo_width()
            parent_h = root.winfo_height()
            parent_x = root.winfo_x()
            parent_y = root.winfo_y()


        self.dev_dialog = root
        dialog = self.dev_dialog
        dialog.overrideredirect(True)

        dlg_x = int((parent_x+parent_w) - (self.dlg_size[W]/2))
        dlg_y = int((parent_y+parent_h) - (self.dlg_size[H]/2))

        if dlg_x < 0 : dlg_x = 0 
        if dlg_y < 0 : dlg_y = 0 

        dialog.geometry('{}x{}+{}+{}'.format(self.dlg_size[W],self.dlg_size[H],dlg_x,dlg_y))


        self.Title_Bar = tk.Frame(
            dialog,            
            relief='flat',
            bg = self.title_bar_color ,
            )
        self.Title_Label = tk.Label(
            self.Title_Bar,
            bg = self.title_bar_color ,
            text = self.box_title,
        )

        dialog.bind('<Button-1>', self.dialog_left_click)
        dialog.bind('<B1-Motion>', self.dialog_mouse_move_on)
        dialog.bind('<ButtonRelease-1>',self.dialog_mouse_release)

        self.MsgArea_frame = tk.Frame(
            dialog,            
            relief='flat',
            bg = self.select_bar_color,
            )
        self.message_frame = tk.Frame(
            self.MsgArea_frame,
            relief='flat',
            bg = self.item_ground_color ,
            )
        self.label_message = tk.Label(
            self.message_frame,
            bg = self.item_ground_color ,
            text = self.box_message,
        )

        self.BtnArea_frame = tk.Frame(
            dialog,            
            relief='flat',
            bg = self.item_ground_color,
            )

        self.btn_ok = tk.Button(
            self.BtnArea_frame,            
            bg = self.item_ground_color,
            text = 'OK',
            command = lambda:self.btn_msgbox_clicked(1),
            )

        self.btn_yes = tk.Button(
            self.BtnArea_frame,            
            bg = self.item_ground_color,
            text = 'YES',
            command = lambda:self.btn_msgbox_clicked(1),
            )

        self.btn_no = tk.Button(
            self.BtnArea_frame,            
            bg = self.item_ground_color,
            text = 'NO',
            command = lambda:self.btn_msgbox_clicked(2),
            )

        self.btn_cancel = tk.Button(
            self.BtnArea_frame,            
            bg = self.item_ground_color,
            text = 'CANCEL',
            command = lambda:self.btn_msgbox_clicked(3),
            )

        frm_space = self.frm_space
        msg_frm_w = 4
        btn_fram_h = 36

        message_area_h = self.dlg_size[H] - self.title_bar_height - frm_space *2 - btn_fram_h

        # Frame
        self.Title_Bar.place(
            x = 0, y = 0, 
            width = self.title_bar_width, height = self.title_bar_height
            )
        self.MsgArea_frame.place(
            x = frm_space, y = self.title_bar_height + frm_space, 
            width = self.title_bar_width - frm_space*2, height = message_area_h
            )
        self.BtnArea_frame.place(
            x = 0, y = self.title_bar_height + frm_space + message_area_h, 
            width = self.title_bar_width, height = btn_fram_h
            )

        self.Title_Label.grid(row = 0, column = 1,sticky = tk.W+tk.N+tk.S)
        self.Title_Bar.columnconfigure(0,minsize = self.frm_space)
        self.Title_Bar.rowconfigure(0,minsize = self.title_bar_height)

        self.MsgArea_frame.columnconfigure(0,minsize = self.frm_space)
        self.MsgArea_frame.rowconfigure(0,minsize = message_area_h)

        self.BtnArea_frame.rowconfigure(0,minsize = btn_fram_h)

        self.message_frame.place(
            x = msg_frm_w, y = msg_frm_w,
            width = self.title_bar_width - frm_space*2 - msg_frm_w*2, height = message_area_h - msg_frm_w*2,
            )

        # self.message_frame
        self.label_message.grid(row = 0, column = 1,sticky = tk.W+tk.N+tk.S)

        if self.box_state == 1:
            self.btn_ok.place(
                x = (self.title_bar_width/2) - 80/2 , y = btn_fram_h/2 - 24/2,
                )            
        if self.box_state == 2:
            self.btn_yes.place(
                x = (self.title_bar_width/2) - (80 + frm_space) , y = btn_fram_h/2 - 24/2,
                )            
            self.btn_no.place(
                x = (self.title_bar_width/2) + frm_space , y = btn_fram_h/2 - 24/2,
                )            
        if self.box_state == 3:
            self.btn_yes.place(
                x = (self.title_bar_width/2) - (80*1.5 + frm_space*2) , y = btn_fram_h/2 - 24/2,
                )            
            self.btn_no.place(
                x = (self.title_bar_width/2) - 80/2 , y = btn_fram_h/2 - 24/2,
                )            
            self.btn_cancel.place(
                x = (self.title_bar_width/2) + 80/2 + frm_space*2 , y = btn_fram_h/2 - 24/2,
                )            

        #dialog.grab_set()
        dialog.grab_set_global()

    def btn_msgbox_clicked(self,state):
        self.return_state = state
        self.isDestroy = True

    def get_return_state(self):
        return self.return_state

    def dialog_mouse_release(self,event):
        if self.isDrag_DlgMotion:
            self.isDrag_DlgMotion = False

        if self.isDestroy:
            self._quit()
        return

    def _quit(self):
        self.dev_dialog.grab_release()
        self.dev_dialog.destroy()


class CreateScreen(object):
    def __init__(self):
        self.cnt = 0
        W = 0
        H = 1
        self.dlg_size = [400,200]

        geo_string = '{}x{}'.format(self.dlg_size[W],self.dlg_size[H])

        self.MainWindow_obj = ttk.tkinter.Tk()
        self.MainWindow_obj.geometry(geo_string) 

        self.CntSting = tk.StringVar()
        self.CntSting.set(str(self.cnt))

        Label_Conter_text = tk.Label(
            self.MainWindow_obj,
            textvariable = self.CntSting,
        )

        self.MsgSting = tk.StringVar()
        self.MsgSting.set('...')

        Label_Message_text = tk.Label(
            self.MainWindow_obj,
            textvariable = self.MsgSting,
        )

        Btn_Messagebox = tk.Button(
            self.MainWindow_obj,
            text = 'Push',
            command = self.Btn_Messagebox_clicked
        )
        Label_Conter_text.pack()
        Label_Message_text.pack()
        Btn_Messagebox.pack()

        self.MainWindow_obj.after(1000,self.loop_msg)

        self.MainWindow_obj.mainloop()

    def Btn_Messagebox_clicked(self):
        self.dlg = CommonMessageBoxDialog(title='Test',message='Do you remember ?',state=3,parent =self.MainWindow_obj)        
        ret = self.dlg.get_return_state()

        if ret == 1:
            self.MsgSting.set('Yes')
        if ret == 2:
            self.MsgSting.set('No')
        if ret == 3:
            self.MsgSting.set('Cancel')

        return

    def loop_msg(self):
        self.cnt += 1
        self.CntSting.set(str(self.cnt))

        self.MainWindow_obj.after(1000,self.loop_msg)

if __name__ == '__main__':
    screen_obj = CreateScreen()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM