phone_list.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. import logging
  2. import tkinter as tk
  3. from tkinter import ttk, messagebox
  4. from control.base.base_control import BaseControl, UIControl
  5. class LeftPanel(tk.LabelFrame):
  6. def __init__(self, master=None, control: UIControl = None):
  7. super().__init__(master, text="手机列表")
  8. self.scrollbar = None
  9. self.canvas = None
  10. self.list_frame = None
  11. self.control = control
  12. self.master = master
  13. self.phone_items_data = [] # 用于存储每个手机项的数据 (包含 CheckVar)
  14. self.create_widgets()
  15. self._check_scrollable() # 初始检查是否需要滚动条
  16. def create_widgets(self):
  17. title_label = ttk.Frame(self, relief="flat", borderwidth=1)
  18. title_label.grid(row=0, column=0, padx=10, pady=10, sticky="ew")
  19. # 添加分割线
  20. separator = ttk.Separator(title_label, orient="horizontal")
  21. separator.grid(row=1, column=0, columnspan=4, sticky="ew", padx=5, pady=5)
  22. name_label = ttk.Label(title_label, text='全选')
  23. name_label.grid(row=0, column=1, padx=5, pady=10, sticky="e")
  24. # 选择框
  25. check_all = tk.BooleanVar()
  26. check_button = ttk.Checkbutton(title_label, variable=check_all,
  27. command=lambda v=check_all: self.toggle_check(v))
  28. check_button.grid(row=0, column=2, padx=5, pady=10, sticky="e")
  29. # 添加数据按钮
  30. add_data_button = ttk.Button(title_label, text="刷新", command=self.flush_data)
  31. add_data_button.grid(row=0, column=3, padx=5, pady=10, sticky="e")
  32. # 创建 Canvas 和 Scrollbar
  33. self.canvas = tk.Canvas(self)
  34. self.scrollbar = ttk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
  35. self.canvas.configure(yscrollcommand=self.scrollbar.set)
  36. self.canvas.grid(row=1, column=0, columnspan=4, sticky="nsew")
  37. self.scrollbar.grid(row=1, column=4, sticky="ns")
  38. # 创建 Frame 放入 Canvas 中
  39. self.list_frame = ttk.Frame(self.canvas)
  40. self.canvas.create_window((0, 0), window=self.list_frame, anchor="nw")
  41. self.list_frame.bind("<Configure>", self._on_frame_configure)
  42. self.canvas.bind("<Enter>", self._bind_mousewheel_enter)
  43. self.canvas.bind("<Leave>", self._bind_mousewheel_leave)
  44. # 配置 Grid 的行和列的权重,使 Canvas 可以扩展
  45. self.grid_rowconfigure(1, weight=1)
  46. self.grid_columnconfigure(0, weight=1)
  47. self.grid_columnconfigure(1, weight=0) # 全选按钮不扩展
  48. self.grid_columnconfigure(2, weight=0) # 添加数据按钮不扩展
  49. self.grid_columnconfigure(3, weight=0) # 取消全选按钮不扩展
  50. self.grid_columnconfigure(4, weight=0) # 滚动条列不扩展
  51. self._populate_list()
  52. def _populate_list(self):
  53. """根据 self.phone_data 填充列表"""
  54. for widget in self.list_frame.winfo_children():
  55. widget.destroy()
  56. self.phone_items_data = []
  57. # for phone in self.phone_data:
  58. # self.create_list_item(phone["name"], phone["status"])
  59. logging.info(f"发现连接手机:{BaseControl.connect_dict}")
  60. for pkg, device in BaseControl.connect_dict.items():
  61. logging.info(f'==添加手机信息=>{pkg} {device}')
  62. self.create_list_item(device)
  63. self._on_frame_configure(None) # 更新滚动区域
  64. def create_list_item(self, device):
  65. item_frame = ttk.Frame(self.list_frame, padding=(5, 5))
  66. item_frame.pack(fill="x", expand=True)
  67. status = device['status']
  68. # 选择框
  69. check_var = tk.BooleanVar()
  70. check_var.set(True)
  71. check_button = ttk.Checkbutton(item_frame, variable=check_var)
  72. check_button.pack(side="left")
  73. # 手机名称
  74. name_label = ttk.Label(item_frame, text=f'{device["name"]}:【{device["pkg"]}】', anchor="w", width=20)
  75. name_label.pack(side="left", padx=5)
  76. # 状态指示器
  77. status_canvas = tk.Canvas(item_frame, width=10, height=10, highlightthickness=0)
  78. status_canvas.pack(side="left", padx=5)
  79. if status == "online":
  80. status_canvas.create_oval(0, 0, 10, 10, fill="green")
  81. else:
  82. status_canvas.create_oval(0, 0, 10, 10, fill="red")
  83. # 状态按钮 (这里我们用一个简单的 Label 模拟)
  84. status_button = ttk.Label(item_frame, text="@", width=3, anchor="center", relief="groove")
  85. status_button.pack(side="right", padx=5)
  86. self.phone_items_data.append((check_var, device["serial"], status)) # 存储 CheckVar 和数据
  87. def toggle_check(self, check_var):
  88. if check_var.get():
  89. print("复选框被选中")
  90. else:
  91. print("复选框被取消选中")
  92. for var, _, _ in self.phone_items_data:
  93. var.set(check_var.get())
  94. pass
  95. def flush_data(self):
  96. """
  97. 刷新数据
  98. """
  99. self._populate_list() # 重新填充列表以显示新数据
  100. self._check_scrollable() # 添加数据后检查是否需要滚动条
  101. def _on_frame_configure(self, event):
  102. """当列表 Frame 大小改变时,更新 Canvas 的滚动区域并检查是否需要绑定滚轮事件"""
  103. self.canvas.configure(scrollregion=self.canvas.bbox("all"))
  104. self._check_scrollable()
  105. def _check_scrollable(self):
  106. """检查内容是否需要滚动条,并决定是否需要显示"""
  107. if self.canvas.winfo_height() < self.canvas.bbox("all")[3]:
  108. self.scrollbar.grid(row=1, column=4, sticky="ns") # 显示滚动条
  109. else:
  110. self.scrollbar.grid_remove() # 隐藏滚动条
  111. def _bind_mousewheel_enter(self, event):
  112. """鼠标进入 Canvas 时绑定滚轮事件"""
  113. if self.canvas.winfo_height() < self.canvas.bbox("all")[3]: # 只有在需要滚动时才绑定
  114. self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
  115. self.canvas.bind_all("<Button-4>", self._on_mousewheel) # Linux
  116. self.canvas.bind_all("<Button-5>", self._on_mousewheel) # Linux
  117. def _bind_mousewheel_leave(self, event):
  118. """鼠标离开 Canvas 时解绑滚轮事件"""
  119. self.canvas.unbind_all("<MouseWheel>")
  120. self.canvas.unbind_all("<Button-4>")
  121. self.canvas.unbind_all("<Button-5>")
  122. def _on_mousewheel(self, event):
  123. """鼠标滚轮滚动事件处理"""
  124. if event.delta:
  125. self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
  126. else:
  127. if event.num == 4:
  128. self.canvas.yview_scroll(-1, "units")
  129. elif event.num == 5:
  130. self.canvas.yview_scroll(1, "units")
  131. class RightPanel(tk.Frame):
  132. def __init__(self, master=None, control: UIControl = None, *args, **kwargs):
  133. super().__init__(master, *args, **kwargs)
  134. self._num_text = None
  135. self.control = control
  136. self.master = master
  137. self.create_args()
  138. self.create_info_ares()
  139. self.grid_columnconfigure(0, weight=1) # 让列可以水平扩展
  140. self.grid_rowconfigure(0, weight=0) # 参数设定区域不需要扩展
  141. self.grid_rowconfigure(1, weight=1) # 让操作区域可以垂直扩展
  142. def create_args(self):
  143. fieldset = ttk.LabelFrame(self, text="参数设定")
  144. fieldset.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")
  145. # fieldset.grid_columnconfigure(0, weight=1)
  146. # fieldset.grid_rowconfigure(1, weight=1)
  147. # name_label = ttk.Label(fieldset, text="币种:", width=5)
  148. # name_label.grid(row=0, column=0, padx=5, pady=5, sticky="nw")
  149. #
  150. # name_val = tk.Text(fieldset, width=20, height=1.2, wrap='word')
  151. # name_val.grid(row=0, column=1, padx=5, pady=5, sticky="nw")
  152. # name_val.insert('1.0', 'HMSTR') # 插入文本
  153. # name_val.config(state='disabled') # 再禁用
  154. num_label = ttk.Label(fieldset, text="数量:", width=5)
  155. num_label.grid(row=0, column=1, padx=5, pady=5, sticky="nw")
  156. num_text = tk.Text(fieldset, height=1.2, width=10, wrap='word')
  157. num_text.grid(row=0, column=2, padx=5, pady=5, sticky="nw")
  158. # button1 = ttk.Button(fieldset, text="设置", command=lambda: print('----'))
  159. # button1.grid(row=0, column=3, padx=2, pady=(5, 2), sticky="ew")
  160. self._num_text = num_text
  161. def create_info_ares(self):
  162. # 创建一个 Frame 来模拟 <fieldset>
  163. fieldset = ttk.Frame(self)
  164. fieldset.grid(row=1, column=0, padx=10, pady=10, sticky="nsew")
  165. # 设置 fieldset 的网格权重配置
  166. fieldset.grid_columnconfigure(1, weight=3) # 让信息区域占据更多水平空间
  167. fieldset.grid_columnconfigure(0, weight=1) # 操作区域占据较少水平空间
  168. fieldset.grid_rowconfigure(0, weight=1) # 让内容区域可以垂直扩展
  169. fieldset_action = ttk.LabelFrame(fieldset, text="操作")
  170. fieldset_action.grid(row=0, column=0, padx=10, pady=10, sticky="new")
  171. # 设置列权重使按钮平分宽度
  172. fieldset_action.grid_columnconfigure(0, weight=1)
  173. fieldset_action.grid_columnconfigure(1, weight=1)
  174. fieldset_log = ttk.LabelFrame(fieldset, text="操作日志")
  175. fieldset_log.grid(row=0, column=1, padx=10, pady=10, sticky="nsew")
  176. fieldset_log.grid_columnconfigure(0, weight=1) # 文本框列可扩展
  177. fieldset_log.grid_rowconfigure(1, weight=1) # 文本框行可扩展
  178. # 给fieldset_log 添加一个多行文本区域
  179. log_text = tk.Text(fieldset_log, wrap='word')
  180. log_text.grid(row=1, column=0, padx=5, pady=5, sticky="nsew")
  181. log_text_scroll = ttk.Scrollbar(fieldset_log, command=log_text.yview)
  182. log_text_scroll.grid(row=1, column=1, sticky="ns")
  183. log_text.config(yscrollcommand=log_text_scroll.set)
  184. def handle_keypress(event):
  185. if event.keysym in ('Return', 'KP_Enter'):
  186. log_text.insert('end', '\n')
  187. return 'break'
  188. return None
  189. log_text.bind('<KeyPress>', handle_keypress)
  190. log_text.focus_set() # 让文本框获得焦点
  191. log_text.focus_set() # 让文本框获得焦点
  192. # 给fieldset_action 添加7个按钮,每行2个 共3行
  193. green_btn_style = ttk.Style()
  194. green_btn_style.configure('Green.TButton', background='#00cc00', foreground='#009900')
  195. red_btn_style = ttk.Style()
  196. red_btn_style.configure('Red.TButton', background='#ff4444', foreground='#cc0000')
  197. gray_btn_style = ttk.Style()
  198. gray_btn_style.configure('Gray.TButton', background='#888888', foreground='#666666')
  199. def check_number(text):
  200. try:
  201. float(text) # 尝试转换为浮点数,可以接受整数和小数
  202. return True
  203. except ValueError:
  204. return False
  205. def fun1():
  206. num = self._num_text.get("1.0", "end").rstrip()
  207. if not check_number(num):
  208. messagebox.showinfo("提示", "请输入数量!")
  209. return
  210. self.control.event_2(**{
  211. 'shuliang': num,
  212. 'log': log_text,
  213. })
  214. button1 = ttk.Button(fieldset_action, text="买入/开多", command=lambda: fun1(), style='Green.TButton')
  215. button1.grid(row=0, column=0, padx=2, pady=(5, 2), sticky="ew")
  216. def fun2():
  217. num = self._num_text.get("1.0", "end").rstrip()
  218. if not check_number(num):
  219. messagebox.showinfo("提示", "请输入数量!")
  220. return
  221. self.control.event_4(**{
  222. 'shuliang': num,
  223. 'log': log_text,
  224. })
  225. button2 = ttk.Button(fieldset_action, text="买入/平多", command=lambda: fun2(), style='Green.TButton')
  226. button2.grid(row=0, column=1, padx=2, pady=(5, 2), sticky="ew")
  227. def fun3():
  228. num = self._num_text.get("1.0", "end").rstrip()
  229. if not check_number(num):
  230. messagebox.showinfo("提示", "请输入数量!")
  231. return
  232. self.control.event_6(**{
  233. 'shuliang': num,
  234. 'log': log_text,
  235. })
  236. button3 = ttk.Button(fieldset_action, text="卖出/开空", command=lambda: fun3(), style='Red.TButton')
  237. button3.grid(row=1, column=0, padx=2, pady=2, sticky="ew")
  238. def fun4():
  239. num = self._num_text.get("1.0", "end").rstrip()
  240. if not check_number(num):
  241. messagebox.showinfo("提示", "请输入数量!")
  242. return
  243. self.control.event_8(**{
  244. 'shuliang': num,
  245. 'log': log_text,
  246. })
  247. button4 = ttk.Button(fieldset_action, text="卖出/平空", command=lambda: fun4(), style='Red.TButton')
  248. button4.grid(row=1, column=1, padx=2, pady=2, sticky="ew")
  249. button5 = ttk.Button(fieldset_action, text="一键全平", command=lambda: self.control.event_11(**{
  250. 'shuliang': self._num_text.get("1.0", "end").rstrip(),
  251. 'log': log_text,
  252. }))
  253. button5.grid(row=2, column=0, padx=2, pady=(2, 5), sticky="ew")
  254. button6 = ttk.Button(fieldset_action, text="一键撤单", command=lambda: self.control.event_9(**{
  255. 'shuliang': self._num_text.get("1.0", "end").rstrip(),
  256. 'log': log_text,
  257. }))
  258. button6.grid(row=2, column=1, padx=2, pady=(2, 5), sticky="ew")
  259. class PhoneListUI(tk.Frame):
  260. """
  261. 手机列表控制页面
  262. """
  263. def __init__(self, control: UIControl = None, master=None, cnf=None, **kw):
  264. super().__init__(master, cnf if cnf is not None else {}, **kw)
  265. self.right_panel = None
  266. self.left_panel = None
  267. self.control = control
  268. self.init_ui()
  269. def init_ui(self):
  270. self.left_panel = LeftPanel(self, control=self.control)
  271. self.left_panel.pack(side="left", fill="y")
  272. # 为了演示,我们创建一个简单的右侧区域
  273. self.right_panel = RightPanel(self, width=400, height=400, control=self.control)
  274. self.right_panel.pack(side="left", fill="both", expand=True)