【代码分享】对虚拟桌面切换器的更新

#在上一篇博客中,利用Python制作了一个简易的虚拟桌面切换按钮工具,本片博客对其进行更新了,增加了按钮隐藏时停留左/右两侧的模式选择,并制作了独立的配置工具,不再依赖插入指定U盘切换模式#

目录

一. 代码示例

1. 更新后的虚拟桌面切换器代码

2. 配置程序的代码

3. 制作一个启动程序

4. 配置文件示例

二. 功能展示


一. 代码示例

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

二. 功能展示

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值