[problem help] 抛砖引玉,请大佬帮忙修复一个桌面管理程序的Bug
Tofloor
poster avatar
dxm6006
deepin
2025-04-12 11:01
Author

代码.txt

找百度的AI写了一个python桌面管理程序,希望实现以下基本功能,现在程序已经写好,也基本能用,但还存在明显的Bug和需要改进的地方,

1.程序启动以后自动最小化到托盘
2.程序启动以后自动在当前用户根目录下检查是否有 .DeskManager 文件夹,如果没有就自动创建。
3.检查.DeskManager 文件夹下面是否有 Config.ini文件,如果没有就自动创建
4.Config.ini里面记录了./DeskManager文件夹下其他文件夹存放在桌面的坐标、透明度、窗口大小等配置信息
5.读取 Config.ini 文件和./DeskManager 文件夹下面的其他文件夹,如果ini文件里已有该文件夹的配置信息,就按配置信息显示在桌面,如果没有配置信息就把该文件夹按400×400像素的窗口大小显示在屏幕中心,并将其更新到ini文件中
6.在桌面上的文件夹窗口以资源管理器的为基本功能,但是屏蔽掉其按钮、菜单、布局,只显示抬头
7.窗口居中显示文件夹的名称,点击右键(在窗体抬头上的鼠标左、右键点击功能都需要重新定义,不要跟系统默认的左、右键功能冲突)可以选择重命名文件夹,设置透明度,锁定\解锁。解锁状态下可以左键拖动位置,也可以点击窗体边缘改变窗体大小,以上操作都实时更新到ini文件中。
7.用户可以把文件、文件夹、快捷方式等资源从资源管理器拖到程序生成的窗体,该操作视为移动文件或文件夹
8.用户点击程序窗体里的资源,需要像在可以拖动并将这些文件夹以资源管理器的方式显示在桌面
9.在托盘点击右键,可以选择创建新文件夹和退出程序,并实现按钮相关功能

我不懂编程,所以就把它放在论坛中,哪位大佬有空帮忙修复一下,如果能直接投递到应用商店就更好了。

以下是完整代码:

import sys
import os
import shutil
import configparser
from PyQt5.QtWidgets import (QApplication, QSystemTrayIcon, QMenu, QWidget, QVBoxLayout,
QTreeView, QFileSystemModel, QInputDialog, QMessageBox, QSizeGrip,
QLabel, QAction, QFileDialog)
from PyQt5.QtGui import QIcon, QCursor, QMouseEvent, QDrag, QKeySequence, QDesktopServices
from PyQt5.QtCore import (Qt, QPoint, QSettings, QSize, QTimer, QMimeData, QCoreApplication,
QUrl, QDir, QMargins)

class ConfigManager:
def init(self):
self.home = os.path.expanduser("~")
self.base_dir = os.path.join(self.home, ".DeskManager")
self.config_path = os.path.join(self.base_dir, "Config.ini")

    self._init_directory()
    self.config = configparser.ConfigParser()
    self.config.read(self.config_path)

def _init_directory(self):
    if not os.path.exists(self.base_dir):
        os.makedirs(self.base_dir)
    if not os.path.exists(self.config_path):
        with open(self.config_path, 'w') as f:
            pass

def get_config(self, folder):
    if self.config.has_section(folder):
        return dict(self.config[folder])
    return None

def update_config(self, folder, data):
    if not self.config.has_section(folder):
        self.config.add_section(folder)
    for key, value in data.items():
        self.config.set(folder, key, str(value))
    with open(self.config_path, 'w') as f:
        self.config.write(f)

class TitleBar(QLabel):
def init(self, parent=None):
super().init(parent)
self.parent = parent
self.setFixedHeight(30)
self.setStyleSheet("""
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #616161, stop:1 #333333);
color: white;
border-radius: 4px;
padding-left: 10px;
qproperty-alignment: 'AlignCenter';
""")
self.setAlignment(Qt.AlignCenter)

def mousePressEvent(self, event):
    self.parent.handleTitleBarEvent(event)

def mouseDoubleClickEvent(self, event):
    self.parent.rename_folder()

