拼图成形/心3.0 Python【主要加了UI界面】

在这之前已经陆续记录了两篇

第一篇主要是大概理了一下思路(已经能实现功能);

第二篇主要是重新整理了代码,修改了一些逻辑;

  就内容方法来说,都是换汤不换药,获取“形状”的那部分还很不成熟,但也够用了(主要是懒得优化了)。优化结构之后便想着给它加一个交互界面,这样用起来就很舒坦了,同时还能练练手,继续学习一点GUI。
  所以,这篇博客主要是记录一下UI的构建。主要功能的实现之前也已经记录。




关于之前版本

版本说明
1.0[链接]详细记录了思路、实现方法。
功能已经初步实现,代码结构稍显混乱。
2.0[链接]改进了代码结构,以及一些小的代码逻辑,
主要实现方法没有改观(人懒了,能用就..)


UI设计开始

 这个之前已经画了一个饼[链接在这里],UI是前久就肝好的,当时没有记录,不想只是草草的几句话,然后贴代码。
 总觉得还是要用点心记录总结啊,要不然总是原地踏步,“书海相遇,交还书海”?!!!哈哈哈哈


之前画的饼:
(来一个倒序…)



UI框架构建所需部件

UI库部件说明
tkinter按钮(Button)用于路径、颜色、图形、开始等选择的触发
文本框(Entry)用于路径的填写/展示,以及颜色的直观与数字展示
标尺(Scale)用于数值相关参数的选择
文件选择对话框(filedialog)用于路径相关的操作
调色板(colorchooser)用于颜色的选择


所需函数、模块梳理引用

(正向梳理…)

 根据之前版本对代码的梳理,要完成贴图,需要用到的参数有:路径类(字符串)、颜色(字符串)、数字参数类(数型)共三类。

  • 至于路径,可以由tkinter自带的filedialog很方便进行选择,同时也可以设有文本框进行显示或者手动输入。
  • 至于颜色,同样可以用tkinter自带的调色板直接选择,并用文本框辅助显示或者手动显示。
  • 至于数值类的参数,有很多选择:标尺、文本框、选值框spinbox。但是由于后两者有人为输入或是调节数值大小并不是那么方便(个人觉得),且容易输入错误卡bug。所以,就一刀切,直接用标尺,直接限制参数类型、范围、刻度。

    路径之类的,感觉还是直接用filedialog的好,不容易卡bug。所以可以限制路径显示文本框的状态属性readonly(只读),从而避免认为失误改变路径内容;

    颜色也同理,毕竟还是用眼选方便,直接输入颜色的数值这可就太淦。

    标尺的话,需要根据数值参数的具体作用,进而选择合适的范围、刻度。
  • 路径相关:filedialog
    • 文件路径的选择(打开、保存文件)
  • 颜色选择 :colorchooser
    • 贴图所用底图的背景颜色选择
  • 各组件
    • 文本框Entry、标尺Scale、按钮Button——参数的选择、显示
  • 更多交互
    • 消息框:messagebox
    • 进度条:progressar
from tkinter.ttk import Progressbar	# 进度条
from tkinter import Tk, Button, Scale, Entry, HORIZONTAL, StringVar, IntVar
from tkinter.colorchooser import askcolor	# 调色板
from tkinter.filedialog import asksaveasfilename, askopenfilename, askdirectory	# 文件选择对话框
from tkinter.messagebox import askyesno, showinfo


参数梳理、UI初步搭建

所需参数梳理并作初始化

特殊类别参数具体实现
字符类StringVar
数字类IntVar

用tkinter自带这两个对象,主要是可以运用其 set 函数与 get 函数来完成数值的置入与获取,同时还可以与一些组件很好的关联更加方便参数的定向获取。

参数的具体意义参考代码注释,以及之前两篇文章

