Wolf从零学编程-用Python打造简单加密程序(六)

来源:互联网 发布:ubuntu ssd 性能优化 编辑:程序博客网 时间:2024/06/05 19:10
 分类:

目录(?)[+]

使用tkinter编写程序界面

之前我实现了加密程序所有的功能逻辑,但是目前还只能停留在命令行界面执行,显然是不够友好的,我需要编辑一个简单的UI界面。

在UI库的选择上,我使用了Python内置的tkinter。

这里先安利一个网站:鱼C论坛tkinter,小甲鱼这里发布了N多tkinter的中文使用方法,我这个简陋的UI就是借助鱼C论坛和网上搜索完成的。

一、UI界面排版

大概画了一个UI的草图,之后会尽量根据这个图进行编写。

mark

起初我打算从最简单的开始,就是先搞出一系列标签、输入框、按钮,从界面上看起来完整,然后逐个实现数据传递和函数调用,但是个人感觉这样做真的不好,在最后编写功能实现的时候代码很乱。

我想可以先集中精力搞定一个完整的实例,比如把获取密钥文件的逻辑完全写好,那么签名文件的实现就可以复制后改个名字。

二、逻辑功能

密钥文件选择

这一步有两个选择:filedialog和dnd。前者可创建一个文件对话框,用于打开和保存文件;后者可实现文件夹拖拽。

鱼C论坛里有filedialog的介绍,看了觉得蛮简单,就用它了~

功能实现上,我希望用户自己手动输入文件路径,当然也可以浏览文件夹以选择密钥文件,选择后文件路径会自动填入输入框。

UI上这么显示:

mark

当点击浏览文件时,会弹出文件对话框:

mark

选择文件后,输入框可以填入路径:

mark

下面看代码:

#RSA密钥读取key_file_label = Label(key_frame,text='密钥文件:')  #标签,这里只显示字符key_file_label.grid(row=0,column=2,padx=20,pady=5)  #给标签定位#输入密钥文件路径key_filename = StringVar()      #实例化一个字符串变量key_filename_entry = Entry(key_frame,textvariable=key_filename,width=20)key_filename_entry.grid(row=0,column=3)#选择密钥文件def openKeyFile():    global key_filename_entry  #如果要在函数中复制,就要声明全局变量    fileName = filedialog.askopenfilename()    key_filename_entry.delete(0,END) #清空输入框已有内容    key_filename_entry.insert(0,fileName)  #将文件路径填入输入框Button(key_frame,text="浏览文件",command=openKeyFile).grid(row=0,column=4)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

这段代码用了tkinter中的label(标签)、entry(输入框)、button(按钮)三个组件,第一个参数是组件归属。定义了一个函数,在按钮中调用,可把被选择文件的路径填入输入框,为此要在函数中声明输入框是全局变量。

布局使用了grid(),它比pack()更加灵活。同时,同一个父组件中有了grid(),就不能再用pack(),会报错

_tkinter.TclError: cannot use geometry manager pack inside .46809488 which already has slaves managed by grid
  • 1
  • 1

这段代码稍加修改,可以用在签名文件选择和源文件选择,不过代码重复率好高,我暂时还想不到办法简化。

模式选择

用户打开软件,首先要在DES、RSA、混合加密、签名验证中进行选择,这个功能最常见的应该是下拉列表了,不过tkinter的Listbox、OptionMenu都太丑,可以用ttk的Combobox。

from tkinter import *from tkinter import ttk#模式选择mode_label = Label(init_frame,text='模式选择:')mode_label.grid(row=0,column=0,padx=5,pady=5)mode = StringVar()mode.set('DES')  #设置默认值mode_choice = ttk.Combobox(init_frame,textvariable=mode,values=['DES','RSA','混合模式','数字签名'])mode_choice.grid(row=0,column=1,padx=0,pady=5)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

没有from tkinter import ttk会报NameError: name 'ttk' is not defined

在没有细致学习ttk的情况下,要使用from tkinter import ttk,而不是from tkinter.ttk import *,后者会使用ttk的组件代替tkinter的组件,可能造成如fg(前景色)、bg(背景色)等组件风格参数不能使用。

用Combobox实现的下拉列表是这样的:

mark

和常见的下拉列表一样一样的~~值得一说的是,最好在参数中加上state='readonly',这会限制用户只能选择下拉列表中的选项,从而避免繁琐的有效输入验证。

同样,相似的代码可用于选择加密解密等操作,以及RSA密钥的位数。

输入框激活

在软件面板中,DES密钥、初始值、密钥文件和签名文件并不是无条件活动的,当不需要它们的时候,输入框应该是不可写状态。在tkingter的组件中有3种state(状态):normal(正常)、disabled(禁用)、readonly(只读),注意写小写时是字符串要加引号,’normal’和’disabled’也可写成NORMAL和DISABLED,这时候不用引号。

模式(操作)输入框DESDES密钥和初始值RSA密钥文件混合密钥文件签名(签名)密钥文件签名(验证)密钥文件和签名文件