class FolderWindow(QWidget):
def init(self, folder_path, config_manager):
super().init()
self.folder_path = folder_path
self.config_manager = config_manager
self.folder_name = os.path.basename(folder_path)
self.is_locked = False
self.resize_margin = 8
self.setup_ui()
self.load_config()

    self.setAcceptDrops(True)
    self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnBottomHint)
    self.setAttribute(Qt.WA_TranslucentBackground)

def setup_ui(self):
    self.main_layout = QVBoxLayout(self)
    self.main_layout.setContentsMargins(2, 2, 2, 2)
    self.main_layout.setSpacing(0)
  
    self.title_bar = TitleBar(self)
    self.title_bar.setText(self.folder_name)
  
    self.content_widget = QWidget()
    self.content_layout = QVBoxLayout(self.content_widget)
    self.content_layout.setContentsMargins(0, 0, 0, 0)
  
    self.model = QFileSystemModel()
    self.model.setRootPath(self.folder_path)
    self.model.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs | QDir.Files)
  
    self.tree = QTreeView()
    self.tree.setModel(self.model)
    self.tree.setRootIndex(self.model.index(self.folder_path))
    self.tree.setHeaderHidden(True)
    self.tree.hideColumn(1)
    self.tree.hideColumn(2)
    self.tree.hideColumn(3)
    self.tree.setIndentation(10)
    self.tree.setStyleSheet("""
        QTreeView {
            background: #f0f0f0;
            border: 1px solid #cccccc;
            border-radius: 3px;
        }
    """)
    self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
    self.tree.customContextMenuRequested.connect(self.show_context_menu)
    self.tree.doubleClicked.connect(self.open_item)
  
    self.content_layout.addWidget(self.tree)
    self.main_layout.addWidget(self.title_bar)
    self.main_layout.addWidget(self.content_widget)
  
    self.size_grip = QSizeGrip(self)
    self.size_grip.setStyleSheet("width: 16px; height: 16px;")

def open_item(self, index):
    path = self.model.filePath(index)
    if os.path.isfile(path):
        QDesktopServices.openUrl(QUrl.fromLocalFile(path))
    elif os.path.isdir(path):
        os.startfile(path)

def load_config(self):
    config = self.config_manager.get_config(self.folder_name)
    if config:
        self.resize(int(config['width']), int(config['height']))
        self.move(int(config['x']), int(config['y']))
        self.setWindowOpacity(float(config['alpha']))
        self.is_locked = config.get('locked', 'False') == 'True'
        self.size_grip.setVisible(not self.is_locked)
    else:
        screen = QApplication.primaryScreen().geometry()
        self.resize(400, 400)
        x = (screen.width() - 400) // 2
        y = (screen.height() - 400) // 2
        self.move(x, y)
        self.update_config()

def update_config(self):
    data = {
        'x': self.x(),
        'y': self.y(),
        'width': self.width(),
        'height': self.height(),
        'alpha': self.windowOpacity(),
        'locked': str(self.is_locked)
    }
    self.config_manager.update_config(self.folder_name, data)

def handleTitleBarEvent(self, event):
    if event.button() == Qt.LeftButton and not self.is_locked:
        if event.type() == QMouseEvent.MouseButtonDblClick:
            self.rename_folder()
        else:
            self.__startDrag(event.globalPos())
    elif event.button() == Qt.RightButton:
        self.show_context_menu(event.globalPos())

def __startDrag(self, global_pos):
    self.drag_start_position = global_pos - self.frameGeometry().topLeft()

def mouseMoveEvent(self, event):
    if not self.is_locked and hasattr(self, 'drag_start_position'):
        self.move(event.globalPos() - self.drag_start_position)
        self.update_config()

def mouseReleaseEvent(self, event):
    if hasattr(self, 'drag_start_position'):
        del self.drag_start_position

