本篇博客我将介绍Tkinter实践项目《植物杂交实验室》中的杂交实验室主菜单、基础植物图鉴、杂交植物图鉴、杂交植物更多信息四个页面的制作。
它们作为主窗口的子页面实例,除了继承主窗口的基础设置(如图标、标题、尺寸等等)、还可以使用主窗口的属性和方法(如数据变量self.hybridizationPlants、页面跳转方法switch_to_screen等等),以及在constants.py中的常量定义和tool.py中定义的通用组件。具体实现见博客10天速通Tkinter库——Day6:项目整体框架介绍
目录
1. 杂交实验室主界面
这个界面非常简单:背景图片 + 五个按钮 + 一个框
首先创建一个名为BreedingScreen
的类,这个类继承自Tkinter的Frame
类,并且使用create_background加载一张背景图片。
class BreedingScreen(tk.Frame):
"""杂交实验室主菜单界面"""
def __init__(self, parent):
super().__init__(parent)
self.breeding_background = create_background(self,breedingScreen_path)
然后是五个按钮,直接使用create_button生成,并且定义点击事件函数,就是调用root.switch_to_screen方法:
# 基础植物图鉴按钮
self.base_catalog_button = create_button(self,
dark_image_path=plantCatalogButton1_path,
light_image_path=plantCatalogButton2_path,
width=360,
height=180,
locate_x=81,
locate_y=104,
command=self.on_base_catalog_button_click)
# 杂交植物图鉴按钮
self.hybrid_catalog_button = create_button(self,
dark_image_path=hybridCatalogButton1_path,
light_image_path=hybridCatalogButton2_path,
width=360,
height=180,
locate_x=460,
locate_y=104,
command=self.on_hybrid_catalog_button_click)
# 杂交实验室按钮
self.experiment_button = create_button(self,
dark_image_path=experimentButton1_path,
light_image_path=experimentButton2_path,
width=360,
height=180,
locate_x=81,
locate_y=293,
command=self.on_experiment_button_click)
# 杂交历记录按钮
self.record_button = create_button(self,
dark_image_path=historyButton1_path,
light_image_path=historyButton2_path,
width=360,
height=180,
locate_x=450,
locate_y=293,
command=self.on_record_button_click)
# 杂交建议显示器
self.advice_text()
# 关闭界面
self.close_button = close_button(self,close1_1_path,close1_2_path)
self.pack()
def on_base_catalog_button_click(self):
# 点击基础植物图鉴按钮的事件处理
self.master.switch_to_screen('plant_catalog_screen')
def on_hybrid_catalog_button_click(self):
# 点击杂交植物图鉴按钮的事件处理
self.master.children['hybrid_catalog_screen'].card_button_matrix(page=0)
self.master.switch_to_screen('hybrid_catalog_screen')
def on_experiment_button_click(self):
# 点击杂交实验按钮的事件处理
self.master.switch_to_screen('experiment_screen')
def on_record_button_click(self):
# 点击杂交记录按钮的事件处理
self.master.children['record_screen'].all_record(page=0)
self.master.switch_to_screen('record_screen')
最后需要单独实现的是循环建议框,这个框的构成有一点点复杂。因为我没有发现任何一种组件可以单独实现“背景+文字+无边框”,也可能我略有一点强迫症。于是我选择使用label加载背景,使用button显示文字,尽可能组合起来不违和。
def advice_text(self):
image = Image.open(advice_background_path)
# 将图片转换为Tkinter可以使用的格式
image = image.resize((663, 89), Image.Resampling.LANCZOS)
photo = ImageTk.PhotoImage(image)
background_label = tk.Label(self, image=photo,bd=0)
background_label.place(x=106,y=470)
self.photo = photo
suggestions = ["你可以尝试不同的杂交方式",
"也许使用不同类型的植物效果会更好!",
"当亲本植物超过四种,会发生糟糕的事!",
"如果没有想法,就去看看历史记录吧!",
"你需要避免完全相反的属性,比如冰与火"]
random_index = random.randint(0, len(suggestions)-1)
suggestion = suggestions[random_index]
button = tk.Button(self,text=suggestion,font=("Times New Roman", 19, "bold"),
fg="gold",
width=39,height=1,
bd=0,background="saddlebrown",
command=None,
activebackground="saddlebrown"
)
button.place(x=145,y=492)
self.after(60000,self.advice_text)
suggestions
列表包含了五条建议文本。random.randint(0, len(suggestions)-1)
:生成一个随机数,作为suggestions
列表的索引。suggestion = suggestions[random_index]
:根据随机索引选择一条建议。- 创建一个Tkinter的Button组件,用于显示建议文本。这里设置了按钮的字体、颜色、大小、背景色等属性。
button.place(x=145,y=492)
:将按钮放置在窗口的指定位置。self.after(60000, self.advice_text)
:设置一个定时器,60000毫秒(即一分钟)后再次调用advice_text
方法。
2. 基础植物图鉴
这个界面看似内容很少,但是,按钮矩阵的实现还是稍微带点难度。
创建一个名为PlantCatalogScreen的类,这个类继承自Tkinter的Frame
类。
class PlantCatalogScreen(tk.Frame):
"""基础植物图鉴
1. 卡片矩阵
2. 植物说明
"""
def __init__(self, parent):
super().__init__(parent)
self.breeding_background = create_background(self,basalcatalog_background_path)
self.close_button = close_button(self,close2_1_path,close2_2_path,clear=94)
self.back_button = back_button(self,back2_1_path,back2_2_path,clear=94)
self.empty_describe = empty_describe(self)
类的初始化:
create_background
:加载一张背景图片。close_button
:创建了一个关闭按钮。back_button
:创建了一个返回按钮。empty_describe
:创建了一个空白描述区域,用于在没有具体植物信息时显示。
def card_button_matrix(self):
"""创建基础植物卡片矩阵"""
card_frame = tk.Frame(self)
card_frame.place(x=28,y=88)
for row in range(6):
for col in range(8):
def on_click(row, col):
card_music.play()
"""卡片点击事件, 展示植物说明"""
label = tk.Label(self, image=self.master.basal_plant_describe[row][col], bd=0)
label.place(x=530, y=94)
button = tk.Button(card_frame, image=self.master.basal_plant_card[row][col])
button.config(borderwidth=0,highlightthickness=0)
button.grid(row=row, column=col,sticky="NSEW")
button.bind('<Button-1>', lambda event, r=row, c=col: on_click(r, c))
-
card_button_matrix
方法:这个方法用于创建植物卡片矩阵。-
card_frame
:创建了一个新框架用于放置卡片按钮。 -
使用
grid
布局管理器来放置卡片按钮,每行有8个按钮,共6行。 -
对于每个卡片按钮,定义了一个点击事件处理器
on_click
。当卡片被点击时:- 播放音效(
card_music.play()
)。 - 显示植物的详细说明。这通过创建一个新的标签(
tk.Label
)并设置其图像(self.master.basal_plant_describe[row][col]
)来实现。
- 播放音效(
-
-
卡片按钮的创建:每个卡片按钮都是一个
tk.Button
实例,它使用了原始卡片图片,并设置了边框宽度和高亮厚度为0,以使按钮看起来更简洁。- 每个按钮都绑定了一个点击事件处理器
on_click
,处理器接收行和列作为参数,并在按钮被点击时调用。
- 每个按钮都绑定了一个点击事件处理器
- 卡片和描述的图片是主界面初始化时加载的,使用self.master.basal_plant_describe和self.master.basal_plant_card进行调用。
3. 杂交植物图鉴和更多信息界面
3.1 杂交植物图鉴
这个界面复杂的点在于:
- 杂交植物数据是动态的
- 上下页需要切换
先来看看杂交植物json数据:
{
"id": 1,
"parent_base_plant_ids": [ 1, 2 ],
"hybridization_method": 1,
"new_hybrid": false
}
字段分别是植物id,亲本植物id列表,获得的杂交方法,以及是否为新植物。而图鉴界面只加载new_hybrid值为false的植物,亲本植物id列表和杂交方法会传给更多信息页面。
class HybridCatalogScreen(tk.Frame):
"""基础植物图鉴
1. 卡片矩阵(翻页功能, 包含两个页面)
2. 植物说明
3. 更多信息
"""
def __init__(self, parent):
super().__init__(parent)
self.breeding_background = create_background(self,hybridCatalog_background_path)
self.close_button = close_button(self,close2_1_path,close2_2_path,clear=82)
self.back_button = back_button(self,back2_1_path,back2_2_path,clear=82)
self.empty_describe = empty_describe(self,Y=82)
同样的,创建一个杂交植物图鉴类,包括对父类的tk.Frame的继承、加载一张背景图片,一个关闭按钮、一个返回按钮以及一张空白描述图片。
self.ahead_buttton = create_button(self,
dark_image_path=ahead1_1_path,
light_image_path=ahead1_2_path,
width=110,
height=27,
locate_x=265,
locate_y=566,
command=self.on_ahead_button_click)
self.next_buttton = create_button(self,
dark_image_path=next1_1_path,
light_image_path=next1_2_path,
width=110,
height=27,
locate_x=377,
locate_y=566,
command=self.on_next_button_click)
这是上一页和下一页按钮的创建,点击事件定义如下:
def on_ahead_button_click(self):
"""上一页按钮点击事件"""
if self.page==1:
self.page_change = True
self.page=0
self.card_button_matrix(page=self.page)
self.empty_describe = empty_describe(self,Y=82)
self.pack()
self.page_change = False
def on_next_button_click(self):
"""下一页按钮点击事件"""
if self.page==0 and self.plants_num>48:
self.page_change = True
self.page=1
self.card_button_matrix(page=self.page)
self.empty_describe = empty_describe(self,Y=82)
self.pack()
self.page_change = False
on_ahead_button_click
方法
- 功能: 当用户点击“上一页”按钮时,此方法被调用。它检查当前页面是否为第一页(
self.page==1
),如果是,则更新页面状态和执行以下操作:- 设置
page_change
标记为True
,用于后续方法或逻辑中,以识别页面是否已改变。 - 将页面设置为第0页(
self.page=0
),这意味着回到卡片矩阵的第一页。 - 调用
card_button_matrix
方法,参数为当前页面和初始化标志(page=self.page, init=1
),用于重新生成或更新卡片矩阵的布局。 - 更新空描述区域(
self.empty_describe = empty_describe(self,Y=82)
),用于显示或更新页面空闲区域的内容。 - 调用
pack
方法,重新布局并显示界面元素。
- 设置
on_next_button_click
方法
- 功能: 当用户点击“下一页”按钮时,此方法被调用。它检查当前页面是否为最后一页(
self.page==0
)且总植物数量大于48(超过一页),如果是,则更新页面状态和执行以下操作:- 设置
page_change
标记为True
。 - 将页面设置为第1页(
self.page=1
),这意味着显示卡片矩阵的第二页。 - 同样,调用
card_button_matrix
方法,参数为当前页面和初始化标志(page=self.page, init=1
)。 - 更新空描述区域(
self.empty_describe = empty_describe(self,Y=82)
)。 - 调用
pack
方法,重新布局并显示界面元素。
- 设置
self.init_card_photo = [[None for _ in range(8)] for _ in range(6)] # 卡片矩阵背景
self.old_hybrid_plant = [[0 for _ in range(8)] for _ in range(12)] # 杂交前可展示植物id
self.new_hybrid_plant = [[0 for _ in range(8)] for _ in range(12)] # 杂交后可展示植物id
# 卡片矩阵初始化
self.plants_num = 0
self.page = 0
self.page_change = False
self.card_frame = tk.Frame(self,bd=0)
self.card_frame.place(x=28,y=89)
self.init_card_matrix()
self.card_button_matrix(page=0,init=1)
# 记录植物亲本及杂交方法信息
self.method = 0
self.basal_plant_ids = [0,0,0,0]
self.pack()
- self.init_card_photo:这个属性用于初始化矩阵按钮背景,因为定义矩阵Frame时,有时按钮会有空缺,于是乎就使用对应位置的背景填充
- self.old_hybrid_plant和self.new_hybrid_plant都用于记录需要显示的植物ID。因为由于杂交实验会更新杂交植物数据,因此用来判断哪些位置更新了,只改变更新位置,减少页面刷新时的多于操作。
- self.plants_num记录解锁的杂交植物数量
- self.page用来记录页号,有0,1两页
- self.page_change判断页面是否切换,True和False两个值
接着是加载初始按钮矩阵背景和按钮矩阵的实现
def init_card_matrix(self):
"""加载卡片矩阵空白背景"""
for row in range(6):
for col in range(8):
self.init_card_photo[row][col]= image_load(57,78,init_card_path,col+1+row*8,".jpg")
def data_load(self,hybridizationplants):
"""加载展示杂交植物卡片"""
self.plants_num=0; row = 0; col = 0
for plant in hybridizationplants:
if plant.get('new_hybrid')==False:
self.plants_num += 1
self.new_hybrid_plant[row][col] = plant.get("id")
col += 1
if col==8:
row += 1
col = 0
- init_card_matrix函数加载48张按钮背景图片
- data_load用于获取最新的杂交植物信息
def card_button_matrix(self,page=0,init=0):
"""创建杂交植物卡片矩阵
1. 保存上一次卡片数据
2. 加载最新卡片数据
3. 只改变更新部分的按钮图片
4. 使用self.page加载不同页面的数据
"""
if init==0:
for row in range(12):
for col in range(8):
self.old_hybrid_plant[row][col] = self.new_hybrid_plant[row][col]
self.data_load(self.master.hybridizationPlants)
for row in range(6):
for col in range(8):
if self.new_hybrid_plant[row+page*6][col]!=self.old_hybrid_plant[row+page*6][col] or \
(self.page_change==True and self.new_hybrid_plant[row+page*6][col]!=0):
card_photo = id_to_photo(self.new_hybrid_plant[row+page*6][col],self.master.hybrid_plant_card)
button = tk.Button(self.card_frame, image=card_photo)
button.config(borderwidth=0,highlightthickness=0)
button.grid(row=row, column=col,sticky="NSEW")
def on_click(row, col):
card_music.play()
describe_photo = id_to_photo(self.new_hybrid_plant[row+page*6][col],self.master.hybrid_plant_describe)
label = tk.Label(self, image=describe_photo, bd=0)
label.place(x=530, y=82)
id = self.new_hybrid_plant[row+page*6][col]
self.basal_plant_ids = self.master.hybridizationPlants[id-1].get("parent_base_plant_ids")
self.method = self.master.hybridizationPlants[id-1].get("hybridization_method")
button.bind('<Button-1>', lambda event, r=row, c=col: on_click(r, c))
elif self.new_hybrid_plant[row+page*6][col]==0 :
label = tk.Label(self.card_frame,image=self.init_card_photo[row][col],bd=0)
label.grid(row=row, column=col,sticky="NSEW")
card_button_matrix用于创建卡片按钮矩阵,对其进行详解
- 这个方法接受两个参数:
page
和init
。page
用于指定加载哪个页面的数据,init
用于指定是否是初始化操作,初始化只有主界面初始化时用到一次。 - 如果
init
参数为0,表示不是初始化操作,则将self.new_hybrid_plant
的数据保存到self.old_hybrid_plant
中,以便后续比较。 - 调用
self.data_load
方法来加载最新的杂交植物数据。 - 创建按钮和布局:
- 遍历卡片矩阵的每一行和每一列。
- 如果
self.new_hybrid_plant
中的数据与self.old_hybrid_plant
不同,或者页面有变化且卡片不为空,则创建一个新的按钮。 - 使用
id_to_photo
函数将植物ID转换为对应的图片,并设置按钮的图片。 - 配置按钮无边框和边框厚度。
- 使用
grid
方法将按钮放置在卡片框架中,并设置粘性(sticky)属性。
- 绑定点击事件:
- 定义了一个
on_click
函数,当按钮被点击时触发。 - 播放音效
card_music.play()
。 - 获取描述图片并显示。
- 获取植物ID,并更新亲本植物ID列表和杂交方法。
- 将
on_click
函数绑定到按钮的鼠标左键点击事件。
- 定义了一个
- 显示空白卡片:
- 如果
self.new_hybrid_plant
中对应的单元格为0,表示没有植物数据,则显示一个初始卡片图片。
- 如果
这个函数应该是本项目中最难的一个,重构了好几次,最终还挺满意。
self.more_button = create_button(self,
dark_image_path=morebutton1_path,
light_image_path=morebutton2_path,
width=138,
height=40,
locate_x=603,
locate_y=542,
command=self.on_more_button_click)
最后就是这个更多信息按钮,以及点击事件处理函数。
def on_more_button_click(self):
"""查看更多信息按钮点击事件"""
if self.method!=0 and self.basal_plant_ids[0]!=0:
self.master.children['more_information_screen'].information_show(self.method,self.basal_plant_ids)
self.master.switch_to_screen('more_information_screen')
self.method=0
self.basal_plant_ids = [0,0,0,0]
- 这段代码首先检查
self.method
和self.basal_plant_ids
是否有有效的值。self.method
应该是杂交方法的信息,而self.basal_plant_ids
是一个包含亲本植物ID的列表。 - 如果这两个值都不为0(即有效),则调用
self.master.children['more_information_screen'].information_show(self.method,self.basal_plant_ids)
方法来展示更多信息,并将界面切换到more_information_screen
。 self.master
指的是创建这个MoreInformation
实例的窗口的主窗口实例。self.master.children
是一个字典,其中包含了主窗口中所有子窗口的引用。information_show
方法是MoreInformation
类中的一个方法,用于展示杂交方法和亲本植物信息。- 重置变量:
- 在检查和可能的屏幕切换之后,代码将
self.method
设置为0,并将self.basal_plant_ids
重置为一个包含四个0的列表。 - 这一步的目的是为了清除之前的信息,以便在用户再次点击“查看更多信息”按钮时,可以显示新的信息。
- 在检查和可能的屏幕切换之后,代码将
3.2 更多信息页面
这个页面很简单,组件如下:
初始化和上述页面差不多,加载背景图片,创建返回按钮和空的描述信息图片,返回按钮的点击事件是返回杂交植物图鉴。
class MoreInformation(tk.Frame):
"""杂交植物更多信息界面,包括杂交方法和亲本植物说明"""
def __init__(self, parent):
super().__init__(parent)
self.more_background = create_background(self,mor_background_path)
self.back_button = create_button(self,
dark_image_path=back1_1_path,
light_image_path=back1_2_path,
width=85,
height=25,
locate_x=24,
locate_y=567,
command=self.on_back_button_click)
self.empty_information = self.empty_information_load()
self.pack()
def empty_information_load(self):
"""加载空的描述卡片"""
image = Image.open(emptydescribe_path)
image = image.resize((212, 308), Image.Resampling.LANCZOS)
photo = ImageTk.PhotoImage(image)
return photo
def on_back_button_click(self):
# 返回杂交植物图鉴界面
self.master.switch_to_screen('hybrid_catalog_screen')
唯一重点就是下面这个信息展示方法:
def information_show(self,method,basal_plant_ids):
"""展示杂交方法"""
self.method = image_load(560,120,more_method_path,method,".png")
label = tk.Label(self,image=self.method,bd=0)
label.place(x=160,y=82)
"""展示亲本植物信息"""
self.basal_plant_describe = [None for _ in range(4)]
self.card_frame = tk.Frame(self,bd=0)
self.card_frame.place(x=15,y=202)
for col in range(4):
if col<len(basal_plant_ids) and basal_plant_ids[col]!=0:
self.basal_plant_describe[col] = image_load(212,308,basal_describe_path,basal_plant_ids[col],'.png')
label = tk.Label(self.card_frame,image=self.basal_plant_describe[col] ,bd=0)
label.grid(row=0, column=col,sticky="NSEW")
else:
label = tk.Label(self.card_frame,image=self.empty_information,bd=0)
label.grid(row=0, column=col,sticky="NSEW")
第一部分:展示杂交方法
- 加载方法图片:使用
image_load
函数加载一个名为method
的图片,图片的大小为560x120像素,图片路径为more_method_path
,图片格式为.png
。加载后的图片被存储在self.method
变量中。 - 显示图片:创建一个
tk.Label
对象,将加载的图片显示在界面上。图片放置在坐标(160, 82)的位置。
第二部分:展示亲本植物信息
- 初始化亲本植物描述列表:创建一个长度为4的列表
self.basal_plant_describe
,用于存储每个亲本植物的描述图片。 - 创建卡片框架:在界面上创建一个
tk.Frame
,用于放置亲本植物的描述图片。这个框架位于坐标(15, 202)。 - 循环遍历亲本植物ID:通过一个循环遍历
basal_plant_ids
列表中的每个亲本植物ID。对于每个ID:- 检查ID有效性:如果ID在列表中有效(即在列表的范围内且不为0),则加载对应的亲本植物描述图片。
- 加载图片:使用
image_load
函数加载一个名为basal_plant_ids[col]
的图片,图片的大小为212x308像素,图片路径为basal_describe_path
。加载后的图片被存储在self.basal_plant_describe[col]
中。 - 显示图片:创建一个
tk.Label
对象,将加载的图片显示在卡片框架中。图片放置在网格布局的第0行和第col
列上。 - 处理无效ID:如果ID无效,则加载一个名为
self.empty_information
的图片,表示没有可用的亲本植物信息。
- 网格布局管理:使用
grid
方法将图片放置在卡片框架中,sticky="NSEW"
表示图片会填充其网格单元格的全部空间。
4. 总结
到这里,我们就实现了杂交实验室主界面、基础植物图鉴和杂交植物图鉴,以及更多信息页面。
简单的关闭和返回按钮我们可以直接使用tool.py中定义的方法,至于较难的卡片按钮矩阵,我们定义了Frame,然后双循环遍历杂交植物数据,创建按钮并绑定点击事件,并且对于杂交植物图鉴,还实现了上下页面跳转的功能,并且对页面更新也做了处理,消除了一开始的白屏卡顿。
下期预告:杂交实验、杂交历史记录页面的实现。
感谢大家支持!