吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 798|回复: 11
上一主题 下一主题
收起左侧

[Python 原创] Windows 剪贴板增强工具(支持置顶 / 搜索 / 统计 / 贴边隐藏)

  [复制链接]
跳转到指定楼层
楼主
oO梦中梦Oo 发表于 2026-4-14 16:13 回帖奖励
本帖最后由 oO梦中梦Oo 于 2026-4-14 16:15 编辑

Windows 剪贴板工具

主要功能
剪贴板历史自动记录
复制过的内容会自动保存下来,不需要手动操作。

点击即用
点一下就会自动变成当前可粘贴内容,比系统剪贴板快很多。

使用次数统计
每条内容都会记录使用次数,常用的东西一眼就能看出来。

置顶
重要内容可以置顶,永远排在最上面。

搜索
内容多了以后可以直接搜关键词,不用翻。

排序
支持按“使用次数 / 时间”排序。

快捷键
ALT+Q呼出/隐藏
----
软件截图:

----
代码部分:
[Python] 纯文本查看 复制代码
import sys, json, os
import pyperclip
from PyQt5 import QtWidgets, QtCore, QtGui
from pynput import keyboard

DATA_FILE = "clipboard_data.json"


# =========================
# 数据结构
# =========================
class ClipboardItem:
    def __init__(self, text, count=0, pinned=False, timestamp=None):
        self.text = text
        self.count = count
        self.pinned = pinned
        self.timestamp = timestamp or QtCore.QDateTime.currentDateTime().toString()


