标签化档案管理系统
文件建立日期: 2020/02/28
最后修订日期: None
相关软件信息:
Win 10 | Python 3.8.2 | PySimpleGUI 4.16.0 | sqlite3 2.6.0 |
说明: 本文请随意引用或更改, 只须标示出处及作者, 作者不保证内容絶对正确无误, 如造成任何后果, 请自行负责.
标题: 标签化档案管理系统
一直以来, 在计算机中的档案大量的增加, 每当想找到某一个最近很少用到的档案, 总很难想到放在哪一个目录下, 也没找到合适自己使用的软件, 所以自己写了一个, 重点是给自己用.
设计要求
- 标签的管理
- 新增标签 (同名的标签在不同分类下视为不同)
- 删除标签 (已有档案归类在该标签下, 不得删除)
- 标签改名
- 标签排序 (同层级之间的排序)
- 标签搜索
- 为方便处理, 以字典转字符串储存与读取
- 档案管理
- 新增选择条件标签, 最多五笔
- 删除选择条件标签 (先删除最后加入的标签)
- 档案删除, 改名, 复制, 移动等 (目前不提供)
- 双击档案开启
- 显示档案分类标签
- 档案全部放在
D:\FILE
下, 按附属檔名分放在附属檔名子目录下 - 多选档案加入系统后, 再移动档案
- 数据库管理
- 两个表
tag_table
及file_table
tag_table
只有一个column:key TEXT
, 字符串化的标签树字典file_table
有六个columns:filename
key1
,key2
,key3
,key4
,key5
- key1 ~ key5 代表分类标签代码
- 两个表
- 更新要求
- 标签更名, 必须更新选择条件标签, 以及档案分类标签
- 选择条件标签更动, 必须更新合乎条件的档案列表
输出画面
代码及说明
- 使用的库
import os
import shutil
from pathlib import Path
import PySimpleGUI as sg
import sqlite3
import ctypes
- 档案处理类
- 建立主目录
D:\FILE
, 以及各个子目录D:\FILE\附属檔名
(没有则为No_Extension
) - 档案移动
- 建立主目录
class FILE():
# 目录建立及档案移动
def __init__(self):
self.file_directory = Path('D:/FILE')
self.no_extension = 'No_Extension'
if not self.file_directory.is_dir():
self.file_directory.mkdir()
# 按附属文件名建立子目录
def Create_Directory_By_File_Extension(self, path_object):
suffix = path_object.suffix
relative_path = self.no_extension if suffix=='' else suffix[1:].upper()
directory = self.file_directory.joinpath(relative_path)
if not directory.is_dir():
directory.mkdir()
return directory
# 文件名转换成絶对路径
def Get_Path(self, text):
path = Path(text)
suffix = path.suffix
sub_dir = self.no_extension if suffix == '' else suffix[1:].upper()
result = self.file_directory.joinpath(sub_dir, path.name)
return result
# 档案移动
def Move_File(self, file_object_from, file_object_to):
if not file_object_from.is_file() or file_object_to.is_file():
return False
shutil.move(file_object_from, file_object_to)
return True
- 提供所有GUI接口的处理
- 窗口的生成
- 控件的动作及更新
- 事件的处理
class GUI():
def __init__(self):
self.title = '档案管理系统'
self.font = ('微软雅黑', 12)
self.button_size = ( 8, 1)
self.select = ['', '', '', '', '']
self.index = 0
# 按键的键值及显示的文字
button00 = [['NEW TAG', '新建标签'],
['ROOT TAG', '选主标签'],
['DELETE', '删除标签']]
button01 = [['RENAME', '标签改名'],
['SEARCH', '搜索标签'],
['NEXT ONE', '找下一个']]
button02 = [['SORTING' , '标签排序']]
button10 = [['INSERT', '档案移入'],
['REMOVE', '标签移除']]
# 选择标签及档案分类标签
self.text0 = ['S1', 'S2', 'S3', 'S4', 'S5']
self.text1 = ['T1', 'T2', 'T3', 'T4', 'T5']
# 标签树及档案树的事件绑定
self.binds = [['TREE0', '<Button-1>', '_CLICK'],
['TREE0', '<Double-Button-1>', '_DOUBLE'],
['TREE1', '<Button-1>', '_CLICK'],
['TREE1', '<Double-Button-1>', '_DOUBLE']]
# 左右两个框及其内容
layout0 = self.Frame([self.Buttons(button00),
self.Buttons(button01), self.Buttons(button02),
[T0]])
layout1 = self.Frame([self.Buttons(button10),
self.Texts(self.text0, p=(7, 7)), [T1],
self.Texts(self.text1, p=(7, 7))])
ctypes.windll.user32.SetProcessDPIAware()
# 窗口生成
self.window = sg.Window(self.title, layout=[[layout0, layout1]],
margins=(2, 2), finalize=True)
# 隐藏树的标题栏
self.Hide_Header()
# 加载数据库已建立的标签, 并更新标签树
T0.Load_Tree(S.Load_Tag_Table())
# 事件绑定
self.Binds()
# INSERT按钮停用
self.Disable_Insert()
def _clear_frame1(self):
# 清除选项, 并更新档案树
self.index = 0
self.select = ['', '', '', '', '']
self._frame1_update()
def _frame1_update(self):
# 更新框架1中的标签选项, 档案分类标签, 以及档案树, 按钮'INSERT'
self._text0_update()
self._tree1_update()
self._text1_update()
self.Disable_Insert()
def _is_select_not_ok(self):
# 标签树中的树根不可加入选择标签中, 重复也不接受
return True if T0.Where() in ['']+self.select else False
def _text0_append(self):
# 新增标签到五个选择标签中
if self.index == 5:
self.select = self.select[1:5]+['']
self.index = self.index + 1 if self.index < 5 else 5
self.select[self.index-1]=T0.Where()
return
def _text0_clear(self):
# 清除选择标签
self.index = 0
self.select = ['', '', '', '', '']
def _text0_update(self):
# 更新选择标签的显示内容
for i in range(5):
self.window[self.text0[i]].Update(
value='' if self.select[i]=='' else
T0.treedata.tree_dict[self.select[i]].text)
def _text1_update(self):
# 更新档案分类标签的显示内容
value = T1.treedata.tree_dict[T1.Where()].values
if value == []: value = ['', '', '', '', '']
for i in range(5):
text = '' if value[i]=='' else T0.treedata.tree_dict[value[i]].text
self.window[self.text1[i]].Update(value=text)
def _tree1_update(self):
# 按选择标签条件, 更新档案树的显示内容
T1.Tree_Update(S.Load_File_Table())
def Binds(self):
# 绑定事件
for bind in self.binds: self.window[bind[0]].bind(bind[1], bind[2])
def Disable_Insert(self):
# 停用或启用INSERT按钮
disabled = True if self.index == 0 else False
self.window['INSERT'].Update(disabled=disabled)
def Buttons(self, buttons):
# 多按钮生成, INSERT按钮为多档案选取
result = []
for i, button in enumerate(buttons):
pad = (0, (2, 0)) if i == 0 else ((2, 0), (2, 0))
if button[0] == 'INSERT':
result.append(sg.FilesBrowse(button[1], target=button[0],
size=self.button_size, key=button[0], font=self.font, pad=pad,
file_types=(('All files', '*.*'),), enable_events=True))
else:
result.append(sg.Button(button[1], auto_size_button=False,
font=self.font, enable_events=True, size=self.button_size,
key=button[0], pad=pad))
return result
def Frame(self, layout):
# 框架生成
return sg.Frame('', layout=layout, pad=(0, 0))
def Hide_Header(self):
# 隐藏标签树及档案树的标题栏
T0.Widget.configure(show='tree')
T1.Widget.configure(show='tree')
def Insert(self):
# 选择标签的加入, 并更新档案树
if self._is_select_not_ok(): return
self._text0_append()
self._frame1_update()
def Pop(self, text):
# 通知文字弹框
sg.PopupOK(text, font=self.font, no_titlebar=True)
def Popup(self, text):
# 输入文字弹框
text = sg.popup_get_text(message=text, font=self.font, size=(40,1),
default_text='', no_titlebar=True, keep_on_top=True)
return None if text == None or text.strip()=='' else text.strip()
def Remove(self):
# 移除最后一项的选择标签
if self.index == 0:
return
self.select[self.index-1] = ''
self.index -= 1
self._frame1_update()
self.Disable_Insert()
def Texts(self, texts, p, size=(20, 1)):
# 文字框生成
result = []
for i, text in enumerate(texts):
pad = (0, p) if i == 0 else ((9, 0), p)
result.append(sg.Text('', size=size, font=self.font,
justification='center', auto_size_text=False, key=text,
text_color='white', background_color='green', pad=pad))
return result
- 数据库处理
- 连接数据库 conn = sqlite3.connect(数据库文件名), c = conn.cursor()
- 建立表格 CREATE TABLE IF NOT EXISTS 表格名 (字段1 格式, 字段2 格式, …, 字段N 格式)
- 插入数据 INSERT INTO表格名 (字段1, 字段2, …, 字段N) VALUES (值1, 值2, .., 值N)
- 删除数据 DELETE FROM 表格名 WHERE 条件
- 查询数据 SELECT * FROM 表格名
- 查询结果 c.fetchone(); c.fetchall()
- 条件查询 SELECT 字段 FROM 表格名 WHERE 条件
- 命令送出及更新
- c.execute(命令, 相关参数)
- conn.commit()
class SQL():
def __init__(self):
# 建立与数据库的连接, 并设置表格及其字段
self.database = 'D:/FILE/file.db'
self.file_table = 'file_table'
self.file_cols = ('filename', 'key1', 'key2', 'key3', 'key4', 'key5')
self.tag_table = 'tag_table'
self.tag_cols = ('key',)
self.conn = sqlite3.connect(self.database)
self.c = self.conn.cursor()
self.Create_File_Table()
self.Create_Tag_Table()
def Commit(self, command):
# 发出命令并更新
self.c.execute(command)
self.conn.commit()
def Create_File_Table(self):
# 建立档案表格
command = ('CREATE TABLE IF NOT EXISTS file_table '
'(filename TEXT, key1 TEXT, key2 TEXT, key3 TEXT, '
'key4 TEXT, key5 TEXT)')
self.Commit(command)
def Create_Tag_Table(self):
# 建立标签表格
command = 'CREATE TABLE IF NOT EXISTS tag_table (key TEXT)'
self.Commit(command)
def Insert_File_Table(self, values):
# 新增一笔档案记录
command = ('INSERT INTO file_table ' +
'(filename, key1, key2, key3, key4, key5) VALUES ' +
repr(tuple(values)))
self.Commit(command)
def Load_File_Table(self):
# 搜索符合选择标签的档案记录, 多条件交互查询
if G.index == 0: return []
t1 = "'), ('".join(G.select[:G.index])
command = """
with list(col) as (VALUES ('""" +t1+"""')), cte as ( select rowid,
',' || key1 || ',' || key2 || ',' || key3 || ',' || key4 || ',' ||
key5 || ',' col from file_table ) select * from file_table where
rowid in ( select c.rowid from cte c inner join list l on c.col
like '%,' || l.col || ',%' group by c.rowid having count(*) =
(select count(*) from list) )"""
self.Commit(command)
return self.c.fetchall()
def Load_Tag_Table(self):
# 读取标签表格, 始终只有一笔, 包含所有的标签, 文字字典
command = ' '.join(('SELECT * from', self.tag_table))
self.Commit(command)
data = self.c.fetchone()
return None if data == None else eval(data[0])
def Records_In_File_Table(self, key):
# 检查标签是否有档案使用该标签, 传回有使用该标签的档案记录
command = ("SELECT * FROM file_table WHERE '" + key +
"' IN (key1, key2, key3, key4, key5)")
self.Commit(command)
return self.c.fetchall()
def Save_To_Tag_Table(self, dictionary):
# 删除原记录, 再加入新的标签记录
self.Commit(' '.join(('DELETE FROM', self.tag_table, 'WHERE ROWID=1')))
command = ' '.join(('INSERT INTO', self.tag_table, '(',
self.tag_cols[0], ')', 'VALUES', '(', repr(str(dictionary)), ')'))
self.Commit(command)
- 标签树 TREE0
- 新增, 删除, 搜索, 排序, 更新
- 树的数据结构
- treedata.tree_dict {'key':Node}
- Node.parent 父Node的key
- Node.children 子Node的列表
- Node.text Node的文字
- Node.values Node的columns值
- Node.icon Node的图示
class Tree0(sg.Tree):
def __init__(self, key):
# 生成标签数据结构treedata及标签树tree
self.font = ('微软雅黑', 12)
self.key = key
self.search = []
self.index = 0
self.text = None
self.treedata = sg.TreeData()
super().__init__(data=self.treedata, col0_width=26, font=self.font,
num_rows=20, row_height=30, background_color='white',
show_expanded=False, justification='left', key=self.key,
visible_column_map=[False,], auto_size_columns=False,
headings=['Nothing',], enable_events=True,
select_mode=sg.TABLE_SELECT_MODE_BROWSE, pad=(0, 0))
def _delete_tag(self, key):
# 删除标签以及以下所有的子标签
# 从父标签中的子标签列表移除, 删除标签数据结构中该标签, 删除该标签对象
# 再删除其所有的子标签
node = self.treedata.tree_dict[key]
self.treedata.tree_dict[node.parent].children.remove(node)
node_list = [node]
while node_list != []:
temp = []
for item in node_list:
temp += item.children
del self.treedata.tree_dict[item.key]
del item
node_list = temp
def _is_root(self):
# 选择的是不是标签的树根
return True if self.Where()=='' else False
def _records(self, key):
# 检查有多少档案用到该标签
tag_list = [key] + self.All_Tags(key)
records = 0
for tag in tag_list:
records += len(S.Records_In_File_Table(tag))
return records
def All_Tags(self, parent='', new=True):
# 取得该标签所有的子标签
if new: self.search = []
children = self.treedata.tree_dict[parent].children
for child in children:
self.search.append(child.key)
self.All_Tags(parent=child.key, new=False)
return self.search
def Delete(self):
# 删除标签, 如果有档案使用该标签或其子标签, 则不准删除
key = self.Where()
if self._is_root(): return
if self._records(key)!= 0:
G.Pop('有档案带有该标签或子标签, 不能删除, 请先修改档案标签 !')
return
previous_key = self.Previous_Key(key)
self._delete_tag(key)
self.Tree_Update()
S.Save_To_Tag_Table(self.Tree_To_Dictionary())
self.Expand(previous_key)
self.Select(previous_key)
G._clear_frame1()
def Expand(self, key):
# 展开标签及其子标签, 使他们能被看到, 而不是收折起来, 看不到.
children = self.treedata.tree_dict[key].children
for child in children:
self.Select(child.key)
def _get_key(self):
# 取得一个唯一的键值
i = 1
while True:
if str(i) in self.treedata.tree_dict:
i += 1
else:
return str(i)
def Insert(self):
# 新增标签, 为方便加入, 选择其父标签, 而不选择该新标签
text = G.Popup('新增标签, 同标签也会被视为不同')
if text == None: return
parent = self.Where()
key = self._get_key()
self.treedata.insert(parent, key, text, [])
self.Tree_Update()
self.Select(key)
self.Select(parent)
S.Save_To_Tag_Table(self.Tree_To_Dictionary())
def Key_To_ID(self, key):
# 转换PySimpleGUI的Key为Tkinter的iid
return [k for k in self.IdToKey if (self.IdToKey[k] == key)][0]
def Load_Tree(self, dictionary):
# 由来自数据库标签表的字典, 建立标签数据结构, 供标签树更新及显示
if dictionary == None:
return
children = dictionary[''][1]
while children != []:
temp = []
for child in children:
node = dictionary[child]
self.treedata.insert(node[0], child, node[2], node[3])
temp += node[1]
children = temp
self.Tree_Update()
for child in self.treedata.tree_dict[''].children:
self.Expand(child.key)
self.Select('')
def Previous_Key(self, key):
# 取得该标签的显示位置的上一个标签
self.All_Tags('')
index = self.search.index(key)
result = '' if index==0 else self.search[index-1]
return result
def Rename(self):
# 更改标签的文字内容
key = self.Where()
if key == '': return
text = G.Popup('新标签文字, 同标签也会被视为不同')
if text == None: return
self.treedata.tree_dict[key].text = text
self.Update(key=key, text=text)
S.Save_To_Tag_Table(self.Tree_To_Dictionary())
def _search_text(self, text=None, next=False):
# 搜索标签数据结构中, 带有该字符串部份的标签
# self.search中保留全部的标签列表, self.index指到下一个未搜索的标签
# 供找下一个标签使用, 如果是新的搜索, 会指到从头开始.
if len(self.treedata.tree_dict) < 2: return
if not next:
self.All_Tags()
self.text = text
self.index = 0
else:
if self.text == None:
return
text = self.text
length = len(self.search)
for i in range(self.index, length):
key = self.search[i]
if text.upper() in self.treedata.tree_dict[key].text.upper():
self.Select(key)
self.index = i + 1 if i + 1 < length else 0
return
G.Pop('找不到相关的标签')
self.index = 0
def Search(self):
# 从头开始搜索标签数据结构中的文字
text = G.Popup('搜索标签文字')
if text != None: self._search_text(text)
def Search_Next(self):
# 从上一次搜索位置后继搜索
self._search_text(next=True)
def Select(self, key=''):
# 移到某个标签
iid = self.Key_To_ID(key)
self.Widget.see(iid)
self.Widget.selection_set(iid)
def Sorting(self):
# 将每个标签的子标签按其文字大小排序
for key, node in self.treedata.tree_dict.items():
children = node.children
node.children = sorted(children, key=lambda x: x.text)
self.Tree_Update()
S.Save_To_Tag_Table(self.Tree_To_Dictionary())
def Tree_To_Dictionary(self):
# 将标签数据结构转换成需要的字典, 供存数据库使用
dictionary = {}
for key, node in self.treedata.tree_dict.items():
children = [n.key for n in node.children]
dictionary[key]=[node.parent, children, node.text, node.values]
return dictionary
def Tree_Update(self):
# 更新标签树的标签数据结构
self.Update(values=self.treedata)
def Where(self):
# 查询标签树的选择位置
item = self.Widget.selection()
return '' if len(item) == 0 else self.IdToKey[item[0]]
- 档案树 TREE1
内容类似标签树, 只是方法不太一样.
class Tree1(sg.Tree):
def __init__(self, key):
# 档案树的生成
self.font = ('微软雅黑', 12)
self.key = key
self.treedata = sg.TreeData()
super().__init__(data=self.treedata, col0_width=112, font=self.font,
num_rows=20, row_height=30, background_color='white',
show_expanded=False, justification='left', key=self.key,
visible_column_map=[False,], auto_size_columns=False,
headings=['Nothing',], enable_events=True,
select_mode=sg.TABLE_SELECT_MODE_BROWSE, pad=(0, 0))
def _get_key(self):
# 生成独一无二的键值
i = 1
while True:
if str(i) in self.treedata.tree_dict:
i += 1
else:
return str(i)
def Execute(self):
# 双击执行开启该档案
filename = F.Get_Path(self.treedata.tree_dict[self.Where()].text)
if Path(filename).is_file:
os.startfile(filename)
else:
G.Pop('File not exist !!')
def Insert(self):
# 插入多选档案, 有建立子目录, 移动档案,
# 并更新数据库, 更新档案数据结构, 以及档案树显示
if G.index == 0: return
paths = values['INSERT'].split(';')
for file in paths:
path = Path(file)
directory= F.Create_Directory_By_File_Extension(path)
target = directory.joinpath(path.name)
if F.Move_File(path, target):
S.Insert_File_Table([target.name]+G.select)
key = self._get_key()
self.treedata.insert('', key, target.name, G.select)
G._frame1_update()
def Key_To_ID(self, key):
# 转换PySimpleGUI的KEY为Tkinter的iid
return [k for k in self.IdToKey if (self.IdToKey[k] == key)][0]
def Select(self, key=''):
# 选择档案树的某一行
iid = self.Key_To_ID(key)
self.Widget.see(iid)
self.Widget.selection_set(iid)
def Tree_Update(self, files):
# 删除档案树, 更新档案树, 再更新档案树的显示
self.treedata = sg.TreeData()
for file in files:
key = self._get_key()
self.treedata.insert('', key, file[0], file[1:])
self.Update(values=self.treedata)
self.Select('')
def Where(self):
# 查询目前档案树选择所在
item = self.Widget.selection()
return '' if len(item) == 0 else self.IdToKey[item[0]]
- 类的实例化
F = FILE()
S = SQL()
T0 = Tree0('TREE0')
T1 = Tree1('TREE1')
G = GUI()
- 事件处理
# 定义每个事件的处理方法
function = {'NEW TAG':T0.Insert, 'ROOT TAG':T0.Select, 'DELETE':T0.Delete,
'RENAME' :T0.Rename, 'SEARCH' :T0.Search, 'INSERT':T1.Insert,
'REMOVE' :G.Remove, 'NEXT ONE':T0.Search_Next,
'TREE0_DOUBLE':G.Insert, 'TREE1_CLICK':G._text1_update,
'TREE1_DOUBLE':T1.Execute, 'SORTING':T0.Sorting}
while True:
# 读取事件
event, values = G.window.read()
# 窗口闗闭
if event == None:
break
# 各个按钮事件
elif event in function:
function[event]()
# 程序结束, 闗闭数据库连接, 闗闭窗口, 删除相闗的主变量
S.conn.close()
G.window.close()
del F, S, T0, T1, G
本作品采用《CC 协议》,转载必须注明作者和本文链接
我想问一下,pysimplegui对于其它的gui库有什么优势?