这个表格列出了模式与输入框激活状态的对应关系,例如用户选择了RSA模式,则应该只有密钥文件的输入框是可输入状态。

这个功能我首先想到了输入验证,在尝试2小时后放弃,以下两种验证方法均失败: 
- 在mode处验证,期望在模式选择后就对相应输入框的state赋值,以改变状态;实测,根本无法更改 
- 在输入框处验证,由于验证validate可设置的值全部与输入框有关,因此只有由激活状态改为不可用状态时成功,当输入框不可用时,无法得到鼠标焦点,也就没办法进行验证

这时候看到了事件绑定,算是把我拯救了,关于事件绑定和验证,还请移步全部是中文的鱼C论坛-事件绑定。

事件绑定的一般语法是:

def handler(event):    ...widget.bind(event,handler)
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

这个代码意为:组件widget发生事件event时,调用方法handler,在这段代码中,handler(event)中的event不用随着事件名称改变。

我只需找到一个合适的event,使得当模式选择完毕后,自动判断需要哪个输入框,将其他输入框关闭,根据模式框特点,选择FocusOut即失去焦点。

事件绑定在模式选择和操作选择后:

mode_choice.bind('<FocusOut>',judgMode)operation_choice.bind('<FocusOut>',judgOperation)
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

然后编写judgState方法就可以了。因为签名和验证需要的文件也不同,所以还要对operation做一个事件绑定。

#模式选择的事件绑定def judgMode(event):    if mode.get() == 'DES':        des_key_entry['state'] = 'normal'        des_IV_entry['state'] = 'normal'        key_filename_entry['state'] = 'disabled'        sig_filename_entry['state'] = 'disabled'    elif mode.get() == 'RSA' or '混合模式':        des_key_entry['state'] = 'disabled'        des_IV_entry['state'] = 'disabled'        key_filename_entry['state'] = 'normal'        sig_filename_entry['state'] = 'disabled'    elif mode.get() == '数字签名':        des_key_entry['state'] = 'disabled'        des_IV_entry['state'] = 'disabled'#操作选择的事件绑定def judgOperation(event):    if operation.get() == '签名':        key_filename_entry['state'] = 'normal'        sig_filename_entry['state'] = 'disabled'    elif operation.get() == '验证':        key_filename_entry['state'] = 'normal'        sig_filename_entry['state'] = 'normal'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

下图就是选择数字签名验证后的效果啦,密钥文件和签名文件可输入,DES密钥和初始值是禁用的。

mark

进度对话框

进度输出使用Text组件,此组件据说异常强大和灵活。但是!它没有readonly状态,Text中的disabled其实就是其他组件中的readonly。

要在Text组件中输出文本很简单,只需熟练使用text.insert(END,var),END是索引,表示从Text文本缓冲区的最后一个字符的下一个位置插入,var是要插入的值,只需不停的给var赋值就可以输出进度了。

先从简单的入手,点击按钮生成RSA密钥文件,并在对话框输出成功消息。

def rsakey():    textVar = base.geneKeys(int(keys_choice.get()))       text.insert(END,textVar)    text.insert(END,'\n')#密钥生成按钮gene_key_button = Button(init_frame,text='生成RSA密钥文件',command=rsakey,width=15)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

点击按钮后,获取RSA位数并调用geneKeys()方法,密钥文件生成后将返回值赋给textVar,在Text组件的实例text中输出,并添加一个换行符。为了给变量赋值,geneKeys()返回值要稍作修改,改为return '密钥文件已生成'

点击两次生成密钥文件,测试成功:

mark


三、整合

所有的模式和操作判断逻辑做成一个方法doCrypto,在UI中获取各个参数后调用doCrypto;同时为了在对话框中打印出进度提示,将各方法的返回值进行类似”密钥文件生成”的更改

获取参数很简单,拿模式选择举例,只要mode = mode_choice.get()',将所有参数获取后全部丢到doCrypto里就成了。

说起来简单做起来难啊。

  • 首先,那么多参数获取,每个写一行感觉太丑。尝试用列表获取,然后回传一个可选参数,这样要涉及到的更改太多,许久之后还在报错就放弃了。先解决’有没有’的问题吧,我丑丑的写了7行参数获取,顺利完成
  • 前面变量命名时有点随意,一共使用了两种命名,为了便于识别,把所有的命名都更改一致

第一次整合出现了这个错误:

_tkinter.TclError: wrong # args: should be ".49388048 insert index chars ?tagList chars tagList ...?"
  • 1
  • 1

出现这个错误的原因是:我在调用insert的时候,要插入的变量值是None。因为我调用的方法没有设置返回值,在doCrypto()末尾简单添加一行return "操作已完成"就不会再报错了。

现在的代码在不故意捣乱的情况下,可以正常运行,但是问题多多,体验也不好,已全部MARK,一条条搞定。

测试用例:DES加密和解密,密码和初始值都是12345678,操作全部成功。

mark


github仓库已更新。

接下来要学会封装。

阅读全文
0 0
原创粉丝点击