jjzjj

python - 在 Tkinter 中制作自定义标题栏时遇到问题

coder 2024-06-14 原文

我正在尝试在 Tkinter 中自定义标题栏。

现在看起来像这样:

我想把它改成这样:

我已经做到了:

使用以下代码:

def move_window(event):
    app.geometry('+{0}+{1}'.format(event.x_root, event.y_root))

if __name__ == "__main__":
    app = SampleApp()
    app.overrideredirect(True)
    screen_width = app.winfo_screenwidth()
    screen_height = app.winfo_screenheight()
    x_coordinate = (screen_width/2) - (1050/2)
    y_coordinate = (screen_height/2) - (620/2)
    app.geometry("{}x{}+{}+{}".format(1050, 650, int(x_coordinate), int(y_coordinate)))
    title_bar = Frame(app, bg='#090909', relief='raised', bd=0, height=20, width=1050)
    close_button = Button(title_bar, text='X', command=app.destroy, width=5, bg="#090909", fg="#888", bd=0)
    title_bar.place(x=0, y=0)
    close_button.place(rely=0, relx=1, x=0, y=0, anchor=NE)
    title_bar.bind('<B1-Motion>', move_window)
    app.mainloop()

我的代码结构是基于这个 Switch between two frames in tkinter

我希望能够添加一个最小化按钮。我尝试使用 app.iconify() 作为命令创建一个类似于关闭按钮的按钮,但这不能与 overrideredirect(True) 一起使用。

如果能出现在任务栏上就好了。

另外,移动有一个很大的问题,因为每当您尝试移动窗口时,它都会移动窗口,使其左上角位于光标所在的位置。这非常烦人,并且不是 Windows 的典型行为。

如果有人知道如何解决这些问题,我们将不胜感激。

编辑:我现在已经设法制作了一个自定义标题栏,我可以用它来无缝地拖动窗口。我还使该应用程序显示在任务栏中,并在标题栏中添加了一个最小化按钮。但是,我无法让最小化按钮真正起作用。

最佳答案

标题栏和所有其他窗口装饰(最大化、最小化和恢复按钮以及窗口的任何边缘)由窗口管理器拥有和管理。 Tk 必须要求窗口管理器对它们执行任何操作,这就是为什么设置应用程序标题是 wm_title() 调用的原因。通过设置 overrideredirect 标志,您已请求窗口管理完全停止装饰您的窗口,并且您现在正在使用框架在窗口的客户区伪造标题栏。

我的建议是停止与系统作对。这就是窗口管理器主题的用途,Windows 不会让您真正搞砸这些主题。如果您确实仍然想走这条路,您可以将窗口框架装饰创建为 Ttk 主题元素,因为它们是当前样式集合的一部分,并且可以使用 vsapi ttk 元素引擎创建。来自 Visual Styles API 的一些值:

| Function    | Class   | ID               | Numerical ID |
+-------------+---------+------------------+--------------+
| Minimize    | WINDOW  | WP_MINBUTTON     | 15           |
| Maximize    | WINDOW  | WP_MAXBUTTON     | 17           |
| Close       | WINDOW  | WP_CLOSEBUTTON   | 18           |
| Restore     | WINDOW  | WP_RESTOREBUTTON | 21           |

我已经在 python 中像这样使用它们来获得一个关闭按钮元素,然后您可以将其包含在 ttk 小部件样式中。

style = ttk.Style()
# There seems to be some argument parsing bug in tkinter.ttk so cheat and eval
# the raw Tcl code to add the vsapi element for a pin.
root.eval('''ttk::style element create closebutton vsapi WINDOW 18 {
    {pressed !selected} 3
    {active !selected} 2
    {pressed selected} 6
    {active selected} 5
    {selected} 4
    {} 1
}''')

但是,我怀疑您可能仍想避免这些按钮的主题性质,因此您更有可能只想使用 png 图像并使用 Canvas 而不是框架。然后,您可以使用标签绑定(bind)轻松地获取伪标题栏按钮上的事件。

使用ttk主题元素的例子

#!/usr/bin/env python3
"""
Q: Trouble making a custom title bar in Tkinter

https://stackoverflow.com/q/49621671/291641
"""

import tkinter as tk
import tkinter.ttk as ttk
from ctypes import windll

GWL_EXSTYLE=-20
WS_EX_APPWINDOW=0x00040000
WS_EX_TOOLWINDOW=0x00000080

def set_appwindow(root):
    """Change the window flags to allow an overrideredirect window to be
    shown on the taskbar.
    (See https://stackoverflow.com/a/30819099/291641)
    """
    hwnd = windll.user32.GetParent(root.winfo_id())
    style = windll.user32.GetWindowLongPtrW(hwnd, GWL_EXSTYLE)
    style = style & ~WS_EX_TOOLWINDOW
    style = style | WS_EX_APPWINDOW
    res = windll.user32.SetWindowLongPtrW(hwnd, GWL_EXSTYLE, style)
    # re-assert the new window style
    root.wm_withdraw()
    root.after(10, lambda: root.wm_deiconify())

def create_button_element(root, name, id):
    """Create some custom button elements from the Windows theme.
    Due to a parsing bug in the python wrapper, call Tk directly."""
    root.eval('''ttk::style element create {0} vsapi WINDOW {1} {{
        {{pressed !selected}} 3
        {{active !selected}} 2
        {{pressed selected}} 6
        {{active selected}} 5
        {{selected}} 4
        {{}} 1
    }} -syssize {{SM_CXVSCROLL SM_CYVSCROLL}}'''.format(name,id))

