Python tkinter-包装Combobox的练习
#python #tkinter

我最近通过将子类包裹在Thetkinter Combobox的定义上解决了一个小问题。在这样做时做笔记之后,我认为我也可以整理并分享我的所作所为。

做这种事情有很多教程 - 我并不是说这是特别或更好的事情。

问题

碰巧的是,我多年来完成了大部分应用程序编码的环境是提供“ ComboBox”控制又称下拉列表的环境。

但是,在那种环境中,我非常习惯将组合蛋白供给小桌子,通常是用钥匙和显示列的行。控件绑定到键,但显示显示列 - 这是通过将第一列的处置宽度设置为零来实现的。因此,Combobox仅具有显示值,但是当我阅读用户选择的内容时,我会得到密钥。大多数时候,我都使用数字键和字符串显示。当通过表提供这些订单时,可以指定订单 - 可能与显示值无关。

曾在Python中编写应用程序并使用TKINTER,这令人沮丧的是,这仅支持显示值的概念,完全是该控件作为用户选择的返回。

虽然令人讨厌,但这个问题还不够重要,无法竭尽所能解决。由于某种原因,我突然觉得自己要解决 - 因此,这是带注释的旅程。

  • 注意:我故意忽略我们可以获取所选项目的索引,而不是显示的值。在大多数情况下,我并不真正在乎显示的下拉列表中的任何位置 - 想要自由重新订购或将项目遗漏。但是,正如我们稍后将要使用的那样,一旦我们工作了如何将一个班级包装在另一个课程中,则该索引可能是有用的。

输入枚举

作为旧的Pascal程序员,我非常喜欢在编程中使用定义的类型和枚举。喜欢它们的最重要原因(尤其是作为组合)是,编译阶段将挑选任何尝试跨越类型定义分配的尝试。

但是,Python作为一种具有动态键入的解释语言并不能真正提供该好处。尽管如此,我仍然非常喜欢使用枚举,因为它使我可以在程序代码中编写符号值,并在很大程度上忽略特定值。

引用WikiBooks: Pascal Programming/Enumerations

type
    weekday = (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);

var
    startOfWeek: weekday;
begin
startOfWeek := Sunday;
end.

用于编译程序的内部使用,将用于枚举值的实际(二进制)值无关紧要。作为一种强烈键入的语言,Pascal编译器将对任何尝试为变量“ startofweek”分配非“工作日”值的尝试都会出现错误。

当然,Python完全不同,我不会详细解释 - 请参阅enum — Support for enumerations的标准Python文档 - 但可以说我发现它们足够有用。

但是,这确实对组合蛋白构成了问题 - 因为它们需要用字符串喂食才能显示,因此它们将这些字符串返回为选定的值。

为了解决这个问题,我一直在创建我的枚举,然后通过编写功能来添加到它们,以将每个枚举值表示为显示/选择字符串和相反功能,以获取具有特定字符串的枚举。一切都起作用,但看起来笨拙。

我想要的是拥有可以通过对列表的组合蛋白,以便将字符串的一半显示给用户,但我可以将枚举的一半(键)(键)恢复到该字符串。

当前方法

目前,我要做的是拥有一个绑定到Combobox属性的TK变量

因此,代码的设置部分确实:

  • 创建TK变量
  • 创建与变量结合的TK Combox控件
  • 为Combobox Control设置values
  • 通过设置界变量来设置ComboBox的默认值
    t06_t2_str_scheme = m_tkntr.StringVar() 
    t06_t2_cb_schemes = m_tkntr_ttk.Combobox( t06_t2_frame02, width = 50, textvariable = t06_t2_str_scheme )
    t06_t2_cb_schemes['values'] = cmntry.MatchScheme_GetTuple()
    t06_t2_str_scheme.set( cmntry.MatchScheme_Default_Value() )

我从某些我与枚举一起定义的支持功能中撤出 - 即:

  • MatchScheme_GetTuple()
  • MatchScheme_Default_Value()

然后,为了稍后阅读选择,我们只是get绑定变量的值

    got_selection = t06_t2_str_scheme.get()

然后,我需要调用另一个函数才能从所选字符串转换为相应的枚举。

    enum_selection = MatchScheme_EnumOfString( got_selection)

并不是说这很难,这只是小事。

我想做的事

我想能够

  • 有一个元组列表,作为我为Combobox设置的内容
  • 每个元组中的第一个值是我要回来的值
  • 每个元组中的第二个值是我要显示的值