# =========================
# 主程序
# =========================
class ClipboardManager(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()

        # ===== 无边框 + 置顶 + 透明 =====
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)

        self.resize(420, 320)

        # ===== 状态 =====
        self.dragPos = None
        self.hidden_to_edge = False
        self.edge_side = None

        # =========================
        # UI 主容器
        # =========================
        self.main = QtWidgets.QFrame(self)

        self.layout = QtWidgets.QVBoxLayout(self.main)
        self.layout.setContentsMargins(6, 6, 6, 6)
        self.layout.setSpacing(6)

        # =========================
        # 顶部栏
        # =========================
        top = QtWidgets.QHBoxLayout()

        self.search = QtWidgets.QLineEdit()
        self.search.setPlaceholderText("搜索")

        self.sort_box = QtWidgets.QComboBox()
        self.sort_box.addItems(["次数", "时间"])

        # 🎨 皮肤
        self.skin_box = QtWidgets.QComboBox()
        self.skin_box.addItems(["light", "dark", "blue"])
        self.skin_box.currentTextChanged.connect(self.apply_skin)

        close_btn = QtWidgets.QPushButton("✕")
        close_btn.setFixedSize(24, 24)
        close_btn.clicked.connect(self.close)

        top.addWidget(self.search)
        top.addWidget(self.sort_box)
        top.addWidget(self.skin_box)
        top.addWidget(close_btn)

        self.layout.addLayout(top)

        # =========================
        # 表格
        # =========================
        self.table = QtWidgets.QTableWidget(0, 2)
        self.table.setHorizontalHeaderLabels(["内容", "次"])
        self.table.setFrameShape(QtWidgets.QFrame.NoFrame)
        self.table.verticalHeader().setVisible(False)

        self.table.setColumnWidth(0, 300)
        self.table.setColumnWidth(1, 50)

        self.layout.addWidget(self.table)

        # 外层布局
        wrapper = QtWidgets.QVBoxLayout(self)
        wrapper.addWidget(self.main)

        # =========================
        # 数据
        # =========================
        self.data = []
        self.current_text = ""
        self.last_clipboard = ""

        self.load_data()

        # =========================
        # 定时监听剪贴板
        # =========================
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.check_clipboard)
        self.timer.start(600)

        # =========================
        # UI事件
        # =========================
        self.search.textChanged.connect(self.refresh_table)
        self.sort_box.currentIndexChanged.connect(self.refresh_table)
        self.table.cellClicked.connect(self.select_item)

        self.table.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.table.customContextMenuRequested.connect(self.menu)

        # =========================
        # Alt + Q 全局快捷键
        # =========================
        self.hotkey = keyboard.GlobalHotKeys({
            '<alt>+q': self.toggle_window
        })
        self.hotkey.start()

        # =========================
        # 贴边检测
        # =========================
        self.edge_timer = QtCore.QTimer()
        self.edge_timer.timeout.connect(self.edge_check)
        self.edge_timer.start(300)

        # 默认皮肤
        self.apply_skin("light")

    # =========================================================
    # &#127912; 换肤系统
    # =========================================================
    def apply_skin(self, skin):
        if skin == "light":
            bg = "rgba(255,255,255,190)"
            text = "#222"
        elif skin == "dark":
            bg = "rgba(35,35,35,210)"
            text = "#eee"
        else:
            bg = "rgba(180,210,255,180)"
            text = "#111"

        self.main.setStyleSheet(f"""
            QFrame {{
                background-color: {bg};
                border-radius: 10px;
            }}
        """)

        self.table.setStyleSheet(f"""
            QTableWidget {{
                background: transparent;
                border: none;
                color: {text};
                gridline-color: rgba(0,0,0,40);
            }}
            QHeaderView::section {{
                background: transparent;
                border: none;
                color: {text};
            }}
            QTableWidget::item {{
                border: none;
                padding: 4px;
            }}
            QTableWidget::item:hover {{
                background: rgba(0,0,0,20);
            }}
        """)

    # =========================================================
    # &#129522; Alt+Q 显示/隐藏
    # =========================================================
    def toggle_window(self):
        if self.isVisible():
            self.hide()
        else:
            self.show()
            self.raise_()

    # =========================================================
    # &#129522; 贴边隐藏
    # =========================================================
    def edge_check(self):
        if not self.isVisible():
            return

        geo = self.geometry()
        screen = QtWidgets.QApplication.primaryScreen().geometry()

        # 左边
        if geo.x() <= 0:
            self.edge_side = "left"
            self.move(-self.width() + 5, geo.y())
            self.hidden_to_edge = True

        # 右边
        elif geo.right() >= screen.width() - 1:
            self.edge_side = "right"
            self.move(screen.width() - 5, geo.y())
            self.hidden_to_edge = True

        # 鼠标唤出
        pos = QtGui.QCursor.pos()

        if self.hidden_to_edge:
            if self.edge_side == "left" and pos.x() <= 20:
                self.move(0, self.y())
                self.hidden_to_edge = False

            if self.edge_side == "right" and pos.x() >= screen.width() - 20:
                self.move(screen.width() - self.width(), self.y())
                self.hidden_to_edge = False

    # =========================================================
    # &#128433;&#65039; &#11088;关键:拖动窗口(已修复)
    # =========================================================
    def mousePressEvent(self, event):
        self.dragPos = event.globalPos()

    def mouseMoveEvent(self, event):
        if self.dragPos:
            self.move(self.pos() + event.globalPos() - self.dragPos)
            self.dragPos = event.globalPos()

    # =========================================================
    # &#128203; 剪贴板
    # =========================================================
    def check_clipboard(self):
        text = pyperclip.paste()
        if not text:
            return

        # 统计
        if text != self.last_clipboard and text == self.current_text:
            for i in self.data:
                if i.text == text:
                    i.count += 1
                    break

        self.last_clipboard = text

        # 新增
        if not any(i.text == text for i in self.data):
            self.data.append(ClipboardItem(text))
            self.save_data()

        self.refresh_table()

    # =========================================================
    # 点击复制
    # =========================================================
    def select_item(self, row, col):
        data = self.get_data()
        self.current_text = data[row].text
        pyperclip.copy(self.current_text)

    # =========================================================
    # 排序逻辑(置顶永远优先)
    # =========================================================
    def get_data(self):
        keyword = self.search.text().lower()
        data = [i for i in self.data if keyword in i.text.lower()]

        pinned = [x for x in data if x.pinned]
        normal = [x for x in data if not x.pinned]

        if self.sort_box.currentIndex() == 0:
            normal.sort(key=lambda x: -x.count)
        else:
            normal.sort(key=lambda x: x.timestamp, reverse=True)

        return pinned + normal

    # =========================================================
    # 刷新表格
    # =========================================================
    def refresh_table(self):
        data = self.get_data()
        self.table.setRowCount(len(data))

        for i, item in enumerate(data):
            self.table.setItem(i, 0, QtWidgets.QTableWidgetItem(item.text))
            self.table.setItem(i, 1, QtWidgets.QTableWidgetItem(str(item.count)))

    # =========================================================
    # 右键菜单
    # =========================================================
    def menu(self, pos):
        row = self.table.currentRow()
        if row < 0:
            return

        data = self.get_data()
        item = data[row]

        menu = QtWidgets.QMenu()
        pin = menu.addAction("置顶/取消")
        delete = menu.addAction("删除")

        action = menu.exec_(self.table.viewport().mapToGlobal(pos))

        if action == pin:
            item.pinned = not item.pinned
        elif action == delete:
            self.data.remove(item)

        self.save_data()
        self.refresh_table()

    # =========================================================
    # 数据保存
    # =========================================================
    def save_data(self):
        with open(DATA_FILE, "w", encoding="utf-8") as f:
            json.dump([i.__dict__ for i in self.data], f, ensure_ascii=False)

    def load_data(self):
        if not os.path.exists(DATA_FILE):
            return
        with open(DATA_FILE, "r", encoding="utf-8") as f:
            self.data = [ClipboardItem(**i) for i in json.load(f)]


# =========================
# 启动
# =========================
if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = ClipboardManager()
    w.show()
    sys.exit(app.exec_())



免费评分

参与人数 4吾爱币 +8 热心值 +4 收起 理由
苏紫方璇 + 5 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
wym201898 + 1 + 1 热心回复!
chshxp + 1 + 1 热心回复!
shuangluo + 1 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
helh0275 发表于 2026-4-14 19:23
linglong2013 发表于 2026-4-14 18:05
比win+x 相比有优势吗?

不应该是win+V吗?
推荐
大森林 发表于 2026-4-14 16:44
沙发
nonfree 发表于 2026-4-14 16:29
3#
penz 发表于 2026-4-14 16:32
提高效率,试一试
5#
 楼主| oO梦中梦Oo 发表于 2026-4-14 16:55 |楼主
nonfree 发表于 2026-4-14 16:29
怎么下载 怎么用?

需要自己打包
6#
尚三宝 发表于 2026-4-14 17:55
问一下:打包成什么后缀的文件名才可用  
7#
linglong2013 发表于 2026-4-14 18:05
比win+x 相比有优势吗?
9#
phiix52 发表于 2026-4-14 20:52
太有用了
10#
ningbai 发表于 2026-4-14 21:01
感谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-4-15 03:07

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表