说明
1、参考大佬写的博文:Python tkinter自定义多选下拉列表框,根据自己的需要加了一些功能,在这里分享一下
2、内容概况:
- 自定义输入框,我这里改成了ScrolledText标签
- 加了滚动条x,y
- 选项根据自身文本长度展示,解决文本过长部分选项展示不全的问题
- 加了全选按钮功能
- 加了取消全选按钮功能
- 加了反选按钮功能
自定义输入框的修改方法
-
继承标签类
我这里已ScrolledText标签为例
-
替换102行的标签名,对应的实例也需要替换
3、全选、取消全选、反选
全部代码内容如下:
# _*_ coding:utf8 _*_
import tkinter as tk
from tkinter import ttk
from tkinter.scrolledtext import ScrolledText
class Picker(tk.Frame):
def __init__(self, master, values, entry_wid, command):
self._selected_item = None
self._values = values
self._entry_wid = entry_wid
self._command = command
ttk.Frame.__init__(self,
master,
borderwidth=1,
relief="solid")
canvas = tk.Canvas(self)
self.frame_ = tk.Frame(canvas)
# 设置滚动条
sb_y = tk.Scrollbar(self, orient="vertical", command=canvas.yview)
sb_x = tk.Scrollbar(self, orient="horizontal", command=canvas.xview)
canvas.configure(yscrollcommand=sb_y.set)
canvas.configure(xscrollcommand=sb_x.set)
sb_y.pack(side="right", fill="y")
sb_x.pack(side="bottom", fill="x")
canvas.pack(side="left")
canvas.create_window((0, 0), window=self.frame_, anchor='nw')
# 绑定鼠标滚动
def canvas_scroll(event):
if event.delta == -120:
canvas.yview_scroll(1, "units")
elif event.delta == 120:
canvas.yview_scroll(-1, "units")
else:
canvas.yview_scroll(-1 * (event.delta / 120), "units")
self.bind("<MouseWheel>", canvas_scroll)
self.frame_.bind("<Configure>", lambda event: canvas.configure(scrollregion=canvas.bbox("all"), width=400, height=300))
# 绑定焦点事件
self.bind("<FocusIn>", lambda event: self.event_generate('<<PickerFocusIn>>'))
self.bind("<FocusOut>", lambda event: self.event_generate('<<PickerFocusOut>>'))
# 生成选项
self.dict_checkbutton = {}
self.dict_intvar_item = {}
for index,item in enumerate(self._values):
self.dict_intvar_item[item] = tk.IntVar()
self.dict_checkbutton[item] = ttk.Checkbutton(self.frame_,
text=item,
variable=self.dict_intvar_item[item],
width=len(item) + self.str_count(item) * 1.5,
command=lambda ITEM=item: self._command(ITEM, 2))
self.dict_checkbutton[item].grid(row=index, column=0, sticky=tk.NSEW)
self.dict_intvar_item[item].set(0)
# 全选按钮
tk.Button(self.frame_, text='全选', command=lambda select_type=1: self.select_all(select_type)).grid(row=0, column=1, sticky=tk.NSEW)
# 全部取消
tk.Button(self.frame_, text='取消', command=lambda select_type=0: self.select_all(select_type)).grid(row=1, column=1, sticky=tk.NSEW)
# 反选
tk.Button(self.frame_, text='反选', command=lambda select_type=2: self.select_all(select_type)).grid(row=2, column=1, sticky=tk.NSEW)
def select_all(self, select_type=None):
for index, item in enumerate(self._values):
if select_type == 2 and self.dict_intvar_item[item].get() == 0:
self.dict_intvar_item[item].set(1)
elif select_type == 2 and self.dict_intvar_item[item].get() == 1:
self.dict_intvar_item[item].set(0)
else:
self.dict_intvar_item[item].set(select_type)
self._command(item, self.dict_intvar_item[item].get())
def str_count(self, str):
"""统计宽字符的数量,因为中文和数字占用宽度不一样"""
zh_cn = 0
for s in str:
if s.isalpha():
zh_cn += 1
return zh_cn
class Combopicker(Picker, ScrolledText):
def __init__(self, master, values: list):
self.entry_var = tk.StringVar()
# 配置下拉选项和输入框的样式
entry_config = {
"width": 60,
"height": 30,
"wrap": tk.WORD
}
self.st = ScrolledText
self.st.__init__(self, master, **entry_config)
self._is_menuoptions_visible = False
# 初始化参数
self.picker_frame = Picker(self.winfo_toplevel(),
values=values,
entry_wid=self.entry_var,
command=self._on_selected_check)
self.bind_all("<1>", self._on_click, "+")
self.bind("<Escape>", lambda event: self.hide_picker())
# 是否有选择历史审批流程
self.his_sp = False
def get_e_value(self):
if not self.his_sp:
return
# 删除已输入内容
self.st.delete(self, '1.0', 'end')
if self.entry_var.get() == '':
return
# 输入到文本框中
self.st.insert(self, '1.0', self.entry_var.get())
def _on_selected_check(self, SELECTED, it_):
self.his_sp = True
# 勾选
value = []
if self.entry_var.get() != "" and self.entry_var.get() != None:
# 获取字符串动态变量的内容
temp_value = self.entry_var.get()
value = temp_value.split("\n")
# 取消勾选
if str(SELECTED) in value and it_ == 0:
value.remove(str(SELECTED))
elif str(SELECTED) in value and it_ == 2:
value.remove(str(SELECTED))
elif str(SELECTED) not in value and it_ == 2:
value.append(str(SELECTED))
elif str(SELECTED) not in value and it_ == 1:
value.append(str(SELECTED))
else:
pass
value.sort()
# 把值赋值给输入框的变量,
temp_value = ""
for index, item in enumerate(value):
if item != "":
if index != 0:
temp_value += "\n"
temp_value += str(item)
self.entry_var.set(temp_value)
def _on_click(self, event):
# 点击后弹出下拉选项
str_widget = str(event.widget)
if str_widget == str(self):
if not self._is_menuoptions_visible:
self.show_picker()
else:
# 如果点其他地方,如:不在下拉选项中点击则隐藏下拉选项框
if not str_widget.startswith(str(self.picker_frame)) and self._is_menuoptions_visible: # 判断点击的鼠标是否在下拉框内
self.hide_picker()
self.get_e_value()
self.his_sp = False
def show_picker(self):
# 显示下拉选项
if not self._is_menuoptions_visible:
self.picker_frame.place(in_=self, relx=0, rely=1, relwidth=1)
self.picker_frame.lift()
self._is_menuoptions_visible = True
def hide_picker(self):
# 隐藏下拉选项
if self._is_menuoptions_visible:
self.picker_frame.place_forget()
self._is_menuoptions_visible = False
if __name__ == "__main__":
root = tk.Tk()
root.geometry("500x800+100+150")
root.attributes('-topmost', 'true')
v_ = ['CELL-S1',
'CELL-S2',
'CELL-S2',
'CELL-S2',
'CELL-S2',
'CELL-S2',
'CELL-S2',
'CELL-S2',
'CELL-S2',
'CELL-S31222222222222222222222221aaav',
'CELL-S334332',
'CELL-S3呀呀呀呀呀呀晕晕晕q',
'CELL-S3ccccccccccccccccccccccccccc',
'CELL-S3发的哒哒哒哒哒哒多多多f',
'CELL-S3',
'CELL-S3',
'CELL-S3 个梵蒂冈地方个地方s',
'CELL-S3水电费d',
'CELL-S4']
v = ['{:0>2} {}'.format(i+1, v_[i]) for i in range(len(v_))]
cp = Combopicker(root, values=v)
cp.pack(anchor="w")
root.mainloop()