例如。定义元组列表

    the_ListTuplePair = [ ( 1, "One" ), ( 2, "Two"), ( 3, "Three"), ( 4, "Four") ]

例如。创建Combobox对象

    t06_t2_cb_schemes = TuplePairCombobox( t06_t2_frame02, width = 50, listTuplePair = the_ListTuplePair, defaultKey = 2 )

例如。选择的集合

    the_selection = t06_t2_cb_schemes.getSelectedKey()

警告和案件

在考虑一些潜在问题时,我认为我需要处理:

  • 不应重复重复键并显示,因此未选择
  • 显示值的重复允许并迎合
  • 匹配不提供密钥的默认密钥意味着未设置默认值
  • 没有选择回报为无

实现A-使用绑定到控件的值

空气代码

提醒自己一些关于班级定义在python中的工作方式(我确实倾向于避开它们),我坐下来输入以下内容(周围有很多玩具):

class TuplePairCombobox( m_tkntr_ttk.Combobox ):
    def _process_listTuplePair( ip_listTuplePair ):
        # ensure the listTuplePair has no repeats of the keys
        t_list_keys = [] # used to detect repeats
        r_dict_valu2key = {} # use both to populate the combobox and reverse lookup after selection
        for tpl in ip_listTuplePair:
            if tpl[0] in t_list_keys :
                pass
            else
                if tpl[1] in i_dict_valu2key:
                    n = t_list_keys # number of space to pad the display string
                    i_dict_valu2key[ tpl[1] + ( " "*n ) ] = tpl[0]
                else
                    i_dict_valu2key[ tpl[1] ] = tpl[0]
                t_list_keys.append( tpl[0] )
        return r_dict_valu2key
    def __init__(self, container, p_listTuplePair, p_defaultKey, *args, **kwargs):
        self.i_StringVar = m_tkntr.StringVar() 
        i_dict_valu2key = _process_listTuplePair( p_listTuplePair )
        super().__init__(container, textvariable = self.i_StringVar, *args, **kwargs)
        self['values'] = tuple( list( i_dict_valu2key.keys() ) )
        if p_defaultKey in list( i_dict_valu2key.keys() ) :
            self.i_StringVar.set( self.i_defaultKey )
    def getSelectedKey():
        txt_selection = self.i_StringVar.get()
        r_key = i_dict_tpls[ txt_selection ]
        return r_key

似乎涵盖了大多数想法的运作方式。简短的描述可能是:

  • 处理提供的单元列表以构建词典,该字典将既充当反向查找 - 从所选的显示到元组键 - 以及在Combobox
  • 中显示的内容列表
  • 应用我有关重复,键和值的逻辑规则
  • 要处理任何重复的值,寻找冲突并在其末端添加额外的空间,当我们沿着我们沿着不同数量的空间

实际代码

这是我粘贴空气代码并修复其一些监督后所拥有的。

班上

class TuplePairCombobox( m_tkntr_ttk.Combobox ):
    def _process_listTuplePair( self, ip_listTuplePair ):
        # ensure the listTuplePair has no repeats of the keys
        t_list_keys = [] # used to detect repeats
        r_dict_valu2key = {} # use both to populate the combobox and reverse lookup after selection
        for tpl in ip_listTuplePair:
            if tpl[0] in t_list_keys :
                pass
            else :
                if tpl[1] in r_dict_valu2key:
                    n = len( t_list_keys ) # number of space to pad the display string
                    r_dict_valu2key[ tpl[1] + ( " "*n ) ] = tpl[0]
                else :
                    r_dict_valu2key[ tpl[1] ] = tpl[0]
                t_list_keys.append( tpl[0] )
        return r_dict_valu2key
    def __init__(self, container, p_listTuplePair, p_defaultKey, *args, **kwargs):
        self.i_StringVar = m_tkntr.StringVar() 
        self.i_dict_valu2key = self._process_listTuplePair( p_listTuplePair )
        super().__init__(container, textvariable = self.i_StringVar, *args, **kwargs)
        self['values'] = tuple( list( self.i_dict_valu2key.keys() ) )
        if p_defaultKey in list( self.i_dict_valu2key.keys() ) :
            self.i_StringVar.set( self.i_defaultKey )
    def getSelectedKey( self ):
        txt_selection = self.i_StringVar.get()
        if txt_selection in self.i_dict_valu2key:
            r_key = self.i_dict_valu2key[ txt_selection ]
        else : 
            r_key = None
        return r_key

物体

这是使用新功能的方式。

