如何在Tkinter中将窗口居中显示?

92

我想要居中一个tkinter窗口。我知道我可以通过编程获取窗口的大小和屏幕的大小,并使用它来设置几何图形,但我想知道是否有更简单的方法将窗口居中于屏幕。

我想居中一个tkinter窗口。虽然我知道可以通过编写代码获取窗口和屏幕的大小来实现,但是我想知道是否有一种更简单的方法居中窗口。


app.eval('tk::PlaceWindow %s center' % app.winfo_pathname(app.winfo_id())) 在 KDE Plasma 上无法正常工作,我看到过这段代码很多次,但从未正常工作过,更长的版本在 Windows 或 Linux 上每次都能正常工作。 - Requiem
15个回答

97

最简单(但可能不精确)的方法是使用tk::PlaceWindow,它以顶级窗口的路径名作为参数。主窗口的路径名是.

import tkinter

root = tkinter.Tk()
root.eval('tk::PlaceWindow . center')

second_win = tkinter.Toplevel(root)
root.eval(f'tk::PlaceWindow {str(second_win)} center')

root.mainloop()

问题

简单的解决方案忽略了带有标题栏和菜单栏的最外层框架,这导致了一个微小的偏移使其没有真正居中。

解决方案

import tkinter  # Python 3

def center(win):
    """
    centers a tkinter window
    :param win: the main window or Toplevel window to center
    """
    win.update_idletasks()
    width = win.winfo_width()
    frm_width = win.winfo_rootx() - win.winfo_x()
    win_width = width + 2 * frm_width
    height = win.winfo_height()
    titlebar_height = win.winfo_rooty() - win.winfo_y()
    win_height = height + titlebar_height + frm_width
    x = win.winfo_screenwidth() // 2 - win_width // 2
    y = win.winfo_screenheight() // 2 - win_height // 2
    win.geometry('{}x{}+{}+{}'.format(width, height, x, y))
    win.deiconify()

if __name__ == '__main__':
    root = tkinter.Tk()
    root.attributes('-alpha', 0.0)
    menubar = tkinter.Menu(root)
    filemenu = tkinter.Menu(menubar, tearoff=0)
    filemenu.add_command(label="Exit", command=root.destroy)
    menubar.add_cascade(label="File", menu=filemenu)
    root.config(menu=menubar)
    frm = tkinter.Frame(root, bd=4, relief='raised')
    frm.pack(fill='x')
    lab = tkinter.Label(frm, text='Hello World!', bd=4, relief='sunken')
    lab.pack(ipadx=4, padx=4, ipady=4, pady=4, fill='both')
    center(root)
    root.attributes('-alpha', 1.0)
    root.mainloop()

使用tkinter时,您始终需要在检索任何几何信息之前直接调用update_idletasks()方法,以确保返回的值准确。

有四种方法可以确定外框架的尺寸。
winfo_rootx()将为我们提供窗口左上角的x坐标,但不包括外框架。
winfo_x()将为我们提供外框架的左上角x坐标。
它们的差是外框架的宽度。

frm_width = win.winfo_rootx() - win.winfo_x()
win_width = win.winfo_width() + (2*frm_width)

winfo_rooty()winfo_y()之间的区别是它们返回窗口标题栏/菜单栏的高度。

titlebar_height = win.winfo_rooty() - win.winfo_y()
win_height = win.winfo_height() + (titlebar_height + frm_width)

你可以使用 geometry 方法 来设置窗口的尺寸和位置。在 geometry 字符串 的前半部分表示窗口的宽度和高度不包括外框,
而后半部分则是外框左上角的 x 和 y 坐标。

win.geometry(f'{width}x{height}+{x}+{y}')

看到窗口移动

防止看到窗口在屏幕上移动的一种方法是使用 .attributes('-alpha', 0.0) 将窗口设置为完全透明,然后在窗口居中后将其设置为 1.0。在 Windows 7 上,稍后使用 withdraw()iconify() ,然后使用 deiconify() 不会很好地达到此目的。我使用 deiconify() 作为激活窗口的技巧。


使它可选

您可能希望考虑向用户提供一个选项来居中窗口,而不是默认居中;否则,您的代码可能会干扰窗口管理器的功能。例如,xfwm4 具有智能放置功能,它将窗口并排放置直到屏幕被填满。还可以将其设置为居中所有窗口,在这种情况下,您将不会看到窗口移动(如上所述)。


多个监视器

如果多监视器场景让您担心,那么您可以查看 screeninfo 项目,或查看您可以通过 Qt (PySide6)GTK (PyGObject) 实现的功能,然后使用其中之一的工具包而不是 tkinter。合并 GUI 工具包会导致一个不合理大的依赖项。


