#在上一篇博客中,利用Python制作了一个简易的虚拟桌面切换按钮工具,本片博客对其进行更新了,增加了按钮隐藏时停留左/右两侧的模式选择,并制作了独立的配置工具,不再依赖插入指定U盘切换模式#
目录
一. 代码示例
1. 更新后的虚拟桌面切换器代码
通过读取配置文件第一行内容确定启动模式,1为右侧模式,2为左侧模式。
import tkinter as tk
import os
import pyautogui
import ctypes
from ctypes import wintypes
from PIL import Image, ImageTk
# 基础设置
user32 = ctypes.windll.user32
SCREEN_WIDTH = user32.GetSystemMetrics(0)
SCREEN_HEIGHT = user32.GetSystemMetrics(1)
# 基础高度比例和预设坐标
def load_config():
try:
with open(r'data\data.txt', 'r') as f:
lines = [line.strip() for line in f.readlines() if line.strip()]
mode = int(lines[0])
offset = 0 if mode == 1 else 9
# 行数校验
if len(lines) < (10 + offset):
raise ValueError("配置文件行数不足")
return {
'current_mode': mode,
'WIN_HEIGHT_1': float(lines[1 + offset]),
'WIN_POSITIONS': {
'win1_x': float(lines[2 + offset]),
'win2_x': float(lines[3 + offset]),
'win3_x': float(lines[4 + offset]),
'col_x': float(lines[5 + offset]),
'h_y': float(lines[6 + offset]),
'c_y': float(lines[7 + offset]),
'a_y': float(lines[8 + offset]),
'x_y': float(lines[9 + offset])
}
}
except Exception as e:
print(f"加载配置文件失败")
# 读取配置
config = load_config()
WIN_HEIGHT_1 = config['WIN_HEIGHT_1']
WIN_POSITIONS = config['WIN_POSITIONS']
# 在类中添加模式控制
class VirtualDesktopToggler:
def __init__(self):
self.root = tk.Tk()
self.root.overrideredirect(1)
self.windows = {}
self.win_sizes = {}
self.dragging = None
self.orig_pos = {}
self.current_mode = config['current_mode'] # 先初始化模式属性
self.setup_ui() # 后执行UI初始化
def load_scaled_image(self, path, target_size):
"""加载并缩放图片,保持比例居中填充"""
img_pil = Image.open(path).convert("RGBA")
canvas = Image.new("RGBA", target_size, (0,0,0,0))
img_ratio = img_pil.width / img_pil.height
target_ratio = target_size[0] / target_size[1]
if img_ratio > target_ratio:
scaled_w = target_size[0]
scaled_h = int(target_size[0] / img_ratio)
else:
scaled_h = target_size[1]
scaled_w = int(target_size[1] * img_ratio)
scaled_img = img_pil.resize((scaled_w, scaled_h), Image.LANCZOS)
x = (target_size[0] - scaled_w) // 2
y = (target_size[1] - scaled_h) // 2
canvas.paste(scaled_img, (x, y), scaled_img)
return ImageTk.PhotoImage(canvas)
def setup_ui(self):
"""初始化所有窗口控件及布局"""
# 尺寸计算
base_size = int(SCREEN_HEIGHT / 12)
win3_size = (int(SCREEN_HEIGHT/24), int(SCREEN_HEIGHT/12))
control_size = (int(SCREEN_WIDTH/9), int(SCREEN_HEIGHT/20))
# 创建窗口 - 同时创建两个模式的窗口
for mode_suffix in ['', '_1']:
for i in range(1,8):
win = tk.Toplevel()
win.overrideredirect(1)
win.wm_attributes('-topmost', 1)
win.wm_attributes('-transparentcolor', 'white')
win.configure(bg='white')
self.windows[f'win{i}{mode_suffix}'] = win # 添加后缀区分模式
# 窗口位置和尺寸设置(根据当前模式加载配置)
current_prefix = '' if self.current_mode == 1 else '_1'
pos_settings = [
(1, base_size, base_size, SCREEN_WIDTH*WIN_POSITIONS['win1_x'], SCREEN_HEIGHT*(1-WIN_HEIGHT_1)),
(2, base_size, base_size, SCREEN_WIDTH*WIN_POSITIONS['win2_x'], SCREEN_HEIGHT*(1-WIN_HEIGHT_1)),
(3, *win3_size, SCREEN_WIDTH*WIN_POSITIONS['win3_x'], SCREEN_HEIGHT*(1-WIN_HEIGHT_1)),
(4, *control_size, SCREEN_WIDTH*WIN_POSITIONS['col_x'], SCREEN_HEIGHT*WIN_POSITIONS['h_y']),
(5, *control_size, SCREEN_WIDTH*WIN_POSITIONS['col_x'], SCREEN_HEIGHT*WIN_POSITIONS['c_y']),
(6, *control_size, SCREEN_WIDTH*WIN_POSITIONS['col_x'], SCREEN_HEIGHT*WIN_POSITIONS['a_y']),
(7, *control_size, SCREEN_WIDTH*WIN_POSITIONS['col_x'], SCREEN_HEIGHT*WIN_POSITIONS['x_y']),
]
# 只设置当前模式的窗口位置
for num, w, h, x, y in pos_settings:
win = self.windows[f'win{num}{current_prefix}']
win.geometry(f"{w}x{h}+{int(x)}+{int(y)}")
self.win_sizes[f'win{num}{current_prefix}'] = (w, h)
# 隐藏非当前模式的窗口
for suffix in ['_1', '']:
if suffix != current_prefix:
for i in range(1,8):
self.windows[f'win{i}{suffix}'].withdraw()
self.update_appearance()
self.bind_events()
self.root.withdraw()
def update_appearance(self):
"""更新所有窗口的透明度和贴图"""
current_prefix = '' if self.current_mode == 1 else '_1'
alpha_config = {
1: ('L1', 0.3),
2: ('R1', 0.3),
3: ('B2', 0.3) if self.current_mode == 2 else ('B1', 0.3),
4: ('H', 0.8),
5: ('C', 0.8),
6: ('A', 0.8),
7: ('X', 0.8)
}
for num in range(1,8):
win = self.windows[f'win{num}{current_prefix}']
img_file, alpha = alpha_config[num]
img_path = f'img/{img_file}.png'
try:
w, h = self.win_sizes[f'win{num}{current_prefix}']
img = self.load_scaled_image(img_path, (w, h))
except Exception as e:
print(f"Error loading {img_path}: {str(e)}")
continue
for child in win.winfo_children():
child.destroy()
label = tk.Label(win, image=img, bg='white')
label.pack(fill="both", expand=True)
label.image = img
win.wm_attributes('-alpha', alpha)
self.orig_pos[f'win{num}'] = (win.winfo_x(), win.winfo_y())
# 隐藏不需要的控件
for w in ['win3','win4','win5','win6','win7']:
self.windows[f'{w}{current_prefix}'].withdraw()
def bind_events(self):
"""绑定控件事件处理"""
current_prefix = '' if self.current_mode == 1 else '_1'
# 主按钮事件绑定到当前模式的窗口
for num in [1, 2]:
win_name = f'win{num}{current_prefix}'
self.windows[win_name].bind('<Button-1>', lambda e,n=num: self.on_press(n))
self.windows[win_name].bind('<ButtonRelease-1>', lambda e,n=num: self.on_release(n))
self.windows[win_name].bind('<Button-3>', self.show_controls)
# 绑定当前模式的控制面板控件
self.windows[f'win3{current_prefix}'].bind('<Button-1>', self.restore_main)
self.windows[f'win4{current_prefix}'].bind('<Button-1>', lambda e: self.hide_controls(animate=True))
self.windows[f'win5{current_prefix}'].bind('<Button-1>', lambda e: os._exit(0))
self.windows[f'win6{current_prefix}'].bind('<Button-1>', self.create_desktop)
self.windows[f'win7{current_prefix}'].bind('<Button-1>', self.close_desktop)
def on_press(self, num):
"""处理按钮按下事件
Args:
num: 按钮编号 (1:左按钮,2:右按钮)
"""
current_prefix = '' if self.current_mode == 1 else '_1'
if self.windows[f'win4{current_prefix}'].winfo_viewable():
self.hide_controls()
return
win = self.windows[f'win{num}{current_prefix}']
win.wm_attributes('-alpha', 0.5)
img_file = 'L2' if num ==1 else 'R2'
try:
w, h = self.win_sizes[f'win{num}{current_prefix}']
img = self.load_scaled_image(f'img/{img_file}.png', (w, h))
win.children['!label'].config(image=img)
win.children['!label'].image = img
except Exception as e:
print(f"Error loading pressed image: {str(e)}")
pyautogui.hotkey('ctrl', 'win', 'left' if num==1 else 'right')
def on_release(self, num):
current_prefix = '' if self.current_mode == 1 else '_1'
win = self.windows[f'win{num}{current_prefix}']
win.wm_attributes('-alpha', 0.3)
img_file = 'L1' if num ==1 else 'R1'
try:
w, h = self.win_sizes[f'win{num}{current_prefix}']
img = self.load_scaled_image(f'img/{img_file}.png', (w, h))
win.children['!label'].config(image=img)
win.children['!label'].image = img
except Exception as e:
print(f"Error loading released image: {str(e)}")
def create_desktop(self, event):
pyautogui.hotkey('ctrl', 'win', 'd')
self.hide_controls(animate=False) # 执行后隐藏控制面板
def close_desktop(self, event):
pyautogui.hotkey('ctrl', 'win', 'f4')
self.hide_controls(animate=False) # 执行后隐藏控制面板
def show_controls(self, event):
current_prefix = '' if self.current_mode == 1 else '_1'
for w in ['win4','win5','win6','win7']:
self.windows[f'{w}{current_prefix}'].deiconify()
def hide_controls(self, event=None, animate=False):
current_prefix = '' if self.current_mode == 1 else '_1'
for w in ['win4','win5','win6','win7']:
self.windows[f'{w}{current_prefix}'].withdraw()
if animate:
self.animate_move_out()
def animate_move_out(self):
current_prefix = '' if self.current_mode == 1 else '_1'
def move_step():
win1 = self.windows[f'win1{current_prefix}']
win2 = self.windows[f'win2{current_prefix}']
win1_x = win1.winfo_x()
win2_x = win2.winfo_x()
if (self.current_mode == 1 and (win1_x < SCREEN_WIDTH or win2_x < SCREEN_WIDTH)) or \
(self.current_mode == 2 and (win1_x > -100 or win2_x > -100)):
delta = 50 if self.current_mode == 1 else -50
new_x1 = win1_x + delta
new_x2 = win2_x + delta
win1.geometry(f"+{new_x1}+{win1.winfo_y()}")
win2.geometry(f"+{new_x2}+{win2.winfo_y()}")
self.root.after(10, move_step)
else:
win1.withdraw()
win2.withdraw()
self.windows[f'win3{current_prefix}'].deiconify() # 更新win3引用
move_step() # 启动动画
def restore_main(self, event):
prefix = '' if self.current_mode == 1 else '_1'
self.windows[f'win3{prefix}'].withdraw()
# 使用动态配置
current_config = load_config()['WIN_POSITIONS'] # 重新加载当前配置
base_size = int(SCREEN_HEIGHT / 12)
y_position = int(SCREEN_HEIGHT * (1 - config['WIN_HEIGHT_1']))
# 更新窗口1的几何属性
self.windows[f'win1{prefix}'].geometry(
f"{base_size}x{base_size}+{int(SCREEN_WIDTH * current_config['win1_x'])}+{y_position}"
)
self.windows[f'win1{prefix}'].deiconify()
# 更新窗口2的几何属性
self.windows[f'win2{prefix}'].geometry(
f"{base_size}x{base_size}+{int(SCREEN_WIDTH * current_config['win2_x'])}+{y_position}"
)
self.windows[f'win2{prefix}'].deiconify()
if __name__ == '__main__':
app = VirtualDesktopToggler()
app.root.mainloop()
2. 配置程序的代码
所有的按键将会显示出来,可拖动改变布局并更新配置文件,点击屏幕中央的按钮后关闭
import tkinter as tk
import os
import pyautogui
import ctypes
from ctypes import wintypes
from PIL import Image, ImageTk
# 基础设置
user32 = ctypes.windll.user32
SCREEN_WIDTH = user32.GetSystemMetrics(0)
SCREEN_HEIGHT = user32.GetSystemMetrics(1)
# 基础高度比例和预设坐标
def load_config():
try:
with open(r'data\data.txt', 'r') as f:
lines = [line.strip() for line in f.readlines() if line.strip()]
mode = int(lines[0])
offset = 0 if mode == 1 else 9
# 行数校验
if len(lines) < (10 + offset):
raise ValueError("配置文件行数不足")
return {
'current_mode': mode,
'WIN_HEIGHT_1': float(lines[1 + offset]),
'WIN_POSITIONS': {
'win1_x': float(lines[2 + offset]),
'win2_x': float(lines[3 + offset]),
'win3_x': float(lines[4 + offset]),
'col_x': float(lines[5 + offset]),
'h_y': float(lines[6 + offset]),
'c_y': float(lines[7 + offset]),
'a_y': float(lines[8 + offset]),
'x_y': float(lines[9 + offset])
}
}
except Exception as e:
print(f"加载配置文件失败")
# 读取配置
config = load_config()
WIN_HEIGHT_1 = config['WIN_HEIGHT_1']
WIN_POSITIONS = config['WIN_POSITIONS']
# 在类中添加模式控制
class VirtualDesktopToggler:
def __init__(self):
self.root = tk.Tk()
self.root.overrideredirect(1)
self.windows = {}
self.win_sizes = {}
self.dragging = None
self.orig_pos = {}
self.current_mode = config['current_mode'] # 先初始化模式属性
self.setup_ui() # 后执行UI初始化
def setup_ui(self):
# 尺寸计算(修改win3尺寸)
base_size = int(SCREEN_HEIGHT / 12)
win3_size = (base_size, base_size) # 修改为与win1/win2相同尺寸
control_size = (int(SCREEN_WIDTH/9), int(SCREEN_HEIGHT/20))
# 创建窗口
for mode_suffix in ['', '_1']:
for i in range(1,8):
win = tk.Toplevel()
win.name = f'win{i}{mode_suffix}' # 添加窗口名称标识
win.overrideredirect(1)
win.wm_attributes('-topmost', 1)
win.wm_attributes('-transparentcolor', 'white')
win.configure(bg='white')
self.windows[f'win{i}{mode_suffix}'] = win # 添加后缀区分模式
# 新增事件绑定
win.bind("<ButtonPress-1>", self.on_drag_start)
win.bind("<B1-Motion>", self.on_drag_motion)
win.bind("<ButtonRelease-1>", self.on_drag_end)
self.windows[f'win{i}{mode_suffix}'] = win # 添加后缀区分模式
# 窗口位置和尺寸设置(根据当前模式加载配置)
current_prefix = '' if self.current_mode == 1 else '_1'
pos_settings = [
(1, base_size, base_size, SCREEN_WIDTH*WIN_POSITIONS['win1_x'], SCREEN_HEIGHT*(1-WIN_HEIGHT_1)),
(2, base_size, base_size, SCREEN_WIDTH*WIN_POSITIONS['win2_x'], SCREEN_HEIGHT*(1-WIN_HEIGHT_1)),
(3, *win3_size, SCREEN_WIDTH*WIN_POSITIONS['win3_x'], int(SCREEN_HEIGHT*(1 - WIN_HEIGHT_1))),
(4, *control_size, SCREEN_WIDTH*WIN_POSITIONS['col_x'], SCREEN_HEIGHT*WIN_POSITIONS['h_y']),
(5, *control_size, SCREEN_WIDTH*WIN_POSITIONS['col_x'], SCREEN_HEIGHT*WIN_POSITIONS['c_y']),
(6, *control_size, SCREEN_WIDTH*WIN_POSITIONS['col_x'], SCREEN_HEIGHT*WIN_POSITIONS['a_y']),
(7, *control_size, SCREEN_WIDTH*WIN_POSITIONS['col_x'], SCREEN_HEIGHT*WIN_POSITIONS['x_y']),
]
# 只设置当前模式的窗口位置
for num, w, h, x, y in pos_settings:
win = self.windows[f'win{num}{current_prefix}']
win.geometry(f"{w}x{h}+{int(x)}+{int(y)}")
self.win_sizes[f'win{num}{current_prefix}'] = (w, h)
# 隐藏非当前模式的窗口
for suffix in ['_1', '']:
if suffix != current_prefix:
for i in range(1,8):
self.windows[f'win{i}{suffix}'].withdraw()
self.windows[f'win3{current_prefix}'].withdraw()
self.update_appearance()
self.root.withdraw()
# 退出按钮窗口
btn_size = int(SCREEN_HEIGHT / 9)
exit_win = tk.Toplevel()
exit_win.overrideredirect(1)
exit_win.wm_attributes('-topmost', 1)
exit_win.geometry(f"{btn_size}x{btn_size}+"
f"{(SCREEN_WIDTH-btn_size)//2}+"
f"{(SCREEN_HEIGHT-btn_size)//2}")
try:
img = self.load_scaled_image('cfgimg/8.png', (btn_size, btn_size))
label = tk.Label(exit_win, image=img, bg='white')
label.pack()
label.image = img
except Exception as e:
print(f"无法加载退出按钮图片: {str(e)}")
# 绑定点击事件
exit_win.bind("<Button-1>", lambda e: self.root.destroy())
exit_win.wm_attributes('-transparentcolor', 'white')
exit_win.configure(bg='white')
exit_win.wm_attributes('-alpha', 1.0)
def load_scaled_image(self, path, target_size):
"""加载并缩放图片,保持比例居中填充"""
img_pil = Image.open(path).convert("RGBA")
canvas = Image.new("RGBA", target_size, (0,0,0,0))
img_ratio = img_pil.width / img_pil.height
target_ratio = target_size[0] / target_size[1]
if img_ratio > target_ratio:
scaled_w = target_size[0]
scaled_h = int(target_size[0] / img_ratio)
else:
scaled_h = target_size[1]
scaled_w = int(target_size[1] * img_ratio)
scaled_img = img_pil.resize((scaled_w, scaled_h), Image.LANCZOS)
x = (target_size[0] - scaled_w) // 2
y = (target_size[1] - scaled_h) // 2
canvas.paste(scaled_img, (x, y), scaled_img)
return ImageTk.PhotoImage(canvas)
def on_drag_start(self, event):
"""开始拖动时的处理"""
widget = event.widget.master # 获取父级Toplevel窗口
self.dragging = widget
self.start_x = event.x_root
self.start_y = event.y_root
self.orig_pos = (widget.winfo_x(), widget.winfo_y())
def on_drag_motion(self, event):
"""拖动过程中的处理"""
if not self.dragging:
return
delta_x = event.x_root - self.start_x
delta_y = event.y_root - self.start_y
new_x = self.orig_pos[0] + delta_x
new_y = self.orig_pos[1] + delta_y
# 更新被拖动窗口的位置
self.dragging.geometry(f"+{new_x}+{new_y}")
# 同步处理win1-3垂直坐标
if self.dragging.name.startswith(('win1','win2','win3')):
mode_suffix = '_' + self.dragging.name.split('_')[1] if '_' in self.dragging.name else ''
current_y = new_y # 使用新的Y坐标
# 同步其他两个窗口的Y坐标(保持各自X坐标不变)
for n in ['1','2','3']:
if n != self.dragging.name[3]: # 排除当前拖动窗口
other_win = self.windows.get(f'win{n}{mode_suffix}')
if other_win:
current_x = other_win.winfo_x()
other_win.geometry(f"+{current_x}+{current_y}")
# 同步处理win4-7横向坐标
elif self.dragging.name.startswith(('win4','win5','win6','win7')):
# 获取当前窗口编号和模式后缀
win_num = self.dragging.name[3:].split('_')[0]
mode_suffix = '_' + self.dragging.name.split('_')[1] if '_' in self.dragging.name else ''
# 同步其他三个控件
for n in ['4','5','6','7']:
if n != win_num:
other_win = self.windows.get(f'win{n}{mode_suffix}')
if other_win:
current_y = other_win.winfo_y()
other_win.geometry(f"+{new_x}+{current_y}")
def on_drag_end(self, event):
"""拖动结束时的处理"""
# 原有逻辑
self.dragging = None
# 新增保存逻辑
if hasattr(event.widget, 'master'):
win_name = event.widget.master.name # 获取窗口名称
if win_name.startswith(('win1','win2','win4','win5','win6','win7')):
self.save_positions_to_file()
def save_positions_to_file(self):
"""将当前位置保存到配置文件"""
try:
# 读取原始文件
with open(r'data\data.txt', 'r') as f:
lines = [line.strip() for line in f.readlines()]
mode = int(lines[0])
offset = 0 if mode == 1 else 9
# 获取当前所有窗口位置
current_prefix = '' if mode == 1 else '_1'
win_positions = {
'win1': self.windows[f'win1{current_prefix}'].winfo_x() / SCREEN_WIDTH,
'win2': self.windows[f'win2{current_prefix}'].winfo_x() / SCREEN_WIDTH,
'win3': self.windows[f'win3{current_prefix}'].winfo_x() / SCREEN_WIDTH,
'col_x': self.windows[f'win4{current_prefix}'].winfo_x() / SCREEN_WIDTH,
'h_y': self.windows[f'win4{current_prefix}'].winfo_y() / SCREEN_HEIGHT,
'c_y': self.windows[f'win5{current_prefix}'].winfo_y() / SCREEN_HEIGHT,
'a_y': self.windows[f'win6{current_prefix}'].winfo_y() / SCREEN_HEIGHT,
'x_y': self.windows[f'win7{current_prefix}'].winfo_y() / SCREEN_HEIGHT
}
# 新增计算高度比例
win1_y = self.windows[f'win1{current_prefix}'].winfo_y()
new_win_height = 1 - (win1_y / SCREEN_HEIGHT) # 根据实际Y坐标反向计算
# 更新对应模式的数据行
lines[1 + offset] = f"{new_win_height:.4f}" # 更新WIN_HEIGHT_1的值
lines[2 + offset] = f"{win_positions['win1']:.4f}"
lines[3 + offset] = f"{win_positions['win2']:.4f}"
lines[4 + offset] = f"{win_positions['win3']:.4f}"
lines[5 + offset] = f"{win_positions['col_x']:.4f}"
lines[6 + offset] = f"{win_positions['h_y']:.4f}"
lines[7 + offset] = f"{win_positions['c_y']:.4f}"
lines[8 + offset] = f"{win_positions['a_y']:.4f}"
lines[9 + offset] = f"{win_positions['x_y']:.4f}"
# 写回文件
with open(r'data\data.txt', 'w') as f:
f.write('\n'.join(lines))
except Exception as e:
print(f"保存配置失败: {str(e)}")
def update_appearance(self):
"""更新所有窗口的透明度和贴图"""
current_prefix = '' if self.current_mode == 1 else '_1'
alpha_config = {
1: ('L1', 0.3),
2: ('R1', 0.3),
3: ('B1', 0),
4: ('H', 0.8),
5: ('C', 0.8),
6: ('A', 0.8),
7: ('X', 0.8)
}
for num in range(1,8):
win = self.windows[f'win{num}{current_prefix}']
img_file, alpha = alpha_config[num]
img_path = f'cfgimg/{img_file}.png'
try:
w, h = self.win_sizes[f'win{num}{current_prefix}']
img = self.load_scaled_image(img_path, (w, h))
except Exception as e:
print(f"Error loading {img_path}: {str(e)}")
continue
for child in win.winfo_children():
child.destroy()
label = tk.Label(win, image=img, bg='white')
label.pack(fill="both", expand=True)
label.image = img
win.wm_attributes('-alpha', alpha)
self.orig_pos[f'win{num}'] = (win.winfo_x(), win.winfo_y())
if __name__ == '__main__':
app = VirtualDesktopToggler()
app.root.mainloop()
3. 制作一个启动程序
为了方便使用,可以通过一个启动程序决定虚拟桌面切换的左/右侧模式,以及启动主程序或打开配置程序,并加入了防多开功能
将打包后的主程序与配置程序放在同一目录下
import wx
import os
import sys
import subprocess
import psutil
class ConfigFrame(wx.Frame):
def __init__(self):
super().__init__(None, title='虚拟桌面切换器启动程序', size=(500, 220))
# 设置窗口图标
ico_path = os.path.join(os.path.dirname(__file__), 'pic', 'VDT.png') # 图标图片的位置
self.SetIcon(wx.Icon(ico_path, wx.BITMAP_TYPE_PNG))
# 窗口居中
self.Centre()
self.SetBackgroundColour(wx.WHITE)
panel = wx.Panel(self)
panel.SetBackgroundColour(wx.WHITE)
vbox = wx.BoxSizer(wx.VERTICAL)
# 提示文字
static_text = wx.StaticText(panel, label='请选择以何种模式启动(决定隐藏时悬浮球的位置)')
static_text.SetFont(wx.Font(14, wx.DEFAULT, wx.NORMAL, wx.NORMAL))
vbox.Add(static_text, 0, wx.ALL | wx.EXPAND, 15)
# 单选按钮组
self.radio1 = wx.RadioButton(panel, label='右测模式', style=wx.RB_GROUP)
self.radio1.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL))
self.radio2 = wx.RadioButton(panel, label='左测模式')
self.radio2.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL))
vbox.Add(self.radio1, 0, wx.LEFT | wx.BOTTOM, 15)
vbox.Add(self.radio2, 0, wx.LEFT | wx.BOTTOM, 15)
# 底部按钮
hbox = wx.BoxSizer(wx.HORIZONTAL)
self.next_btn = wx.Button(panel, label='下一步')
self.next_btn.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL))
cancel_btn = wx.Button(panel, label='取消')
cancel_btn.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL))
hbox.Add(self.next_btn, 0, wx.RIGHT, 10)
hbox.Add(cancel_btn, 0)
vbox.Add(hbox, 0, wx.ALIGN_CENTER | wx.ALL, 15)
# 事件绑定
cancel_btn.Bind(wx.EVT_BUTTON, self.on_cancel)
self.next_btn.Bind(wx.EVT_BUTTON, self.on_next)
panel.SetSizer(vbox)
def on_cancel(self, event):
self.Destroy()
def on_next(self, event):
# 获取选择模式
mode = 1 if self.radio1.GetValue() else 2
try:
data_path = os.path.join('data/data.txt')
with open(data_path, 'r+') as f:
lines = f.readlines()
lines[0] = f"{mode}\n"
f.seek(0)
f.writelines(lines)
f.truncate()
# 后续功能预留
self.Destroy()
NextFrame().Show()
except Exception as e:
wx.MessageBox(f'保存配置失败:{str(e)}', '错误', wx.OK | wx.ICON_ERROR)
class NextFrame(wx.Frame):
def __init__(self):
super().__init__(None, title='虚拟桌面切换器启动程序', size=(400, 160))
self.SetBackgroundColour(wx.WHITE)
ico_path = os.path.join(os.path.dirname(__file__), 'pic', 'VDT.png')
self.SetIcon(wx.Icon(ico_path, wx.BITMAP_TYPE_PNG))
self.Centre()
panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
# 显示选择模式
try:
with open(os.path.join('data','data.txt'), 'r') as f:
mode = '右侧' if f.readline().strip() == '1' else '左侧'
except Exception as e:
mode = '未知'
static_text = wx.StaticText(panel, label=f'是否以{mode}模式启动虚拟桌面切换器?')
static_text.SetFont(wx.Font(14, wx.DEFAULT, wx.NORMAL, wx.NORMAL))
static_text.SetBackgroundColour(wx.WHITE)
vbox.Add(static_text, 0, wx.ALL | wx.EXPAND, 15)
# 按钮组
hbox = wx.BoxSizer(wx.HORIZONTAL)
start_btn = wx.Button(panel, label='启动')
start_btn.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL))
setting_btn = wx.Button(panel, label='设置')
setting_btn.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL))
prev_btn = wx.Button(panel, label='上一步')
prev_btn.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL))
cancel_btn = wx.Button(panel, label='取消')
cancel_btn.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL))
hbox.Add(setting_btn, 0, wx.RIGHT, 10)
hbox.Add(prev_btn, 0, wx.RIGHT, 10)
hbox.Add(start_btn, 0, wx.RIGHT, 10)
hbox.Add(cancel_btn, 0)
vbox.Add(hbox, 0, wx.ALIGN_CENTER | wx.ALL, 15)
panel.SetSizer(vbox)
# 事件绑定
setting_btn.Bind(wx.EVT_BUTTON, self.on_setting)
prev_btn.Bind(wx.EVT_BUTTON, self.on_prev)
start_btn.Bind(wx.EVT_BUTTON, self.on_start)
cancel_btn.Bind(wx.EVT_BUTTON, self.on_cancel)
def kill_existing_process(self, process_names):
try:
for proc in psutil.process_iter(['name']):
if proc.info['name'] in process_names:
proc.terminate()
psutil.wait_procs([proc], timeout=5)
if proc.is_running():
proc.kill()
except Exception as e:
wx.MessageBox(f'进程终止失败:{str(e)}', '警告', wx.OK | wx.ICON_WARNING)
def on_setting(self, event):
try:
self.kill_existing_process(['主程序文件', '配置程序文件']) # 替换文件名
subprocess.Popen('配置程序文件') # 替换文件名
self.Destroy()
except Exception as e:
wx.MessageBox(f'启动配置程序失败:{str(e)}', '错误', wx.OK | wx.ICON_ERROR)
def on_start(self, event):
try:
self.kill_existing_process(['主程序文件', '配置程序文件']) # 替换文件名
subprocess.Popen('主程序文件') # 替换文件名
self.Destroy()
except Exception as e:
wx.MessageBox(f'启动主程序失败:{str(e)}', '错误', wx.OK | wx.ICON_ERROR)
def on_prev(self, event):
self.Destroy()
ConfigFrame().Show()
def on_cancel(self, event):
self.Destroy()
if __name__ == '__main__':
app = wx.App()
frame = ConfigFrame()
frame.Show()
app.MainLoop()
4. 配置文件示例
由于增加了第二个模式,需要增加新的配置数据
1
0.1500
0.8771
0.9396
0.9758
0.8760
0.7519
0.8005
0.6546
0.7032
0.1500
0.0180
0.0771
0.0000
0.0161
0.7519
0.8005
0.6542
0.7028