class TitleFrame(ttk.Widget):
    """Frame based class that has button elements at one end to
    simulate a windowmanager provided title bar.
    The button click event is handled and generates virtual events
    if the click occurs over one of the button elements."""
    def __init__(self, master, **kw):
        self.point = None
        kw['style'] = 'Title.Frame'
        kw['class'] = 'TitleFrame'
        ttk.Widget.__init__(self, master, 'ttk::frame', kw)
    @staticmethod
    def register(root):
        """Register the custom window style for a titlebar frame.
        Must be called once at application startup."""
        style = ttk.Style()
        create_button_element(root, 'close', 18)
        create_button_element(root, 'minimize', 15)
        create_button_element(root, 'maximize', 17)
        create_button_element(root, 'restore', 21)
        style.layout('Title.Frame', [
            ('Title.Frame.border', {'sticky': 'nswe', 'children': [
                ('Title.Frame.padding', {'sticky': 'nswe', 'children': [
                    ('Title.Frame.close', {'side': 'right', 'sticky': ''}),
                    ('Title.Frame.maximize', {'side': 'right', 'sticky': ''}),
                    ('Title.Frame.minimize', {'side': 'right', 'sticky': ''})
                ]})
            ]})
        ])
        style.configure('Title.Frame', padding=(1,1,1,1), background='#090909')
        style.map('Title.Frame', **style.map('TEntry'))
        root.bind_class('TitleFrame', '<ButtonPress-1>', TitleFrame.on_press)
        root.bind_class('TitleFrame', '<B1-Motion>', TitleFrame.on_motion)
        root.bind_class('TitleFrame', '<ButtonRelease-1>', TitleFrame.on_release)
    @staticmethod
    def on_press(event):
        event.widget.point = (event.x_root,event.y_root)
        element = event.widget.identify(event.x,event.y)
        if element == 'close':
            event.widget.event_generate('<<TitleFrameClose>>')
        elif element == 'minimize':
            event.widget.event_generate('<<TitleFrameMinimize>>')
        elif element == 'restore':
            event.widget.event_generate('<<TitleFrameRestore>>')
    @staticmethod
    def on_motion(event):
        """Use the relative distance since the last motion or buttonpress event
        to move the application window (this widgets toplevel)"""
        if event.widget.point:
            app = event.widget.winfo_toplevel()
            dx = event.x_root - event.widget.point[0]
            dy = event.y_root - event.widget.point[1]
            x = app.winfo_rootx() + dx
            y = app.winfo_rooty() + dy
            app.wm_geometry('+{0}+{1}'.format(x,y))
            event.widget.point=(event.x_root,event.y_root)
    @staticmethod
    def on_release(event):
        event.widget.point = None


class SampleApp(tk.Tk):
    """Example basic application class"""
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        self.wm_geometry('320x240')

def main():
    app = SampleApp()
    TitleFrame.register(app)
    app.overrideredirect(True)
    screen_width = app.winfo_screenwidth()
    screen_height = app.winfo_screenheight()
    x_coordinate = (screen_width/2) - (1050/2)
    y_coordinate = (screen_height/2) - (620/2)
    app.geometry("{}x{}+{}+{}".format(1050, 650, int(x_coordinate), int(y_coordinate)))
    title_bar = TitleFrame(app, height=20, width=1050)
    title_bar.place(x=0, y=0)
    app.bind('<<TitleFrameClose>>', lambda ev: app.destroy())
    app.bind('<<TitleFrameMinimize>>', lambda ev: app.wm_iconify())
    app.bind('<Key-Escape>', lambda ev: app.destroy())
    app.after(10, lambda: set_appwindow(app))
    app.mainloop()

if __name__ == "__main__":
    main()

您无法使最小化工作,因为这是对 overrideredirect 窗口的拒绝操作。我认为如果您需要支持的话,对窗口样式进行一些干预可能会允许创建没有标题栏的顶层窗口。我提供了一个示例,让您的 overrideredirect 窗口出现在任务栏中,但它不会响应来自该窗口的输入,因为这种状态的关键点不是由窗口管理器管理。

关于python - 在 Tkinter 中制作自定义标题栏时遇到问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49621671/

有关python - 在 Tkinter 中制作自定义标题栏时遇到问题的更多相关文章

  1. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  2. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  3. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  4. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  5. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  6. ruby - 通过 RVM (OSX Mountain Lion) 安装 Ruby 2.0.0-p247 时遇到问题 - 2

    我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search

  7. ruby-on-rails - 使用 Rmagick 或 ImageMagick 在背景上放置标题 - 2

    我有一张背景图片,我想在其中添加一个文本框。我想弄清楚如何将标题放置在其顶部的正确位置。(我使用标题是因为我需要自动换行功能)。现在,我只能让文本显示在左上角,但我需要能够手动定位它的开始位置。require'RMagick'require'Pry'includeMagicktext="Loremipsumdolorsitamet"img=ImageList.new('template001.jpg')img 最佳答案 这是使用convert的ImageMagick命令行的答案。如果你想在Rmagick中使用这个方法,你必须自己移植

  8. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  9. ruby - 定义方法参数的条件 - 2

    我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano

  10. ruby - 如何在 Grape 中定义哈希数组? - 2

    我使用Ember作为我的前端和GrapeAPI来为我的API提供服务。前端发送类似:{"service"=>{"name"=>"Name","duration"=>"30","user"=>nil,"organization"=>"org","category"=>nil,"description"=>"description","disabled"=>true,"color"=>nil,"availabilities"=>[{"day"=>"Saturday","enabled"=>false,"timeSlots"=>[{"startAt"=>"09:00AM","endAt"=>

随机推荐