def show_context_menu(self, pos):
    menu = QMenu()
    rename_action = menu.addAction("📝 重命名")
    opacity_menu = menu.addMenu("🎨 透明度")
    lock_action = menu.addAction("🔒 锁定" if not self.is_locked else "🔓 解锁")
  
    # 透明度设置
    for value in [0.3, 0.5, 0.7, 1.0]:
        opacity_action = opacity_menu.addAction(f"{int(value*100)}%")
        opacity_action.triggered.connect(
            lambda checked, v=value: self.set_opacity(v))
  
    # 功能连接
    rename_action.triggered.connect(self.rename_folder)
    lock_action.triggered.connect(self.toggle_lock)
  
    # 文件右键菜单
    if isinstance(pos, QPoint):  # æ ‡é¢˜æ å³é”®
        menu.exec_(pos)
    else:  # 文件列表右键
        global_pos = self.tree.viewport().mapToGlobal(pos)
        menu.exec_(global_pos)

def set_opacity(self, value):
    self.setWindowOpacity(value)
    self.update_config()

def toggle_lock(self):
    self.is_locked = not self.is_locked
    self.size_grip.setVisible(not self.is_locked)
    self.update_config()

def rename_folder(self):
    new_name, ok = QInputDialog.getText(
        self, "重命名", "输入新名称:", text=self.folder_name)
    if ok and new_name:
        new_path = os.path.join(os.path.dirname(self.folder_path), new_name)
        try:
            os.rename(self.folder_path, new_path)
            self.folder_path = new_path
            self.folder_name = new_name
            self.title_bar.setText(new_name)
            self.config_manager.config.remove_section(self.folder_name)
            self.update_config()
        except Exception as e:
            QMessageBox.warning(self, "错误", str(e))

def dragEnterEvent(self, event):
    if event.mimeData().hasUrls():
        event.acceptProposedAction()

def dropEvent(self, event):
    for url in event.mimeData().urls():
        src_path = url.toLocalFile()
        dst_path = os.path.join(self.folder_path, os.path.basename(src_path))
        try:
            if os.path.isdir(src_path):
                shutil.move(src_path, dst_path)
            else:
                shutil.move(src_path, dst_path)
        except Exception as e:
            QMessageBox.warning(self, "移动失败", str(e))
    self.model.refresh()

class TrayIcon(QSystemTrayIcon):
def init(self, config_manager):
super().init()
self.config_manager = config_manager
self.setIcon(QIcon.fromTheme("folder"))
self.setup_menu()
self.windows = []

    self.activated.connect(self.on_tray_activate)
    QTimer.singleShot(0, self.init_folders)

def setup_menu(self):
    menu = QMenu()
    create_action = menu.addAction("新建文件夹")
    exit_action = menu.addAction("退出")
    create_action.triggered.connect(self.create_folder)
    exit_action.triggered.connect(QCoreApplication.quit)
    self.setContextMenu(menu)

def init_folders(self):
    base_dir = self.config_manager.base_dir
    for folder in os.listdir(base_dir):
        folder_path = os.path.join(base_dir, folder)
        if os.path.isdir(folder_path) and not folder.startswith('.'):
            self.create_window(folder_path)

def create_window(self, folder_path):
    window = FolderWindow(folder_path, self.config_manager)
    window.show()
    self.windows.append(window)

def on_tray_activate(self, reason):
    if reason == QSystemTrayIcon.DoubleClick:
        self.create_folder()

def create_folder(self):
    folder_name, ok = QInputDialog.getText(
        None, "新建文件夹", "输入文件夹名称:")
    if ok and folder_name:
        folder_path = os.path.join(self.config_manager.base_dir, folder_name)
        try:
            os.makedirs(folder_path, exist_ok=True)
            self.create_window(folder_path)
        except Exception as e:
            QMessageBox.warning(None, "创建失败", str(e))

if name == "main":
app = QApplication(sys.argv)
config = ConfigManager()
tray = TrayIcon(config)
tray.show()
sys.exit(app.exec_())

Reply Favorite View the author
All Replies
‌量子巡游者
deepin
Resources Team Moderator
2025-04-12 11:12
#1

能说下没啥开发这个工具不?

Reply View the author
dxm6006
deepin
2025-04-12 11:24
#2

因为日常用20.9办公,桌面上各种文件很多,排序也很乱,以前用Windows就有用这一类桌面分类管理的程序,所以就找AI写了一个,我估计日常办公的人对于这类程序还是有需求的。

Reply View the author
把一切操作变成GUI
deepin
Backbone of ecological co-construction group
2025-04-12 12:45
#3