版本说明
1.0[链接]详细记录了思路、实现方法。
功能已经初步实现,代码结构稍显混乱。
2.0[链接]改进了代码结构,以及一些小的代码逻辑
def __init__(self):
    Heart.__init__(self)
    self.root = Tk()
    self.root.title('请选择相关参数【贴图UI】')	#窗口标题
    self.root.geometry('700x540')	# 窗口大小。组件放置过程中不断调整
    self.root.attributes("-alpha", 0.97)	# 窗口透明度
    self.root.configure(background='beige')	# 窗口背景颜色

    self.imgs_path = StringVar()	# 贴图所存的文件夹路径
    self.source_path = StringVar()	# 黑白图/形状图的路径
    self.img_outpath = StringVar()	# 目标图片的输出路径
    self.quality = IntVar()	# 目标文件的输出品质
    self.quality.set(40)	# 置初始值
    self.leap = IntVar()	# 跳行数
    self.leap.set(30)
    self.per_size = IntVar()	# 每张小贴图的大小
    self.per_size.set(200)
    self.dx = IntVar()	# 贴图的横向间距
    self.dx.set(20)
    self.dy = IntVar()	# 贴图的纵向间距
    self.dy.set(22)
    self.bg = StringVar()	# 画布背景颜色
    self.bg.set('#000000')
    self.show = 0	# 贴图完成后是否展示

UI组件的布置
 可以结合下图看代码。下图的布局并非一步到位,也是搭建过程中不断调整得到的。

窗口第1行,图集路径部分
在这里插入图片描述

# 图源
Entry(self.root, textvariable=self.imgs_path,
      state='readonly', width=50, font=3,
      bg='black', fg='black',
      ).grid(row=0, column=0, columnspan=3)
Button(self.root, text='选择图集路径',
       font=16, command=self.get_imgspath,
       width=14,
       fg='black', bg='violet'
       ).grid(row=0, column=3, columnspan=2, sticky='E')

说明:
  1、self.imgs_path变量与组件Entry相关联,从而可以完成显示、置值、取值的操作;
  2、self.get_imgspath函数与Button组件关联,可以将其功能设计为:点击后触发文件选择对话框,选择后将值置入变量self.imgs_path
  3、其它参数就字面意思,详细了解可以翻阅其它博客。
  4、接下来主要梳理一下布局的知识(hin重要)【grid、place、pack】

梳理之前先写一下涉及的功能函数:

def get_imgspath(self):
    self.imgs_path.set(askdirectory(title='选择图集路径',))
    if not self.imgs_path:
        if askyesno(title='图集路径未选择', message='是否重新选择图集路径?'):
            self.get_imgspath()
        else:
            pass

Tkinter布局之grid、place、pack梳理
grid(**options)
选项含义
column--指定组件插入的列(0表示第一列)
--默认值是0
columnspan--指定用多少列(跨列)显示该组件
in_ --将该组件放到该选项指定的组件中
--指定的组件必须是该组件的父组件
ipadx--指定水平方向上的内边距
ipady--指定垂直方向上的内边距
padx--指定水平方向上的外边距
pady--指定垂直方向上的外边距
 row --指定组件插入的行(0表示第一行)
 rowspan --指定用多少行(跨行)显示该组件
 sticky --控制组件在grid分配的空间中的位置 --可以使用N,E,S,W以及他们的组合来定位 --使用加号(+)表示拉长填充,例如N+S表示将该组件垂直拉长填充网格,N+S+W+E表示填充整个网格
--不指定该值则居中显示
pack(**options)
选项 含义
anchor --控制组件在pack分配的空间中的位置
--N, NE, E, SE, S, SW, W, NW或CENTER来定位(EWSN表示东南西北)
--默认值是CENTER
expand  --指定是否填充父组件的额外空间
--默认值是False
fill --指定填充pack分配的空间
--默认值是NONE,表示保持子组件的原始尺寸
--还可以使用的值有:X(水平填充),Y(垂直填充)和BOTH(水平和垂直填充)
in_ --将该组件放到该选项指定的组件中
--指定的组件必须是该组件的父组件
ipadx --指定水平方向上的内边距
ipady   --指定垂直方向上的内边距
padx --指定水平方向上的外边距
pady --指定垂直方向上的外边距
side --指定组件的放置位置
--默认值是TOP
--还可以设置的值有:LEFT,BOTTOM,RIGHT
place(**options)
选项 含义
anchor --控制组件在place分配的空间中的位置
--N, NE, E, SE, S, SW, W, NW或CENTER来定位(EWSN表示东南西北)
--默认值是NW
bordermode --指定边框模式(INSIDE或OUTSIDE)
--默认值是INSIDE
height --指定该组件的高度(像素)
in_ --将该组件放到该选项指定的组件中
--指定的组件必须是该组件的父组件
relheight --指定该组件相对于父组件的高度
--取值范围是0.0~1.0
relwidth --指定该组件相对于父组件的宽度
--取值范围是0.0~1.0
relx --指定该组件相对于父组件的水平位置
--取值范围是0.0~1.0
rely --指定该组件相对于父组件的垂直位置
--取值范围是0.0~1.0
width --指定该组件的宽度(像素)
 x --指定该组件的水平偏移位置(像素)
