透明快捷键或命令管理器

透明快捷键或命令管理器

二次开发文件下载地址

核心模块分析 PyQt5框架选择与基础结构搭建

窗口类继承与初始化方法解析跨平台数据存储路径处理逻辑无边框窗口实现关键技术点

界面架构设计 复合式布局管理系统详解

QVBoxLayout/QHBoxLayout嵌套使用自定义标题栏实现方案动态布局管理器的内存优化

数据持久化方案 JSON格式数据处理流程

本地文件读写安全机制数据结构设计与版本兼容异常处理与数据恢复策略

交互功能实现 高级事件处理机制

鼠标拖动无边框窗口实现上下文菜单与动态编辑功能列表控件与树形控件的联动交互

视觉增强技术 现代化UI效果实现

亚克力背景效果算法动态模糊渲染技术跨平台字体适配方案

性能优化要点 内存管理最佳实践

对象生命周期控制延迟加载技术应用事件过滤器的合理使用

部署与分发 跨平台打包方案

PyInstaller配置技巧资源文件打包策略自动更新机制设计

扩展开发方向 插件系统架构设计

热加载模块实现快捷键映射导入导出云端同步功能拓展

每个技术点可展开为独立章节,包含:

原理示意图关键代码片段性能测试数据跨平台适配方案典型问题解决方案

方便在linux或者命令行等工具中使用
run.bat



@echo off  
cd /d "C:UsersHerryDesktop"
python shortcuts.py
pause

shortcuts.py
 



import sys
import platform
import json
import os  # 确保导入os模块
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 
                            QHBoxLayout, QLabel, QTreeWidget, QTreeWidgetItem,
                            QPushButton, QLineEdit, QMessageBox, QHeaderView,
                            QFrame, QInputDialog, QMenu, QAction, 
                            QFileDialog, QListWidget, QListWidgetItem, QStyle)
from PyQt5.QtCore import Qt, QPoint, QSize, QEvent, QTimer
from PyQt5.QtGui import (QFont, QColor, QIcon, QPixmap, QPainter, 
                         QBrush, QPalette, QFontMetrics, QTextOption)
 