你把这个帖子的问题发到AI里面问不就解决了吗?


还有你贴出的代码有乱码,请改为UTF-8编码

Reply View the author
‌量子巡游者
deepin
Resources Team Moderator
2025-04-12 15:19
#4
dxm6006

因为日常用20.9办公,桌面上各种文件很多,排序也很乱,以前用Windows就有用这一类桌面分类管理的程序,所以就找AI写了一个,我估计日常办公的人对于这类程序还是有需求的。

代码潜在问题检查

1. 路径处理问题

Python
# ConfigManager 中 base_dir 的创建 if not os.path.exists(self.base_dir): os.makedirs(self.base_dir) # 应添加 exist_ok=True 参数更安全

建议 改为:

Python
os.makedirs(self.base_dir, exist_ok=True)

2. 编码问题

Python
rename_action = menu.addAction("📝 重命名") # 明显编码错误

原因:表情符号未正确转码 修复:应使用 Unicode 字符或直接使用文字描述:

Python
rename_action = menu.addAction("📝 重命名")

3. 文件覆盖问题

Python
# dropEvent 中移动文件未检查是否已存在 shutil.move(src_path, dst_path) # 如果目标存在会抛出异常

建议 添加存在性检查:

Python
if os.path.exists(dst_path): # 处理覆盖或重命名逻辑

4. 内存泄漏风险

Python
self.windows.append(window) # 窗口关闭后未从列表中移除

建议 添加窗口销毁时的清理:

Python
window.destroyed.connect(lambda: self.windows.remove(window))

5. 跨平台兼容性

Python
os.startfile(path) # 仅限 Windows 系统

建议 使用跨平台方法:

Python
QDesktopServices.openUrl(QUrl.fromLocalFile(path))
Reply View the author
‌量子巡游者
deepin
Resources Team Moderator
2025-04-12 18:06
#5

将你的工具弄好了 ,这是开源仓库

Reply View the author
dxm6006
deepin
2025-04-14 11:59
#6

感谢大佬的支持,谢谢

Reply View the author
dxm6006
deepin
2025-04-14 12:07
#7
‌量子巡游者

将你的工具弄好了 ,这是开源仓库

斑竹,你给的仓库里deb下载地址,我点了下不了,wget也找不到文件
我自己也整了一个版本,在原基础上增加了以下功能,你看看能否跟你的整合到一起,然后上线到应用商店
1.单独修改标题栏颜色
2.可以对窗体里的文件命名和删除
3.删除整个文件夹时,会自动把文件移到桌面

image.png

Reply View the author
‌量子巡游者
deepin
Resources Team Moderator
2025-04-14 12:18
#8
dxm6006

斑竹,你给的仓库里deb下载地址,我点了下不了,wget也找不到文件
我自己也整了一个版本,在原基础上增加了以下功能,你看看能否跟你的整合到一起,然后上线到应用商店
1.单独修改标题栏颜色
2.可以对窗体里的文件命名和删除
3.删除整个文件夹时,会自动把文件移到桌面

image.png

你把你的直接放到码云这类代码仓库里就行了,然后附上下载地址

Reply View the author
dxm6006
deepin
2025-04-14 13:45
#9
‌量子巡游者

你把你的直接放到码云这类代码仓库里就行了,然后附上下载地址

不会用仓库,也没有注册,我直接附件给你,
dm.txt

Reply View the author
‌量子巡游者
deepin
Resources Team Moderator
2025-04-14 14:10
#10
dxm6006

不会用仓库,也没有注册,我直接附件给你,
dm.txt

我看你都运行了,打好的包也发我下吧

Reply View the author
dxm6006
deepin
2025-04-14 14:56
#11
‌量子巡游者

我看你都运行了,打好的包也发我下吧

我用的就是这份单独代码,然后自己写了个桌面desktop文件启动,不会打包

Reply View the author
‌量子巡游者
deepin
Resources Team Moderator
2025-04-14 17:38
#12
dxm6006

我用的就是这份单独代码,然后自己写了个桌面desktop文件启动,不会打包

好的,我这边弄好放到我的仓库里面

Reply View the author