10天速通Tkinter库——Day7:《植物杂交实验室》主菜单及图鉴

本篇博客我将介绍Tkinter实践项目《植物杂交实验室》中的杂交实验室主菜单、基础植物图鉴、杂交植物图鉴、杂交植物更多信息四个页面的制作。

它们作为主窗口的子页面实例,除了继承主窗口的基础设置(如图标、标题、尺寸等等)、还可以使用主窗口的属性和方法(如数据变量self.hybridizationPlants、页面跳转方法switch_to_screen等等),以及在constants.py中的常量定义和tool.py中定义的通用组件。具体实现见博客10天速通Tkinter库——Day6:项目整体框架介绍

目录

1. 杂交实验室主界面

2. 基础植物图鉴

3. 杂交植物图鉴和更多信息界面

3.1 杂交植物图鉴

3.2 更多信息页面

4. 总结


1. 杂交实验室主界面

bcc94b0d241346318e654c0902732942.png

这个界面非常简单:背景图片 + 五个按钮 + 一个框

8cd2c64220fb4e74af59fb3c73a257b0.png

首先创建一个名为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)
  1. suggestions列表包含了五条建议文本。
  2. random.randint(0, len(suggestions)-1):生成一个随机数,作为suggestions列表的索引。
  3. suggestion = suggestions[random_index]:根据随机索引选择一条建议。
  4. 创建一个Tkinter的Button组件,用于显示建议文本。这里设置了按钮的字体、颜色、大小、背景色等属性。
  5. button.place(x=145,y=492):将按钮放置在窗口的指定位置。
  6. self.after(60000, self.advice_text):设置一个定时器,60000毫秒(即一分钟)后再次调用advice_text方法。

2. 基础植物图鉴

0d597889ff4e4419816980c48fc471f1.gif

 这个界面看似内容很少,但是,按钮矩阵的实现还是稍微带点难度。

181d88c06b5d4087b103b456d1400080.png

 创建一个名为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)

类的初始化:

  1. create_background:加载一张背景图片。
  2. close_button:创建了一个关闭按钮。
  3. back_button:创建了一个返回按钮。
  4. 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))
  1. card_button_matrix 方法:这个方法用于创建植物卡片矩阵。

    • card_frame:创建了一个新框架用于放置卡片按钮。

    • 使用 grid 布局管理器来放置卡片按钮,每行有8个按钮,共6行。

    • 对于每个卡片按钮,定义了一个点击事件处理器 on_click。当卡片被点击时:

      • 播放音效(card_music.play())。
      • 显示植物的详细说明。这通过创建一个新的标签(tk.Label)并设置其图像(self.master.basal_plant_describe[row][col])来实现。
  2. 卡片按钮的创建:每个卡片按钮都是一个 tk.Button 实例,它使用了原始卡片图片,并设置了边框宽度和高亮厚度为0,以使按钮看起来更简洁。

    • 每个按钮都绑定了一个点击事件处理器 on_click,处理器接收行和列作为参数,并在按钮被点击时调用。
  3. 卡片和描述的图片是主界面初始化时加载的,使用self.master.basal_plant_describe和self.master.basal_plant_card进行调用。

3. 杂交植物图鉴和更多信息界面

05d59a22a1d9438fa919cbf21428aa8b.gif

3.1 杂交植物图鉴

这个界面复杂的点在于:

  1. 杂交植物数据是动态的
  2. 上下页需要切换

c2502b8f80c34709b1691de820aaa176.png

先来看看杂交植物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()
  1.  self.init_card_photo:这个属性用于初始化矩阵按钮背景,因为定义矩阵Frame时,有时按钮会有空缺,于是乎就使用对应位置的背景填充
  2. self.old_hybrid_plant和self.new_hybrid_plant都用于记录需要显示的植物ID。因为由于杂交实验会更新杂交植物数据,因此用来判断哪些位置更新了,只改变更新位置,减少页面刷新时的多于操作。
  3. self.plants_num记录解锁的杂交植物数量
  4. self.page用来记录页号,有0,1两页
  5. 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
  1. init_card_matrix函数加载48张按钮背景图片
  2. 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 和 initpage 用于指定加载哪个页面的数据,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 更多信息页面

这个页面很简单,组件如下:

baa6f855fb0644e4aaf86276828559cc.png

初始化和上述页面差不多,加载背景图片,创建返回按钮和空的描述信息图片,返回按钮的点击事件是返回杂交植物图鉴。

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")  

第一部分:展示杂交方法

  1. 加载方法图片:使用image_load函数加载一个名为method的图片,图片的大小为560x120像素,图片路径为more_method_path,图片格式为.png。加载后的图片被存储在self.method变量中。
  2. 显示图片:创建一个tk.Label对象,将加载的图片显示在界面上。图片放置在坐标(160, 82)的位置。

第二部分:展示亲本植物信息

  1. 初始化亲本植物描述列表:创建一个长度为4的列表self.basal_plant_describe,用于存储每个亲本植物的描述图片。
  2. 创建卡片框架:在界面上创建一个tk.Frame,用于放置亲本植物的描述图片。这个框架位于坐标(15, 202)。
  3. 循环遍历亲本植物ID:通过一个循环遍历basal_plant_ids列表中的每个亲本植物ID。对于每个ID:
    1. 检查ID有效性:如果ID在列表中有效(即在列表的范围内且不为0),则加载对应的亲本植物描述图片。
    2. 加载图片:使用image_load函数加载一个名为basal_plant_ids[col]的图片,图片的大小为212x308像素,图片路径为basal_describe_path。加载后的图片被存储在self.basal_plant_describe[col]中。
    3. 显示图片:创建一个tk.Label对象,将加载的图片显示在卡片框架中。图片放置在网格布局的第0行和第col列上。
    4. 处理无效ID:如果ID无效,则加载一个名为self.empty_information的图片,表示没有可用的亲本植物信息。
  4. 网格布局管理:使用grid方法将图片放置在卡片框架中,sticky="NSEW"表示图片会填充其网格单元格的全部空间。

4. 总结

到这里,我们就实现了杂交实验室主界面、基础植物图鉴和杂交植物图鉴,以及更多信息页面。

简单的关闭和返回按钮我们可以直接使用tool.py中定义的方法,至于较难的卡片按钮矩阵,我们定义了Frame,然后双循环遍历杂交植物数据,创建按钮并绑定点击事件,并且对于杂交植物图鉴,还实现了上下页面跳转的功能,并且对页面更新也做了处理,消除了一开始的白屏卡顿。

下期预告:杂交实验、杂交历史记录页面的实现。

感谢大家支持!

  • 13
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值