首先是Combobox对象的创建,并带有通常的tkinter对:创建和放置步骤。

    i_defaultKey = 3
    t02_cb_schemer = TuplePairCombobox( t02_frame50, i_listTuplePair, i_defaultKey, width = 50) 
    t02_cb_schemer.pack( side = m_tkntr.LEFT, padx=tk_padx(), pady=tk_pady() )

值获取

这是如何从Combobox收集值。

            print( t02_cb_schemer.getSelectedKey() )

元组清单

这是一个模拟示例,用于向新的TuplePairCombobox对象提供的元素列表。

    #i_listTuplePair = [ ( 1, "One" ), ( 2, "Two"), ( 3, "Three"), ( 4, "Four") ]

重复值的元组列表

仅检查我的额外边缘案例代码是否有效,这是一个替代列表,其中有两个相同的显示字符串。

    i_listTuplePair = [ ( 1, "One" ), ( 2, "Two"), ( 3, "Three"), ( 4, "Two") ]

在这种情况下,所有四个选项均在Combobox中,每个选项都可以返回一个独特的键 - 即,在其中处理了两个显示字符串。

警告:

  • 这样的机制非常简单, 对于源元组中的某些组合而言会失败。我将不得不决定是否写一些更健壮的东西。

带有重复键的元组列表

类似地,这里是一个替代列表,其中有两个相同的钥匙值。

    i_listTuplePair = [ ( 1, "One" ), ( 2, "Two"), ( 2, "Three"), ( 4, "Four") ]

在这种情况下,根据我写的逻辑,将省略密钥2的第二次出现。

方法B-使用选择的索引

在方法A中,我们刚刚围绕了组合“返回”显示值之一的问题。

但是,使用标准的TKINTER COMBOBOX,我们也可以选择取出所选值的索引。可以用来做出更好的方法吗?

空气代码

为此,我复制了完成的方法A代码,并将getSelectedKey的定义更改为使用current(),然后又一次地考虑了它需要存在的数据结构。我选择并行构建两个列表,以便相应的键和显示字符串在相同的索引号上。

为我提供了以下代码,可以植入我的程序。我逆转了“ tuplepair”的一部分变成“ pairtuple”,以便可以轻松地将用法调用进行测试。

class PairTupleCombobox( m_tkntr_ttk.Combobox ):
    def _process_listTuplePair( self, ip_listTuplePair ):
        r_list_keys = [] 
        r_list_shows = [] 
        for tpl in ip_listTuplePair:
                r_list_keys.append( tpl[0] )
                r_list_shows.append( tpl[1] )
        return r_list_keys, r_list_shows
    def __init__(self, container, p_listTuplePair, p_defaultKey, *args, **kwargs):
        self.i_list_keys, self.i_list_shows = self._process_listTuplePair( p_listTuplePair )
        super().__init__(container, textvariable = self.i_StringVar, *args, **kwargs)
        self['values'] = tuple( self.i_list_shows )
        # still need to set the default value from the nominated key
    def getSelectedKey( self ):
        i_index = self.current()
        return self.i_list_keys[ i_index ]

实际代码

这是最终代码。

class PairTupleCombobox( m_tkntr_ttk.Combobox ):
    def _process_listPairTuple( self, ip_listPairTuple ):
        r_list_keys = [] 
        r_list_shows = [] 
        for tpl in ip_listPairTuple:
            r_list_keys.append( tpl[ 0] )
            r_list_shows.append( tpl[ 1] )
        return r_list_keys, r_list_shows
    def __init__( self, container, p_listPairTuple, p_defaultKey, *args, **kwargs):
        self.i_list_keys, self.i_list_shows = self._process_listPairTuple( p_listPairTuple )
        super().__init__(container, *args, **kwargs)
        self['values'] = tuple( self.i_list_shows )
        # still need to set the default value from the nominated key
        try:
            t_default_key_index = self.i_list_keys.index( p_defaultKey ) 
            self.current( t_default_key_index )
        except:
            pass
    def getSelectedKey( self ):
        try:
            i_index = self.current()
            return self.i_list_keys[ i_index ]
        except:
            return None

主要更改是,一旦通过初始测试,我将try条款添加到:

  • 处理查找默认值的索引,并使用current作为setter
  • 句柄如果未设置默认设置并且没有选择选择

概括

作为练习,这是一个很好的组合:

  • 使自己能够返工并简化我使用Combobox Control的大多数地方
  • 重新发现的是,使用课程重新工作其他班级并不像我期望的那样棘手

我确实认为我应该为新课程找到一个更好的名称,并且/或选择其中哪一个可以长期保留。