似乎在通过SSH原始X11连接Windows Xming时,中心功能无法工作。它似乎遵循Windows的标准窗口放置方式,从左上角开始。几何输出看起来正确,但可能与Xming的某种交互无法正常工作? - Kumba
1
在Linux中,-alpha技巧似乎不起作用。不确定我还能做什么。 - Gabriel Staples
你的代码将窗口设置为固定大小。当小部件被添加/删除/调整大小时,窗口不会自动调整大小。最好只设置窗口的位置,保持其大小不变。 - Aran-Fey
1
Aran-Fey,我已经将这段代码实现到我的方法中,该方法创建一个Toplevel窗口,其中包括一个带有图像的标签,该图像会被调整大小。如果您在调用调整大小方法之后调用此方法,则会将具有调整大小图像的窗口放置在中心位置,因此对我而言它确实有效,诀窍就是在调整大小之后调用该方法。 - James

71

你可以试着使用方法winfo_screenwidthwinfo_screenheight,它们分别返回你的Tk实例(窗口)的宽度和高度(以像素为单位),通过一些基本的数学计算,你可以居中你的窗口:

import tkinter as tk
from PyQt4 import QtGui    # or PySide

def center(toplevel):
    toplevel.update_idletasks()

    # Tkinter way to find the screen resolution
    # screen_width = toplevel.winfo_screenwidth()
    # screen_height = toplevel.winfo_screenheight()

    # PyQt way to find the screen resolution
    app = QtGui.QApplication([])
    screen_width = app.desktop().screenGeometry().width()
    screen_height = app.desktop().screenGeometry().height()

    size = tuple(int(_) for _ in toplevel.geometry().split('+')[0].split('x'))
    x = screen_width/2 - size[0]/2
    y = screen_height/2 - size[1]/2

    toplevel.geometry("+%d+%d" % (x, y))
    toplevel.title("Centered!")    

if __name__ == '__main__':
    root = tk.Tk()
    root.title("Not centered")

    win = tk.Toplevel(root)
    center(win)

    root.mainloop()

为了确保返回的值准确,我在检索窗口的宽度和高度之前调用了 update_idletasks 方法。

Tkinter无法识别是否存在2个或更多的水平或垂直扩展监视器。因此,您将获得所有屏幕的总分辨率,并且您的窗口最终将出现在屏幕中央。

另一方面,PyQt也不能看到多屏幕环境,但它只会获取左上角显示器的分辨率(想象一个由4个显示器组成的正方形,其中有2个在上面,2个在下面)。因此,它通过将窗口放置在该屏幕的中心来完成工作。如果不想同时使用 PyQtTkinter,可能最好从一开始就选择 PyQt。


20
“不是最强大的”有点主观。它与其他工具包一样具有同等的“能力”(这取决于您对“能力”的定义),只是它没有提供那么多的辅助功能。这就像用预制墙壁建造房屋(其他某些工具包)或用木材堆建造房屋(Tk)。您在Tkinter中可以做任何其他工具包可以做的事情,只是有时需要付出一些努力。 - Bryan Oakley
5
@Bryan - 这就是我所说的“力量”。当然,你可以开着Yugo(也许)和Ferrari横穿美国,但其中一个可以让你更快地到达目的地。当然,它们都有与用户交互的能力,但Tkinter没有让你用更少的代码(并且更轻松)编写更大程序的能力。当然,在Tkinter中编写较小的程序实际上更容易,因为你不必担心像其他工具包那样的小部件的同样开销,这就是我喜欢Tkinter的原因-编写较小的程序。@psicopoo,不用谢! - Wayne Werner
1
当我使用这个方法时,窗口会“跳跃”到中心位置,刚开始并不会出现在中心位置。为什么会这样? - Mohammad
2
注意:我已经编辑了你的答案,所以它不再设置窗口大小。你原来的代码会阻止窗口在添加/删除/调整小部件时自动调整大小。 - Aran-Fey
2
谢谢! 在我的情况下,我不得不安装 PyQt5 并从中导入 QtWidgets(而不是 from PyQt4 import QtGui)。我猜这是某种升级或更新 :) - Benny
显示剩余7条评论

45

这个答案更适合初学者理解

import tkinter as tk

win = tk.Tk()  # Creating instance of Tk class
win.title("Centering windows")
win.resizable(False, False)  # This code helps to disable windows from resizing

window_height = 500
window_width = 900

screen_width = win.winfo_screenwidth()
screen_height = win.winfo_screenheight()

x_cordinate = int((screen_width/2) - (window_width/2))
y_cordinate = int((screen_height/2) - (window_height/2))

win.geometry("{}x{}+{}+{}".format(window_width, window_height, x_cordinate, y_cordinate))

win.mainloop()

有一个问题涉及到两个屏幕,其中一个是主屏幕,另一个是次要屏幕。窗口在两个屏幕之间启动。 - e-info128

31

Tk提供了一个帮助函数,可以像tk::PlaceWindow这样实现,但我认为它没有作为Tkinter的封装方法暴露出来。要居中一个小部件,请使用以下方法:

from tkinter import *

app = Tk()
app.eval('tk::PlaceWindow %s center' % app.winfo_pathname(app.winfo_id()))
app.mainloop()
这个函数应该能正确地处理多个显示器。它还具有相对于另一个小部件或指针定位的选项(用于放置弹出式菜单),以便它们不会跌落屏幕之外。

这个函数应该能正确地处理多个显示器。它还具有相对于另一个小部件或指针定位的选项(用于放置弹出式菜单),以便它们不会跌落屏幕之外。


2
不幸的是,它没有考虑到带有标题和关闭按钮的外框架;因此,在Windows 7上它仍然没有完全居中。这是对Wayne答案的简短替代方案。+1因为提出了这个问题。也许我们可以通过Tk开发人员将其升级。 - Honest Abe
2
winfo_pathname在Python 3.5.1(Win10上)上失败,并显示“_tkinter.TclError:窗口ID“2885868”在此应用程序中不存在”。我认为我从未在Python 3上使其工作,但从您的小写导入中,我假设它对您有效?我能够通过使用winfo_toplevel更新代码来解决问题:app.eval('tk::PlaceWindow %s center' % app.winfo_toplevel()) - idbrii

24

这也适用于Python 3.x,并将窗口居中显示:

from tkinter import *

app = Tk()
app.eval('tk::PlaceWindow . center')
app.mainloop()

13

我在这个网站上找到了解决相同问题的方法


from tkinter import Tk
from tkinter.ttk import Label
root = Tk()
Label(root, text="Hello world").pack()

# Apparently a common hack to get the window size. Temporarily hide the
# window to avoid update_idletasks() drawing the window in the wrong
# position.
root.withdraw()
root.update_idletasks()  # Update "requested size" from geometry manager

x = (root.winfo_screenwidth() - root.winfo_reqwidth()) / 2
y = (root.winfo_screenheight() - root.winfo_reqheight()) / 2
root.geometry("+%d+%d" % (x, y))

# This seems to draw the window frame immediately, so only call deiconify()
# after setting correct window position
root.deiconify()
root.mainloop()

没错,我按照我的需求进行了相应的更改,它现在可以正常工作了。


10

使用:

import tkinter as tk

if __name__ == '__main__':
    root = tk.Tk()
    root.title('Centered!')

    w = 800
    h = 650

    ws = root.winfo_screenwidth()
    hs = root.winfo_screenheight()
    x = (ws/2) - (w/2)
    y = (hs/2) - (h/2)

    root.geometry('%dx%d+%d+%d' % (w, h, x, y))

    root.mainloop()

2

多显示器解决方案(包括复杂几何形状)

我使用了tkinterscreeninfo的组合,以便在您有一个根窗口并希望将弹出窗口放置在当前活动监视器上的情况下,在屏幕中心定位窗口。

在本例中,root是根窗口,popup是要放置在root所在屏幕中心的弹出窗口。

此解决方案也适用于您的屏幕设置比相邻放置更为复杂的情况,例如上下堆叠。

import tkinter as tk
from screeninfo import get_monitors

def place_popup(popup: tk.Toplevel, root: tk.Tk, width: int, height: int) -> None:
    """Places a new window in the middle of the selected screen"""
    monitor = get_monitor_from_coord(root.winfo_x(), root.winfo_y())
    popup.geometry(
        f"{width}x{height}+{(monitor.width - width) // 2 + monitor.x}+{(monitor.height - height) // 2+ monitor.y}")

def get_monitor_from_coord(x, y):
    """Find the active monitor from tkinter geometry coords"""
    monitors = get_monitors()

    for m in reversed(monitors):
        if m.x <= x <= m.width + m.x and m.y <= y <= m.height + m.y:
            return m
    return monitors[0]

...
place_popup(popup, root, width, height)

2

我使用了框架和展开选项,非常简单。我想在屏幕中间放一些按钮,调整窗口大小后,按钮会保持在中间位置。这是我的解决方案。

frame = Frame(parent_window)
Button(frame, text='button1', command=command_1).pack(fill=X)
Button(frame, text='button2', command=command_2).pack(fill=X)
Button(frame, text='button3', command=command_3).pack(fill=X)
frame.pack(anchor=CENTER, expand=1)

2
from tkinter import * 

root = Tk()

# Gets the requested values of the height and widht.
windowWidth = root.winfo_reqwidth()
windowHeight = root.winfo_reqheight()
print("Width",windowWidth,"Height",windowHeight)

# Gets both half the screen width/height and window width/height
positionRight = int(root.winfo_screenwidth()/2 - windowWidth/2)
positionDown = int(root.winfo_screenheight()/2 - windowHeight/2)

# Positions the window in the center of the page.
root.geometry("+{}+{}".format(positionRight, positionDown))


root.mainloop()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接