--如果同时指定了relx选项,优先实现relx选项
 y  --指定该组件的垂直偏移位置(像素)
--如果同时指定了rely选项,优先实现rely选项

窗口第2行,形状图路径部分
在这里插入图片描述

# 图形图
self.source_et = Entry(self.root, textvariable=self.source_path,
      state='readonly', width=37, font=3, fg='grey',
      )
self.source_et.grid(row=1, column=1, columnspan=2,)
self.source_path.set('请选择黑白图....')

self.unlockb = Button(self.root, text='Unlock',
       width=6, font=10, height=1,
       command=self.unlock, state='disable',
       fg='purple', bg='#fff5ee'
       )
self.unlockb.grid(row=1, column=0, columnspan=1, pady=1, sticky='')

self.default = Button(self.root, text='默认', font=16,
       width=6,
       command=self.default_list,
       fg='black', bg='violet',
       )
self.default.grid(row=1, column=3, sticky='E')
self.source_button = Button(self.root, text='选择', width=7, font=16,
       command=self.get_source,
       fg='black', bg='violet',
       )
self.source_button.grid(row=1, column=4, sticky='W')

说明:
  关于形状图(黑白图)的选择逻辑,这里在代码中加入了心形图的数据,于是将其作为默认选项(代码的初衷便就是贴图成心);

*点击默认按钮后,文本框、按钮失效即被锁定,点击Unlock按钮可解除锁定;
在这里插入图片描述
*需要选择其他形状的话,可以点击选择按钮选择制作好的黑白图。此时函数self.【黑白图的规范详见之前的文章】

  • 将相关变量与文本框关联
  • 默认按钮关联函数,其功能大概为:讲贴图数据设为默认,将相应的组件的状态设为disabled,同时置必要的标志参数。
  • Unlock按钮关联函数,功能为:重新设置相应组件状态为normal
  • 选择按钮关联函数,功能大致为:跳出文件选择窗口,获取形状图路径。

涉及的功能函数:

def unlock(self):
    self.if_default = 0
    self.source_button['state'] = 'normal'
    self.default['state'] = 'normal'
    self.source_button['bg'] = 'violet'
    self.default['bg'] = 'violet'
    self.source_et['fg'] = 'grey'
    self.source_path.set('请选择黑白图....')
    self.unlockb['state'] = 'disable'
    self.unlockb['bg'] = '#fff5ee'

def default_list(self):
    if askyesno('确认', '是否选择默认形状?'):
        self.lst = lst
        self.if_default = 1
        self.source_button['bg'] = 'snow'
        self.default['bg'] = 'snow'
        self.source_button['state'] = 'disable'
        self.default['state'] = 'disable'
        self.unlockb['bg'] = 'tomato'
        self.unlockb['state'] = 'normal'
        self.source_et['fg'] = 'red'
        self.source_path.set('已锁定默认(心形)')

def get_source(self):
    self.source_path.set(askopenfilename(title='选择黑白图路径',
                                       filetypes=[('pic', '.jpg .gif .png .jpeg')])
                         )
    if not self.imgs_path:
        if askyesno(title='图片路径未选择', message='是否重新选择?'):
            self.get_source()
        else:
            pass

窗口第3行,输出路径部分
在这里插入图片描述

# 输出
Entry(self.root, textvariable=self.img_outpath,
      state='readonly', width=50, font=3,
      ).grid(row=2, column=0, columnspan=3)