class LocalSaveShortcutViewer(QMainWindow):
    def __init__(self):
        super().__init__()
        # 初始化属性
        self.current_software = "系统通用"
        self.categories_menu = None  # 分区下拉菜单
        self.tools_menu = None  # 工具栏下拉菜单
        self.data_file = self.get_data_file_path()  # 数据保存路径
        self.is_maximized = False  # 新增:跟踪窗口是否最大化
        self.normal_geometry = None  # 新增:保存窗口正常状态的几何信息
        self.initUI()
        self.load_software_shortcuts()  # 加载数据
        self.setWindowFlags(
            Qt.FramelessWindowHint |  # 无边框
            Qt.WindowStaysOnTopHint |  # 窗口置顶
            Qt.Tool  # 工具窗口样式
        )
        self.setAttribute(Qt.WA_TranslucentBackground)  # 透明背景
        self.set_glass_effect()
        
        # 状态变量
        self.dragging = False
        self.drag_position = QPoint()
        self.editing_item = None
        self.edit_widget = None
 
    def get_data_file_path(self):
        """获取数据保存文件的路径"""
        if getattr(sys, 'frozen', False):
            current_dir = os.path.dirname(sys.executable)
        else:
            current_dir = os.path.dirname(os.path.abspath(__file__))
        return os.path.join(current_dir, "shortcuts.json")
 
    def initUI(self):
        # 窗口基本设置
        self.setGeometry(300, 300, 700, 500)
        self.setMinimumSize(400, 300)
        self.setWindowTitle('快捷键查看器')
        
        # 主布局
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        self.main_layout = QVBoxLayout(central_widget)
        self.main_layout.setContentsMargins(5, 5, 5, 5)
        self.main_layout.setSpacing(2)
        
        # 标题栏
        title_bar = self.create_title_bar()
        self.main_layout.addWidget(title_bar)
        
        # 合并的分区和搜索栏(同一行)
        combined_bar = self.create_combined_bar()
        self.main_layout.addWidget(combined_bar)
        
        # 添加分区列表框(用于显示筛选结果)
        self.category_listbox = QListWidget()
        self.category_listbox.setStyleSheet("""
            QListWidget {
                background-color: rgba(255, 255, 255, 200);
                border: 1px solid rgba(200, 200, 200, 80);
                border-radius: 4px;
                font-family: "Microsoft YaHei", "Heiti TC";
                font-size: 9pt;
            }
            QListWidget::item {
                height: 25px;
                padding: 2px 5px;
            }
            QListWidget::item:selected {
                background-color: rgba(180, 220, 255, 80);
                color: #0055aa;
            }
        """)
        self.category_listbox.hide()  # 默认隐藏
        self.category_listbox.itemClicked.connect(self.on_category_selected)
        self.main_layout.addWidget(self.category_listbox)
        
        # 内容区域
        content_area = self.create_content_area()
        self.main_layout.addWidget(content_area, 1)
        
        # 底部信息栏
        footer = self.create_footer()
        self.main_layout.addWidget(footer)
 
    def create_title_bar(self):
        """创建带工具菜单和窗口控制的标题栏"""
        title_bar = QFrame()
        title_bar.setMinimumHeight(38)
        title_bar.setStyleSheet("""
            QFrame {
                background-color: rgba(255, 255, 255, 100);
                border-radius: 8px 8px 0px 0px;
            }
        """)
        
        layout = QHBoxLayout(title_bar)
        layout.setContentsMargins(15, 0, 10, 0)
        
        # 标题
        title_label = QLabel("命令速览")
        title_font = QFont()
        title_font.setFamily("Microsoft YaHei" if platform.system() == "Windows" else "Heiti TC")
        title_font.setPointSize(12)
        title_font.setBold(True)
        title_label.setFont(title_font)
        title_label.setStyleSheet("color: #333333;")
        layout.addWidget(title_label, 1, Qt.AlignLeft | Qt.AlignVCenter)
        
        # 工具菜单按钮(≡按钮)
        tool_menu_btn = QPushButton()
        tool_menu_btn.setFixedSize(28, 28)
        tool_menu_btn.setStyleSheet("""
            QPushButton {
                background-color: transparent;
                border: none;
            }
            QPushButton:hover {
                background-color: rgba(200, 200, 200, 80);
                border-radius: 4px;
            }
        """)
        tool_menu_btn.setIcon(self.create_icon("≡", "#666666", 12))
        tool_menu_btn.setIconSize(QSize(14, 14))
        tool_menu_btn.clicked.connect(self.show_tools_menu)
        layout.addWidget(tool_menu_btn)
        
        # 最小化按钮
        minimize_btn = QPushButton()
        minimize_btn.setFixedSize(28, 28)
        minimize_btn.setStyleSheet("""
            QPushButton {
                background-color: transparent;
                border: none;
            }
            QPushButton:hover {
                background-color: rgba(200, 200, 200, 80);
                border-radius: 4px;
            }
        """)
        minimize_btn.setIcon(self.create_icon("−", "#666666", 12))
        minimize_btn.setIconSize(QSize(14, 14))
        minimize_btn.clicked.connect(self.showMinimized)
        layout.addWidget(minimize_btn)
        
        # 最大化/还原按钮
        self.maximize_btn = QPushButton()
        self.maximize_btn.setFixedSize(28, 28)
        self.maximize_btn.setStyleSheet("""
            QPushButton {
                background-color: transparent;
                border: none;
            }
            QPushButton:hover {
                background-color: rgba(200, 200, 200, 80);
                border-radius: 4px;
            }
        """)
        self.maximize_btn.setIcon(self.create_icon("□", "#666666", 12))
        self.maximize_btn.setIconSize(QSize(14, 14))
        self.maximize_btn.clicked.connect(self.toggle_maximize)
        layout.addWidget(self.maximize_btn)
        
        # 关闭按钮
        close_btn = QPushButton()
        close_btn.setFixedSize(28, 28)
        close_btn.setStyleSheet("""
            QPushButton {
                background-color: transparent;
                border: none;
            }
            QPushButton:hover {
                background-color: #ff4d4d;
                border-radius: 14px;
            }
        """)
        close_btn.setIcon(self.create_icon("✕", "#666666", 12))
        close_btn.setIconSize(QSize(14, 14))
        close_btn.clicked.connect(self.force_close)  # 修改为强制关闭
        layout.addWidget(close_btn)
        
        # 拖动事件
        title_bar.mousePressEvent = self.on_title_bar_press
        title_bar.mouseMoveEvent = self.on_title_bar_move
        title_bar.mouseReleaseEvent = self.on_title_bar_release
        
        return title_bar
 
    def toggle_maximize(self):
        """切换窗口最大化/还原状态"""
        if self.is_maximized:
            # 还原窗口
            if self.normal_geometry:
                self.setGeometry(self.normal_geometry)
            self.is_maximized = False
            self.maximize_btn.setIcon(self.create_icon("□", "#666666", 12))
        else:
            # 最大化窗口
            self.normal_geometry = self.geometry()
            screen_geometry = QApplication.desktop().availableGeometry()
            # 调整最大化时的位置和大小,保留一些边距
            self.setGeometry(
                screen_geometry.x() + 20,
                screen_geometry.y() + 20,
                screen_geometry.width() - 40,
                screen_geometry.height() - 80
            )
            self.is_maximized = True
            self.maximize_btn.setIcon(self.create_icon("⧉", "#666666", 12))
 
    def create_combined_bar(self):
        """创建合并了当前分区和搜索框的工具栏(同一行)"""
        combined_frame = QFrame()
        combined_frame.setMinimumHeight(40)
        combined_frame.setStyleSheet("""
            QFrame {
                background-color: rgba(255, 255, 255, 80);
                border-top: 1px solid rgba(200, 200, 200, 30);
                border-bottom: 1px solid rgba(200, 200, 200, 30);
            }
        """)
        
        layout = QHBoxLayout(combined_frame)
        layout.setContentsMargins(10, 5, 10, 5)
        layout.setSpacing(10)  # 分区和搜索框之间的间距
        
        # 左侧:分区搜索输入框
        category_layout = QHBoxLayout()
        category_layout.setContentsMargins(0, 0, 0, 0)
        category_layout.setSpacing(5)
        
        # 分区搜索输入框
        self.category_input = QLineEdit()
        self.category_input.setPlaceholderText("输入分区关键词搜索...")
        self.category_input.setText(self.current_software)  # 初始显示当前分区
        self.category_input.setStyleSheet("""
            QLineEdit {
                background-color: rgba(255, 255, 255, 200);
                border: 1px solid rgba(200, 200, 200, 80);
                border-radius: 4px;
                padding: 3px 8px;
                font-family: "Microsoft YaHei", "Heiti TC";
                font-size: 9pt;
            }
            QLineEdit:focus {
                border-color: rgba(70, 130, 180, 150);
                outline: none;
            }
        """)
        # 绑定KeyRelease事件
        self.category_input.keyReleaseEvent = self.on_category_key_release
        category_layout.addWidget(self.category_input, 0, Qt.AlignVCenter)
        
        # 将分区部分添加到主布局
        layout.addLayout(category_layout, 0)
        
        # 右侧:搜索框,设置伸缩因子为1(拉伸填充剩余空间)
        search_layout = QHBoxLayout()
        search_layout.setContentsMargins(0, 0, 0, 0)
        search_layout.setSpacing(5)
        
        # 搜索框
        self.search_input = QLineEdit()
        self.search_input.setPlaceholderText("搜索快捷键或功能...")
        self.search_input.setStyleSheet("""
            QLineEdit {
                background-color: rgba(255, 255, 255, 200);
                border: 1px solid rgba(200, 200, 200, 80);
                border-radius: 4px;
                padding: 3px 8px;
                font-family: "Microsoft YaHei", "Heiti TC";
                font-size: 9pt;
            }
            QLineEdit:focus {
                border-color: rgba(70, 130, 180, 150);
                outline: none;
            }
        """)
        self.search_input.returnPressed.connect(self.perform_search)
        self.search_input.textChanged.connect(self.handle_search_change)
        search_layout.addWidget(self.search_input, 1)  # 搜索框拉伸
        
        # 搜索按钮
        search_btn = QPushButton()
        search_btn.setFixedSize(26, 26)
        search_btn.setStyleSheet("""
            QPushButton {
                background-color: transparent;
                border: none;
            }
            QPushButton:hover {
                background-color: rgba(200, 200, 200, 80);
                border-radius: 3px;
            }
        """)
        search_btn.setIcon(self.create_icon("🔍", "#666666", 12))
        search_btn.setIconSize(QSize(16, 16))
        search_btn.clicked.connect(self.perform_search)
        search_layout.addWidget(search_btn, 0)
        
        # 将搜索部分添加到主布局,设置伸缩因子为1(拉伸)
        layout.addLayout(search_layout, 1)
        
        return combined_frame
 
    def create_content_area(self):
        """创建内容区域"""
        content_frame = QFrame()
        content_frame.setStyleSheet("""
            QFrame {
                background-color: rgba(255, 255, 255, 80);
                border-radius: 0px 0px 8px 8px;
            }
        """)
        layout = QVBoxLayout(content_frame)
        layout.setContentsMargins(5, 5, 5, 5)
        
        # 创建树状视图
        self.shortcut_tree = QTreeWidget()
        self.shortcut_tree.setColumnCount(2)
        self.shortcut_tree.setHeaderLabels(["命令", "功能说明"])
        # 命令和功能说明列居中显示
        self.shortcut_tree.setStyleSheet("""
            QTreeWidget {
                background-color: transparent;
                border: 1px solid rgba(200, 200, 200, 50);
                border-radius: 4px;
                font-family: "Microsoft YaHei", "Heiti TC";
                font-size: 10pt;
                color: #333333;
                alternate-background-color: rgba(240, 240, 240, 60);
            }
            QTreeWidget::item {
                height: 28px;
                border-bottom: 1px solid rgba(200, 200, 200, 20);
            }
            QTreeWidget::item:selected {
                background-color: rgba(180, 220, 255, 80);
                color: #0055aa;
            }
            QHeaderView::section {
                background-color: rgba(245, 245, 245, 100);
                padding: 6px;
                border: 1px solid rgba(200, 200, 200, 50);
                font-family: "Microsoft YaHei", "Heiti TC";
                font-size: 9pt;
                font-weight: bold;
                color: #555555;
                text-align: center; /* 标题居中 */
            }
            QTreeWidget::item:!selected {
                text-align: center; /* 内容居中 */
            }
        """)
        
        # 设置列宽模式
        self.shortcut_tree.header().setSectionResizeMode(0, QHeaderView.Interactive)
        self.shortcut_tree.header().setSectionResizeMode(1, QHeaderView.Interactive)
        
        # 双击编辑
        self.shortcut_tree.itemDoubleClicked.connect(self.edit_item)
        
        # 内容变化时自动调整窗口大小
        self.shortcut_tree.itemChanged.connect(self.schedule_adjust_window_size)
        self.shortcut_tree.itemChanged.connect(self.adjust_columns_width)
        
        layout.addWidget(self.shortcut_tree)
        
        return content_frame
 
    def create_footer(self):
        """创建底部信息栏"""
        footer = QFrame()
        footer.setMinimumHeight(26)
        footer.setStyleSheet("""
            QFrame {
                background-color: rgba(255, 255, 255, 80);
                border-top: 1px solid rgba(200, 200, 200, 30);
            }
        """)
        
        layout = QHBoxLayout(footer)
        info_label = QLabel("双击编辑(Enter确认) | 按 Ctrl+Q 或 Esc 关闭 | 数据自动保存")
        info_font = QFont()
        info_font.setFamily("Microsoft YaHei" if platform.system() == "Windows" else "Heiti TC")
        info_font.setPointSize(8)
        info_label.setFont(info_font)
        info_label.setStyleSheet("color: #777777;")
        
        layout.addWidget(info_label, Qt.AlignCenter)
        layout.setContentsMargins(0, 0, 0, 0)
        
        return footer
 
    def create_icon(self, text, color, size):
        """创建文本图标"""
        pixmap = QPixmap(24, 24)
        pixmap.fill(Qt.transparent)
        painter = QPainter(pixmap)
        painter.setPen(QColor(color))
        painter.setFont(QFont("Arial", size, QFont.Bold))
        painter.drawText(pixmap.rect(), Qt.AlignCenter, text)
        painter.end()
        return QIcon(pixmap)
 
    def set_glass_effect(self):
        """设置玻璃效果"""
        if platform.system() == "Windows":
            try:
                import ctypes
                from ctypes import windll, byref
                from ctypes.wintypes import HWND, DWORD, BOOL
                
                hwnd = windll.user32.GetParent(int(self.winId()))
                ex_style = windll.user32.GetWindowLongW(hwnd, -20)
                ex_style |= 0x00080000  # WS_EX_LAYERED
                windll.user32.SetWindowLongW(hwnd, -20, ex_style)
                
                # 设置透明度
                windll.user32.SetLayeredWindowAttributes(hwnd, 0, 230, 0x00000002)
                
                # 定义DWM_BLURBEHIND结构
                class DWM_BLURBEHIND(ctypes.Structure):
                    _fields_ = [
                        ("dwFlags", DWORD),
                        ("fEnable", BOOL),
                        ("hRgnBlur", ctypes.c_void_p),
                        ("fTransitionOnMaximized", BOOL)
                    ]
                
                # 启用模糊效果
                blur = DWM_BLURBEHIND()
                blur.dwFlags = 1
                blur.fEnable = True
                blur.hRgnBlur = None
                windll.dwmapi.DwmEnableBlurBehindWindow(hwnd, byref(blur))
            except:
                pass  # 不支持Aero时忽略
        else:
            self.setStyleSheet("background-color: rgba(255, 255, 255, 180);")
 
    def get_default_shortcuts(self):
        """获取默认快捷键数据"""
        return {
            "系统通用": [
                ("Ctrl + C", "复制选中内容到剪贴板"),
                ("Alt + F4", "关闭当前应用程序"),
                ("Win + L", "锁定电脑屏幕")
            ],
            "Office": [
                ("Ctrl + B", "加粗选中文字"),
                ("Ctrl + ;", "在Excel中插入当前日期"),
                ("Ctrl + Shift + :", "在Excel中插入当前时间")
            ],
            "浏览器": [
                ("Ctrl + T", "打开新标签页"),
                ("F11", "切换全屏模式")
            ]
        }
 
    def load_software_shortcuts(self):
        """加载快捷键数据"""
        try:
            if os.path.exists(self.data_file):
                with open(self.data_file, 'r', encoding='utf-8') as f:
                    self.shortcuts_data = json.load(f)
                    self.show_shortcuts(self.current_software)
                    QTimer.singleShot(100, self.adjust_window_size)
                    return
        except Exception as e:
            print(f"加载保存的数据失败: {e}")
            QMessageBox.warning(self, "数据加载失败", f"加载保存的快捷键数据时出错:
{str(e)}
将使用默认数据。")
        
        self.shortcuts_data = self.get_default_shortcuts()
        self.show_shortcuts(self.current_software)
        QTimer.singleShot(100, self.adjust_window_size)
 
    def save_shortcuts_data(self):
        """保存快捷键数据到文件"""
        try:
            with open(self.data_file, 'w', encoding='utf-8') as f:
                json.dump(self.shortcuts_data, f, ensure_ascii=False, indent=2)
            return True
        except Exception as e:
            print(f"保存数据失败: {e}")
            QMessageBox.warning(self, "保存失败", f"保存快捷键数据时出错:
{str(e)}
请检查文件权限或目录是否可写。")
            return False
 
    def on_category_key_release(self, event):
        """分区搜索框按键释放事件处理"""
        # 先处理原始事件
        QLineEdit.keyReleaseEvent(self.category_input, event)
        # 调用更新列表函数
        self.update_listbox()
        # 输入变化时调整窗口大小
        self.schedule_adjust_window_size()
 
    def update_listbox(self):
        """更新分区列表框,显示匹配关键词的分区"""
        keyword = self.category_input.text().strip().lower()
        
        # 清空列表
        self.category_listbox.clear()
        
        if not keyword:
            # 如果关键词为空,隐藏列表框
            self.category_listbox.hide()
            return
        
        # 特殊处理:输入*时显示所有分区
        if keyword == '*':
            matched_categories = list(self.shortcuts_data.keys())
        else:
            # 遍历所有软件名,筛选包含所有关键词字符的名称
            matched_categories = []
            for category in self.shortcuts_data.keys():
                # 检查关键词中的每个字符是否都在分区名称中存在
                if all(char in category.lower() for char in keyword):
                    matched_categories.append(category)
        
        # 向列表框添加匹配的分区
        for category in matched_categories:
            item = QListWidgetItem(category)
            self.category_listbox.addItem(item)
        
        # 根据结果显示或隐藏列表框
        if matched_categories:
            self.category_listbox.show()
            # 调整列表框高度
            self.category_listbox.setFixedHeight(min(len(matched_categories) * 25 + 4, 200))
        else:
            self.category_listbox.hide()
 
    def on_category_selected(self, item):
        """处理列表框中分区的选择"""
        selected_category = item.text()
        self.current_software = selected_category
        self.category_input.setText(selected_category)
        self.show_shortcuts(selected_category, self.search_input.text())
        self.category_listbox.hide()  # 选择后隐藏列表框
        # 选择后调整窗口大小
        self.schedule_adjust_window_size()
 
    def show_tools_menu(self):
        """显示工具下拉菜单(优化UI)"""
        self.tools_menu = QMenu()
        self.tools_menu.setMinimumWidth(160)
        self.tools_menu.setStyleSheet("""
            QMenu {
                background-color: rgba(255, 255, 255, 220);
                border: 1px solid rgba(200, 200, 200, 100);
                border-radius: 4px;
                font-family: "Microsoft YaHei", "Heiti TC";
                font-size: 9pt;
            }
            QMenu::item {
                height: 28px;
                padding: 2px 10px;
            }
            QMenu::item:selected {
                background-color: rgba(180, 220, 255, 80);
                color: #0055aa;
            }
            QMenu::separator {
                height: 1px;
                background-color: rgba(200, 200, 200, 50);
                margin: 2px 0;
            }
        """)
        
        # 添加快捷键
        add_shortcut_action = QAction(self.style().standardIcon(QStyle.SP_FileDialogNewFolder), "添加快捷键", self)
        add_shortcut_action.triggered.connect(self.add_shortcut)
        self.tools_menu.addAction(add_shortcut_action)
        
        # 删除选中项
        delete_shortcut_action = QAction(self.style().standardIcon(QStyle.SP_TrashIcon), "删除选中项", self)
        delete_shortcut_action.triggered.connect(self.delete_shortcut)
        self.tools_menu.addAction(delete_shortcut_action)
        
        self.tools_menu.addSeparator()
        
        # 导入数据
        import_action = QAction(self.style().standardIcon(QStyle.SP_FileDialogDetailedView), "导入数据", self)
        import_action.triggered.connect(self.import_data)
        self.tools_menu.addAction(import_action)
        
        # 导出数据
        export_action = QAction(self.style().standardIcon(QStyle.SP_FileDialogInfoView), "导出数据", self)
        export_action.triggered.connect(self.export_data)
        self.tools_menu.addAction(export_action)
        
        # 恢复默认数据
        reset_action = QAction(self.style().standardIcon(QStyle.SP_BrowserReload), "恢复默认数据", self)
        reset_action.triggered.connect(self.restore_default_data)
        self.tools_menu.addAction(reset_action)
        
        self.tools_menu.addSeparator()
        
        # 添加新分区
        add_category_action = QAction(self.style().standardIcon(QStyle.SP_FileDialogNewFolder), "添加新分区", self)
        add_category_action.triggered.connect(self.add_new_category)
        self.tools_menu.addAction(add_category_action)
        
        # 删除本分区
        delete_category_action = QAction(self.style().standardIcon(QStyle.SP_TrashIcon), "删除本分区", self)
        delete_category_action.triggered.connect(self.delete_current_category)
        if self.current_software == "系统通用":
            delete_category_action.setEnabled(False)
            delete_category_action.setToolTip("默认分区不能删除")
        self.tools_menu.addAction(delete_category_action)
        
        self.tools_menu.addSeparator()
        
        # 帮助中心
        help_action = QAction(self.style().standardIcon(QStyle.SP_MessageBoxQuestion), "帮助中心", self)
        help_action.triggered.connect(self.show_help)
        self.tools_menu.addAction(help_action)
        
        # 关于作者
        about_action = QAction(self.style().standardIcon(QStyle.SP_FileDialogInfoView), "关于作者", self)
        about_action.triggered.connect(self.show_about)
        self.tools_menu.addAction(about_action)
        
        # 显示菜单
        btn = self.main_layout.itemAt(0).widget().layout().itemAt(1).widget()
        self.tools_menu.exec_(btn.mapToGlobal(QPoint(0, btn.height())))
 
    def show_help(self):
        """优化帮助中心弹窗"""
        help_text = """
        <h3>命令速览工具使用帮助</h3>
        <p><strong>基本功能:</strong></p>
        <ul>
            <li>管理不同分区的快捷键信息</li>
            <li>快速搜索和查看各类快捷键或命令</li>
        </ul>
        <p><strong>分区操作:</strong></p>
        <ul>
            <li>在顶部左侧输入框可搜索分区</li>
            <li>输入*可显示所有分区</li>
            <li>可添加新分区或删除现有分区(系统通用分区不可删除)</li>
        </ul>
        <p><strong>快捷键管理:</strong></p>
        <ul>
            <li>双击列表项可编辑快捷键或说明</li>
            <li>选中项后按Delete键可删除</li>
            <li>使用工具菜单可批量导入导出数据</li>
        </ul>
        <p><strong>快捷键:</strong></p>
        <ul>
            <li>Ctrl+F: 聚焦到快捷键搜索框</li>
            <li>Ctrl+E: 聚焦到分区搜索框</li>
            <li>Ctrl+T: 打开工具菜单</li>
            <li>Ctrl+Q/Esc: 关闭窗口</li>
            <li>Enter: 确认编辑</li>
        </ul>
        """
        QMessageBox.information(self, "帮助中心", help_text, QMessageBox.Ok, QMessageBox.Ok)
 
    def show_about(self):
        """优化关于作者弹窗"""
        about_text = """
        <h3>命令速览工具 v1.0</h3>
        <p>这是一个简单实用的快捷键管理工具,<br>可以帮助您整理和快速查询各类软件的快捷键。</p>
        <p><strong>作者:</strong>鹓于<br><strong>日期:</strong>2025年10月<br><strong>联系方式:</strong>Herryplank@outlook.com</p>
        <p>本工具为免费软件,欢迎使用和分享。</p>
        """
        QMessageBox.about(self, "关于作者", about_text)
 
    def delete_current_category(self):
        """删除当前分区"""
        if self.current_software == "系统通用":
            QMessageBox.information(self, "提示", "系统通用分区为默认分区,不能删除!")
            return
            
        reply = QMessageBox.question(
            self, "确认删除", 
            f"确定要删除分区 '{self.current_software}' 及其所有快捷键吗?
此操作不可恢复!",
            QMessageBox.Yes | QMessageBox.No, QMessageBox.No
        )
        
        if reply == QMessageBox.Yes:
            if self.current_software in self.shortcuts_data:
                del self.shortcuts_data[self.current_software]
            
            self.current_software = "系统通用"
            self.category_input.setText(self.current_software)
            self.show_shortcuts(self.current_software)
            self.categories_menu = None
            self.save_shortcuts_data()
            # 删除后调整窗口大小
            self.schedule_adjust_window_size()
 
    def show_shortcuts(self, software, filter_text=None):
        """显示指定软件的快捷键"""
        self.shortcut_tree.clear()
        shortcuts = self.shortcuts_data.get(software, [])
        
        if filter_text and filter_text.strip():
            filter_text = filter_text.lower()
            shortcuts = [
                (sc, desc) for sc, desc in shortcuts 
                if filter_text in sc.lower() or filter_text in desc.lower()
            ]
        
        for shortcut, desc in shortcuts:
            item = QTreeWidgetItem([shortcut, desc])
            self.shortcut_tree.addTopLevelItem(item)
        
        if not shortcuts:
            item = QTreeWidgetItem(["", "没有找到匹配的快捷键"])
            item.setForeground(0, QColor("#999999"))
            item.setForeground(1, QColor("#999999"))
            self.shortcut_tree.addTopLevelItem(item)
            
        # 显示后立即调整列宽和窗口大小
        self.adjust_columns_width()
        self.schedule_adjust_window_size()
 
    def adjust_columns_width(self):
        """根据内容调整列宽"""
        # 为每一列计算最大内容宽度
        column_widths = [0, 0]  # 分别存储"命令"列和"功能说明"列的最大宽度
        
        # 计算标题宽度
        header = self.shortcut_tree.headerItem()
        for col in range(2):
            header_width = self.fontMetrics().width(header.text(col)) + 20  # 加边距
            if header_width > column_widths[col]:
                column_widths[col] = header_width
        
        # 计算每个项的宽度
        for i in range(self.shortcut_tree.topLevelItemCount()):
            item = self.shortcut_tree.topLevelItem(i)
            for col in range(2):
                item_width = self.fontMetrics().width(item.text(col)) + 20  # 加边距
                if item_width > column_widths[col]:
                    column_widths[col] = item_width
        
        # 设置列宽
        self.shortcut_tree.setColumnWidth(0, column_widths[0])
        self.shortcut_tree.setColumnWidth(1, column_widths[1])
 
    def switch_software(self, software):
        """切换显示的软件快捷键"""
        self.current_software = software
        self.category_input.setText(software)
        self.show_shortcuts(software, self.search_input.text())
        # 切换后调整窗口大小
        self.schedule_adjust_window_size()
 
    def perform_search(self):
        """执行搜索"""
        search_text = self.search_input.text()
        self.show_shortcuts(self.current_software, search_text)
        # 搜索后调整窗口大小
        self.schedule_adjust_window_size()
 
    def handle_search_change(self):
        """处理搜索文本变化"""
        self.perform_search()
 
    def add_new_category(self):
        """添加新的软件分区"""
        category_name, ok = QInputDialog.getText(
            self, "添加新分区", "请输入新分区名称:"
        )
        
        if ok and category_name and category_name.strip():
            category_name = category_name.strip()
            
            if category_name in self.shortcuts_data:
                QMessageBox.information(self, "提示", f"分区 '{category_name}' 已存在!")
                return
                
            self.shortcuts_data[category_name] = []
            self.current_software = category_name
            self.category_input.setText(category_name)
            self.show_shortcuts(category_name)
            self.categories_menu = None
            self.save_shortcuts_data()
            # 添加后调整窗口大小
            self.schedule_adjust_window_size()
 
    def edit_item(self, item, column):
        """编辑选中项"""
        if self.edit_widget:
            self.finish_editing(commit=False)
            
        self.editing_item = item
        self.editing_column = column
        
        self.edit_widget = QLineEdit(item.text(column))
        self.edit_widget.setStyleSheet("""
            QLineEdit {
                background-color: rgba(255, 255, 255, 230);
                border: 1px solid #888;
                padding: 3px;
                font-family: "Microsoft YaHei", "Heiti TC";
                font-size: 10pt;
                text-align: center; /* 编辑框居中 */
            }
        """)
        
        self.shortcut_tree.setItemWidget(item, column, self.edit_widget)
        self.edit_widget.setFocus()
        self.edit_widget.selectAll()
        
        # 编辑时实时调整宽度
        self.edit_widget.textChanged.connect(self.handle_edit_text_change)
        self.edit_widget.returnPressed.connect(lambda: self.finish_editing(commit=True))
        self.edit_widget.editingFinished.connect(lambda: self.finish_editing(commit=True))
        self.edit_widget.installEventFilter(self)
 
    def handle_edit_text_change(self):
        """编辑文本变化时实时调整宽度"""
        if self.edit_widget and self.editing_item:
            # 临时更新项目文本以计算宽度
            temp_text = self.edit_widget.text()
            # 计算临时宽度
            temp_width = self.fontMetrics().width(temp_text) + 20  # 加边距
            
            # 如果当前编辑的列需要更宽,则临时调整
            current_col_width = self.shortcut_tree.columnWidth(self.editing_column)
            if temp_width > current_col_width:
                self.shortcut_tree.setColumnWidth(self.editing_column, temp_width)
                self.schedule_adjust_window_size()
 
    def finish_editing(self, commit=True):
        """完成编辑"""
        if not self.edit_widget or not self.editing_item:
            return
            
        if commit:
            old_text = self.editing_item.text(self.editing_column)
            new_text = self.edit_widget.text()
            self.editing_item.setText(self.editing_column, new_text)
            
            if self.current_software in self.shortcuts_data:
                for i, (sc, desc) in enumerate(self.shortcuts_data[self.current_software]):
                    if self.editing_column == 0 and sc == old_text:
                        self.shortcuts_data[self.current_software][i] = (new_text, desc)
                        break
                    elif self.editing_column == 1 and desc == old_text:
                        self.shortcuts_data[self.current_software][i] = (sc, new_text)
                        break
            
            self.save_shortcuts_data()
        
        self.shortcut_tree.setItemWidget(self.editing_item, self.editing_column, None)
        # 完成编辑后重新计算所有列宽
        self.adjust_columns_width()
        self.edit_widget = None
        self.editing_item = None
        self.editing_column = -1
        # 完成编辑后调整窗口大小
        self.schedule_adjust_window_size()
 
    def eventFilter(self, obj, event):
        """事件过滤"""
        if obj == self.edit_widget and event.type() == QEvent.MouseButtonPress:
            if not self.edit_widget.geometry().contains(event.pos()):
                self.finish_editing(commit=True)
                return True
        return super().eventFilter(obj, event)
 
    def add_shortcut(self):
        """添加新快捷键"""
        if not self.current_software:
            return
            
        new_item = QTreeWidgetItem(["新快捷键", "功能说明"])
        self.shortcut_tree.addTopLevelItem(new_item)
        self.shortcut_tree.scrollToItem(new_item)
        self.edit_item(new_item, 0)
        
        self.shortcuts_data[self.current_software].append(("新快捷键", "功能说明"))
        self.save_shortcuts_data()
        # 添加后调整窗口大小
        self.schedule_adjust_window_size()
 
    def delete_shortcut(self):
        """删除选中项"""
        selected_items = self.shortcut_tree.selectedItems()
        if not selected_items:
            QMessageBox.information(self, "提示", "请先选中要删除的项")
            return
            
        reply = QMessageBox.question(
            self, "确认删除", 
            f"确定要删除选中的 {len(selected_items)} 项吗?",
            QMessageBox.Yes | QMessageBox.No, QMessageBox.No
        )
        
        if reply == QMessageBox.Yes:
            if self.current_software in self.shortcuts_data:
                to_delete = []
                for item in selected_items:
                    sc = item.text(0)
                    desc = item.text(1)
                    to_delete.append((sc, desc))
                
                self.shortcuts_data[self.current_software] = [
                    (sc, desc) for sc, desc in self.shortcuts_data[self.current_software]
                    if (sc, desc) not in to_delete
                ]
                
                for item in selected_items:
                    index = self.shortcut_tree.indexOfTopLevelItem(item)
                    self.shortcut_tree.takeTopLevelItem(index)
            
            self.save_shortcuts_data()
            # 删除后重新计算列宽和窗口大小
            self.adjust_columns_width()
            self.schedule_adjust_window_size()
 
    def schedule_adjust_window_size(self):
        """延迟调整窗口大小 - 避免频繁调整"""
        # 当窗口最大化时不调整大小
        if not self.is_maximized:
            QTimer.singleShot(50, self.adjust_window_size)
 
    def adjust_window_size(self):
        """根据内容自适应调整窗口大小(重点优化宽度)"""
        # 计算内容区域总宽度(两列宽度之和)
        content_width = self.shortcut_tree.columnWidth(0) + self.shortcut_tree.columnWidth(1)
        
        # 计算标题栏所需宽度
        title_widget = self.main_layout.itemAt(0).widget().layout().itemAt(0).widget()
        title_width = self.fontMetrics().width(title_widget.text()) + 100  # 加上按钮和边距
        
        # 计算分区搜索结果的宽度
        listbox_width = 0
        if self.category_listbox.isVisible():
            for i in range(self.category_listbox.count()):
                item = self.category_listbox.item(i)
                item_width = self.fontMetrics().width(item.text()) + 50  # 加上边距
                if item_width > listbox_width:
                    listbox_width = item_width
        
        # 确定最终宽度(取最大值,并确保不小于最小值)
        final_width = max(content_width + 40, title_width, listbox_width, self.minimumWidth())
        
        # 计算所需高度
        content_height = 0
        for i in range(self.main_layout.count()):
            item = self.main_layout.itemAt(i)
            widget = item.widget()
            if widget and widget.isVisible():
                content_height += widget.sizeHint().height()
        
        # 调整树状视图高度
        item_count = self.shortcut_tree.topLevelItemCount()
        tree_height = min(item_count * 28 + 40, 500)
        content_height += (tree_height - self.shortcut_tree.sizeHint().height())
        
        # 确定最终高度(确保不小于最小值)
        final_height = max(content_height, self.minimumHeight())
        
        # 应用计算出的尺寸
        self.resize(final_width, final_height)
 
    # 窗口拖动相关
    def on_title_bar_press(self, event):
        if event.button() == Qt.LeftButton:
            self.dragging = True
            self.drag_position = event.globalPos() - self.frameGeometry().topLeft()
            event.accept()
 
    def on_title_bar_move(self, event):
        if self.dragging and event.buttons() & Qt.LeftButton and not self.is_maximized:
            self.move(event.globalPos() - self.drag_position)
            event.accept()
 
    def on_title_bar_release(self, event):
        self.dragging = False
 
    # 数据导入导出功能
    def import_data(self):
        """导入数据"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "导入数据", "", "JSON Files (*.json);;All Files (*)"
        )
        
        if not file_path:
            return
            
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                imported_data = json.load(f)
            
            # 验证导入的数据格式
            if not isinstance(imported_data, dict):
                raise ValueError("导入的数据格式不正确,应为字典类型")
                
            for key, value in imported_data.items():
                if not isinstance(key, str) or not isinstance(value, list):
                    raise ValueError(f"分区 '{key}' 的数据格式不正确")
                
                for item in value:
                    if not isinstance(item, list) or len(item) != 2:
                        raise ValueError(f"分区 '{key}' 中的快捷键格式不正确")
        
            # 询问是否合并或替换现有数据
            reply = QMessageBox.question(
                self, "数据合并选项",
                "请选择导入方式:
"
                "• 合并: 将导入的数据添加到现有数据中
"
                "• 替换: 用导入的数据完全替换现有数据",
                QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes
            )
            
            if reply == QMessageBox.No:  # 替换
                self.shortcuts_data = imported_data
            else:  # 合并
                for key, value in imported_data.items():
                    if key in self.shortcuts_data:
                        # 合并时去重
                        existing_set = set(tuple(item) for item in self.shortcuts_data[key])
                        for item in value:
                            item_tuple = tuple(item)
                            if item_tuple not in existing_set:
                                self.shortcuts_data[key].append(item)
                                existing_set.add(item_tuple)
                    else:
                        self.shortcuts_data[key] = value
            
            # 保存并刷新界面
            self.save_shortcuts_data()
            self.show_shortcuts(self.current_software)
            QMessageBox.information(self, "导入成功", f"成功导入数据,共包含 {len(imported_data)} 个分区")
            
        except Exception as e:
            QMessageBox.warning(self, "导入失败", f"导入数据时出错:
{str(e)}")
 
    def export_data(self):
        """导出数据"""
        # 获取当前分区的名称作为默认文件名
        default_filename = f"{self.current_software}_shortcuts.json"
        
        file_path, _ = QFileDialog.getSaveFileName(
            self, "导出数据", default_filename, "JSON Files (*.json);;All Files (*)"
        )
        
        if not file_path:
            return
            
        # 确保文件有.json扩展名
        if not file_path.endswith('.json'):
            file_path += '.json'
        
        try:
            # 询问导出范围
            reply = QMessageBox.question(
                self, "导出范围",
                "请选择导出范围:
"
                "• 当前分区: 只导出当前显示的分区数据
"
                "• 所有数据: 导出所有分区的完整数据",
                QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes
            )
            
            if reply == QMessageBox.Yes:  # 只导出当前分区
                export_data = {self.current_software: self.shortcuts_data.get(self.current_software, [])}
                export_scope = f"当前分区 '{self.current_software}'"
            else:  # 导出所有数据
                export_data = self.shortcuts_data
                export_scope = f"所有 {len(self.shortcuts_data)} 个分区"
            
            with open(file_path, 'w', encoding='utf-8') as f:
                json.dump(export_data, f, ensure_ascii=False, indent=2)
            
            QMessageBox.information(self, "导出成功", f"已成功导出 {export_scope} 的数据到:
{file_path}")
            
        except Exception as e:
            QMessageBox.warning(self, "导出失败", f"导出数据时出错:
{str(e)}")
 
    def restore_default_data(self):
        """恢复默认数据"""
        reply = QMessageBox.question(
            self, "确认恢复默认数据",
            "确定要恢复默认数据吗?
"
            "此操作将清除所有自定义的快捷键和分区,恢复为初始状态!",
            QMessageBox.Yes | QMessageBox.No, QMessageBox.No
        )
        
        if reply == QMessageBox.Yes:
            try:
                # 备份当前数据
                if os.path.exists(self.data_file):
                    backup_path = f"{self.data_file}.bak"
                    with open(self.data_file, 'r', encoding='utf-8') as f:
                        current_data = f.read()
                    with open(backup_path, 'w', encoding='utf-8') as f:
                        f.write(current_data)
                
                # 恢复默认数据
                self.shortcuts_data = self.get_default_shortcuts()
                self.current_software = "系统通用"
                self.category_input.setText(self.current_software)
                self.save_shortcuts_data()
                self.show_shortcuts(self.current_software)
                
                QMessageBox.information(
                    self, "恢复成功", 
                    "已成功恢复默认数据。
"
                    f"原有数据已备份至: {self.data_file}.bak" if os.path.exists(f"{self.data_file}.bak") else ""
                )
                
            except Exception as e:
                QMessageBox.warning(self, "恢复失败", f"恢复默认数据时出错:
{str(e)}")
 
    # 新增:强制关闭函数
    def force_close(self):
        """强制关闭整个应用程序,包括CMD窗口"""
        if self.edit_widget:
            self.finish_editing(commit=True)
        os._exit(0)  # 强制终止所有进程
        
    # 键盘事件
    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            if self.edit_widget:
                self.finish_editing(commit=False)
            else:
                self.force_close()  # 修改为强制关闭
        elif event.modifiers() & Qt.ControlModifier and event.key() == Qt.Key_Q:
            self.force_close()  # 修改为强制关闭
        elif event.key() == Qt.Key_Delete:
            self.delete_shortcut()
        elif event.modifiers() & Qt.ControlModifier and event.key() == Qt.Key_F:
            self.search_input.setFocus()
        elif event.modifiers() & Qt.ControlModifier and event.key() == Qt.Key_E:
            self.category_input.setFocus()
        elif event.modifiers() & Qt.ControlModifier and event.key() == Qt.Key_T:
            self.show_tools_menu()
 
    def closeEvent(self, event):
        """窗口关闭时确保数据已保存并强制终止进程"""
        if self.edit_widget:
            self.finish_editing(commit=True)
        event.accept()
        os._exit(0)  # 强制终止所有进程
 
if __name__ == '__main__':
    font = QFont("Microsoft YaHei")
    
    app = QApplication(sys.argv)
    app.setFont(font)
    app.setStyle("Fusion")
    
    window = LocalSaveShortcutViewer()
    window.show()
    
    sys.exit(app.exec_())

shortcuts.json



{
  "系统通用": [
    [
      "Ctrl + C",
      "复制选中内容到剪贴板"
    ],
    [
      "Ctrl + V",
      "粘贴剪贴板内容"
    ],
    [
      "Ctrl + X",
      "剪切选中内容"
    ],
    [
      "Ctrl + Z",
      "撤销上一步操作"
    ],
    [
      "Ctrl + Y",
      "重做被撤销的操作"
    ],
    [
      "Ctrl + S",
      "保存当前文件"
    ],
    [
      "Ctrl + A",
      "全选"
    ],
    [
      "Ctrl + O",
      "打开文件对话框"
    ],
    [
      "Ctrl + N",
      "新建文件/窗口"
    ],
    [
      "Ctrl + F",
      "打开查找对话框"
    ],
    [
      "Ctrl + P",
      "打印"
    ],
    [
      "Win + D",
      "显示桌面"
    ],
    [
      "Win + L",
      "锁定电脑"
    ],
    [
      "Win + E",
      "打开资源管理器"
    ],
    [
      "Alt + F4",
      "关闭当前应用程序"
    ],
    [
      "Alt + Tab",
      "切换应用程序"
    ]
  ],
  "Office": [
    [
      "Ctrl + B",
      "加粗选中文字"
    ],
    [
      "Ctrl + I",
      "斜体选中文字"
    ],
    [
      "Ctrl + U",
      "下划线选中文字"
    ],
    [
      "Ctrl + [",
      "减小字体大小"
    ],
    [
      "Ctrl + ]",
      "增大字体大小"
    ],
    [
      "Ctrl + E",
      "段落居中对齐"
    ],
    [
      "Ctrl + L",
      "段落左对齐"
    ],
    [
      "Ctrl + R",
      "段落右对齐"
    ],
    [
      "Ctrl + J",
      "段落两端对齐"
    ],
    [
      "Ctrl + K",
      "插入超链接"
    ],
    [
      "Ctrl + Enter",
      "在Word中插入分页符"
    ],
    [
      "Ctrl + ;",
      "在Excel中插入当前日期"
    ],
    [
      "Ctrl + Shift + :",
      "在Excel中插入当前时间"
    ],
    [
      "Alt + Enter",
      "在Excel单元格内换行"
    ],
    [
      "F4",
      "在Excel中重复上一次操作"
    ]
  ]
}

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...