更改ttk组合框下拉列表框的宽度

7

我正在尝试改变 ttk Combobox 弹出列表的宽度。设置 Combobox 的宽度也会改变 Listbox 的宽度,使部分值无法阅读。

我在 Tk/Tcl 中阅读了 这个解决方案,但我不熟悉这种语言,希望用 Python 解决问题。我尝试更改主题参数,但似乎没有帮助。下面是一段示例代码。

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("testing the combobox")
root.geometry('300x300+50+50')
fruit = ['apples are the best', 'bananas are better']

c = ttk.Combobox(root, values=fruit, width=10)
c.pack()

# Trying to change the width, does not work
c.option_add("*TCombobox*Listbox*Width", 50)

root.mainloop()

这里有人能帮我或给我一些指点吗?
3个回答

4

通过深入解读patthoyts给出的优秀答案,我们可以得到一个使用派生样式而不是修改 TCombobox 样式的通用解决方案(但请注意Tk的一个错误,稍后会有更多信息)。

基本上,为每个组合框创建一个具有唯一名称的新样式(我不知道这如何扩展 - 或许只在需要时应用更安全)。此外,从组件本身中读取组合框值,并取最长的值:还有一个检查来避免使下拉框小于窗口部件,如果插入短文本。

import tkinter as tk
import tkinter.ttk as ttk
import tkinter.font as tkfont

def on_combo_configure(event):
    combo = event.widget
    style = ttk.Style()
    # check if the combobox already has the "postoffest" property
    current_combo_style = combo.cget('style') or "TCombobox"
    if len(style.lookup(current_combo_style, 'postoffset'))>0:
        return
    combo_values = combo.cget('values')
    if len(combo_values) == 0:
        return
    longest_value = max(combo_values, key=len)
    font = tkfont.nametofont(str(combo.cget('font')))
    width = font.measure(longest_value + "0") - event.width
    if (width<0):
        # no need to make the popdown smaller
        return
    # create an unique style name using widget's id
    unique_name='Combobox{}'.format(combo.winfo_id())
    # the new style must inherit from curret widget style (unless it's our custom style!) 
    if unique_name in current_combo_style:
        style_name = current_combo_style 
    else:
        style_name = "{}.{}".format(unique_name, current_combo_style)

    style.configure(style_name, postoffset=(0,0,width,0))
    combo.configure(style=style_name)

root = tk.Tk()
root.title("testing the combobox")
root.geometry('300x300+50+50')
fruit = ['apples are the best', 'bananas are way more better']

c = ttk.Combobox(root, values=fruit, width=10)
c.bind('<Configure>', on_combo_configure)
c.pack()

c1 = ttk.Combobox(root, values=['shorter','than','widget'], width=15)
c1.bind('<Configure>', on_combo_configure)
c1.pack()

root.mainloop()

但是...

正如之前所述,Tk Combobox中存在一个bug:从派生样式中只读取TCombobox样式的postoffset属性。

可以通过编辑[python-install-dir]\tcl\tk[version]\ttk\combobox.tcl来解决这个问题;在PlacePopdown方法中找到以下行:

set postoffset [ttk::style lookup TCombobox -postoffset {} {0 0 0 0}]

并将其替换为:

set style [$cb cget -style]
set postoffset [ttk::style lookup $style -postoffset {} {0 0 0 0}]

或者,等待我的拉取请求被合并并发布。


2
Tk库文件(ttk/combobox.tcl)实现了菜单发布,显式地读取组合框部件的宽度,并将菜单顶层设置为相同的宽度(在ttk::combobox::PlacePopdown中)。但是,您可以使用-postoffset配置选项应用主题样式以覆盖此设置。这是为了允许一些主题偏移下拉菜单,但我们可以设置这个选项来允许自定义宽度。注意:所有使用这种样式的部件都会获得相同的宽度,因此您可能需要派生一个自定义样式。
在Tcl/Tk中,这是:ttk::style configure TCombobox -postoffset {0 0 300 0},将下拉菜单设置为300像素宽(x y width height)。
更新
以下Python tkinter代码添加了一些代码到<Configure>事件获取组合框的大小并更新postoffset宽度,使得下拉菜单与字符串列表中的第一个字符串的大小匹配。结果看起来像这样:screenshot of an amended width combobox dropdown
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.font as tkfont

def on_combo_configure(event):
    global fruit
    font = tkfont.nametofont(str(event.widget.cget('font')))
    width = font.measure(fruit[0] + "0") - event.width
    style = ttk.Style()
    style.configure('TCombobox', postoffset=(0,0,width,0))

root = tk.Tk()
root.title("testing the combobox")
root.geometry('300x300+50+50')
fruit = ['apples are the best', 'bananas are better']

c = ttk.Combobox(root, values=fruit, width=10)
c.bind('<Configure>', on_combo_configure)
c.pack()

root.mainloop()

我们使用事件绑定来实现这一点,因为这可以确保我们可以获取小部件在屏幕上的实际大小,从而将其宽度从偏移量中删除。

2

虽然有点晚了,但我最近遇到了这个问题...

我尝试了dipanda的解决方案,并确认派生样式在<Configure>事件期间被改变,但由于某种原因,postoffset选项没有任何效果。它被设置了,但弹出窗口仍然与原始小部件相同的宽度。

对于我的目的而言有效的是以下内容; 与dipanda的代码非常相似:

def combo_configure(event):
    combo = event.widget
    style = ttk.Style()

    long = max(combo.cget('values'), key=len)

    font = tkfont.nametofont(str(combo.cget('font')))
    width = max(0,font.measure(long.strip() + '0') - combo.winfo_width())

    style.configure('TCombobox', postoffset=(0,0,width,0))

但是这里我有一个奇怪的想法:

我没有将其绑定到<Configure>事件,而是将其绑定到<ButtonPress>事件:

widget.bind('<ButtonPress>', combo_configure)

我知道这些更改会影响窗口中的所有组合框,但由于您一次只能拥有一个下拉框,因此这并不重要;对用户来说它是透明的。此外,无需修改combobox.tcl文件!

仅供参考...


绝对不太晚了。完美运作。请注意,您可以使用 width = max(0,font.measure(long.strip() + '0') - combo.winfo_reqwidth()) 缩短函数,这意味着您可以省略 if 语句。此外,函数末尾的 combo.configure(style='TCombobox') 行仍然是必需的。 - Thomas Kühn
不错!还有一个小改动,我在测试时发现了...我将 combo.winfo_reqwidth() 更改为 combo.winfo_width()。我注意到如果我最大化窗口并且组合框的宽度发生变化,它会给它比它需要的更多。更新了我的答案以反映这个改变和 Thomas 的建议 :). - MickeyPvX

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