Button(self.root, text='选择保存路径', font=16,
       command=self.get_imoutpath,
       width=14,
       fg='black', bg='violet',
       ).grid(row=2, column=3, columnspan=2, sticky='E')

说明:
  *按钮关联函数,功能大致为:跳出文件选择窗口,获取输出路径。
  *将相关变量与文本框关联

涉及的功能函数:

def get_imoutpath(self):
    self.img_outpath.set(asksaveasfilename(title='选择保存路径',
                                       filetypes=[('pic', '.jpg .gif .png .jpeg')],
                                       defaultextension='.png',
                                       initialfile='wula.png')
                         )
    if not self.imgs_path:
        if askyesno(title='图片路径未选择', message='是否重新选择?'):
            self.get_imoutpath()
        else:
            pass

窗口第4至6行,数字参数部分
在这里插入图片描述

# 品质
Scale(self.root,
      from_=2, to=96,
      font=4, length=320,
      tickinterval=8,
      orient=HORIZONTAL,
      resolution=1,
      fg='white', bg='black',
      bd=2,
      variable=self.quality,
      label='输出图片品质'
      ).grid(row=4, column=0, columnspan=2, sticky='W', pady=10)
# 跳行
Scale(self.root,
      from_=1, to=98,
      font=4, length=320,
      tickinterval=10,
      orient=HORIZONTAL,
      resolution=1,
      fg='white', bg='black',
      variable=self.leap,
      label='跳行步长').grid(row=4, column=2, columnspan=3, sticky='W',pady=10)

# 单张尺寸
Scale(self.root,
      from_=20, to=800,
      font=4, length=640,
      tickinterval=50,
      orient=HORIZONTAL,
      resolution=20,
      fg='gold', bg='black',
      variable=self.per_size,
      label='单张贴图尺寸').grid(row=5, column=0, columnspan=5, pady=10)
# 间隔
Scale(self.root,
      from_=0, to=200,
      font=4, length=320,
      tickinterval=40,
      orient=HORIZONTAL,
      resolution=1,
      fg='lime', bg='black',
      variable=self.dy,
      label='纵向间距').grid(row=7, column=0, columnspan=2, sticky='W', pady=10)
Scale(self.root,
      from_=0, to=200,
      font=4, length=320,
      tickinterval=40,
      orient=HORIZONTAL,
      resolution=1,
      fg='lime', bg='black',
      variable=self.dx,
      label='横向间距').grid(row=7, column=2, columnspan=3, sticky='W', pady=10)

说明:
  *要点一:根据参数含义调整设置标尺的范围、刻度;
  *要点二:将组件与相关变量关联。

窗口第7行,背景颜色选择部分
在这里插入图片描述

# 背景颜色
self.show_c = Entry(self.root, bg=self.bg.get(), state='normal',
                    font=18, width=8)
self.show_c.grid(row=8, column=0, sticky='E')
self.bg_et = Entry(self.root,
      textvariable=self.bg,
      state='readonly', width=19, font=16,
      fg=self.bg.get(),
      )
self.bg_et.grid(row=8, column=1, sticky='E', pady=5)
Button(self.root,
       text='选择背景颜色', font=16,
       command=self.get_bg,
       fg='black', bg='gold'
       ).grid(row=8, column=2, sticky='W', pady=5)# pady 组件间的纵间距

说明:
  *第一个小模块是文本框,用于直观显示所选取的颜色;
  *第二个小模块也是文本框,用于所选颜色的16进制显示/输入;
  *第三个模块便是按钮,与其关联函数功能设计为:调出调色板获取颜色,并改变之前两个小模块的参数,使所选颜色得以显示。

涉及的功能函数:

    def get_bg(self):
        self.bg.set(askcolor(title='请选择背景颜色')[-1])
        self.bg_et['fg'] = self.bg.get()
        self.show_c['bg'] = self.bg.get()
        if not self.bg:
            if askyesno(title='背景颜色未选择', message='是否重新选择?'):
                self.get_bg()
            else:
                pass

窗口第7行,开始退出按钮部分
在这里插入图片描述

Button(self.root, text='开始',
       width=10, font=50, height=2,
       command=self.test,
       fg='orangered', bg='springgreen'
       ).grid(row=8, column=3, columnspan=1, pady=10)
Button(self.root, text='退出',
       width=7, font=20, height=2,
       command=self.over,
       fg='black', bg='red'
       ).grid(row=8, column=4, columnspan=1, pady=10)

self.root.mainloop()

说明:
  *开始按钮关联函数功能大致为:判断参数是否获取完全、是否合法,如果准备就绪,那么就开始制作(参数传入功能代模块),否则视为没准备好并通过消息窗做出相应提示;
  *退出按钮功能则设计为destroy便可。

涉及的功能函数:

# 开始按钮对应
def test(self):
    if self.imgs_path.get():
        if self.source_path.get() and (self.source_path.get() != '请选择黑白图....'):
            if self.img_outpath.get():
                self.show = askyesno('绘图', '贴图后是否展示?')
                self.mkit(leap=self.leap.get(),
                          imgs_path=self.imgs_path.get(),
                          source_path=self.source_path.get(),
                          out={
                              'path': self.img_outpath.get(),
                              'quality': self.quality.get()
                                },
                          show=self.show,
                          per_size=self.per_size.get(),
                          bg=self.bg.get()
                          )
                #showinfo('完成!', ' 已完成')
                '''
                if askyesno('bye~~?', '是否结束程序?'):
                self.over()  
                '''
            else:
                showinfo('提示', '未选择输出路径,请选择并重新确认')
                self.get_imoutpath()
                #self.test()
        else:
            if askyesno('未选黑白图', '是否选择已有黑白图?【否则用默认数据】'):
                self.get_source()
            else:
                self.default_list()
            #self.test()
    else:
        showinfo('提示', '未选择图集路径,请选择并重新确认')
        self.get_imgspath()
        #self.test()

def over(self):
    self.root.destroy()

进度条部分

在这里插入图片描述

进度条的简单使用可以参考下面这片文章
《进度条的简单使用——点击直达》

self.show_progressbar()
self.p_bar['maximum'] = int((lst[-1][0]-top_yblank)/leap-1)
for line in lst:
    #根据leap值筛去不贴图的像素条,达到控制贴图像素条数
    if not (line[0] - top_yblank) % leap:
        # 当前-行数-(从零开始) [每行高度(per_size+dy)]
        column = int((line[0] - top_yblank) // leap)
        #print(f'\rget__{column+1}/{lst[-1][0]//leap}__!', end='')
        self.p_bar['value'] = column
        self.root.update()
        for x in range(line[1], line[2]+1, per_size+self.dx.get()):
            s_img = openi(next(imgs)).resize((per_size, per_size))
            canv.paste(s_img, (x, top_yblank+column*int(per_size+self.dy.get())))
else:
    showinfo('完成!', '已完成!')
    self.lst = []
    self.p_bar.destroy()
    self.p_button.destroy()
    self.root.geometry('700x540')
    self.root.update()
    #print('\rAll Done!', end='')
    pass
def show_progressbar(self):
    self.root.geometry('700x580')
    self.p_button = Button(self.root,
           text='贴图进度', font=19, state='disable', bg='snow'
           )
    self.p_button.grid(row=9, column=0, columnspan=1)
    self.p_bar = Progressbar(self.root,
                        length=500, orient=HORIZONTAL,
                        value=0)
    self.p_bar.grid(row=9, column=1, columnspan=4, pady=10)

说明:
  *进度条的逻辑为:开始贴图是在窗口中加载出进度条,并根据贴图进度跟新进度条填充状态;当贴图结束时,“去掉”进度条,窗口大小还原。
  *进度条的使用是在功能函数内部,如果UI与功能代码分开为两类的话,需要注意子父类继承,使参数、函数串联起来。


至此,这个“小项目”也已经完成。


 总结一下。这个练手“小项目”中比较详细的整理了tkinter的布局知识,也涉及了进度条、消息窗的简单使用,还有文件对话框filedialog模块、调色板colorchooser模块的使用,以及几个常用组件的进一步学习(参数的学习,参数的设置关联,根据实际需要着重使用某些参数…)。用于tkinter学习练手是很不错的小项目,理论性的只是看再多也不如动手实践几个小项目来的快啊。


源代码存档


给你小心心

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

薛定谔的壳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值