前言
wxPython ,Python 的优秀的图形用户界面开发包,可以实现的功能自然不少。但是,总免不了需要自定义控件,以实现更好的外观或更丰富的功能。
这篇文章,为了界面的美观,实现了一个仅带有下划线的简洁的文本框 ConciseTextCtrl
,可以用作登录等界面。效果如下。
基础实现过程
这个控件虽然本质上是一个 BoxSizer ,一个添加了 TextCtrl 和 StaticLine 的垂直 BoxSizer 。但为了调用方法时的方便,这里的控件直接继承 TextCtrl 实现。关于继承 BoxSizer 的实现方法大抵相似,在文末会有所说明。
创建控件
# ConciseTextCtrl.py
from wx import *
class ConciseTextCtrl(TextCtrl):
def __init__(self, parent, id=ID_ANY, value=EmptyString, hint=EmptyString, style=0, name=TextCtrlNameStr);
pass
创建了一个类 ConciseTextCtrl ,参数即大部分控件的参数。这里多出的参数是 hint ,可以直接为文本框设置提示字,自然与这种自定义控件的样式风格较符合。
初始化
# ConciseTextCtrl.py
...
def __init__(...):
super(ConciseTextCtrl, self).__init__(parent, id=id, value=value, style=style | BORDER_NONE, name=name)
super 调用构造方法所传的参数基本是直接将实例化时传入的参数直接传给了 TextCtrl ,唯独 style 需作处理。
这里是获取的 style 与 BORDER_NONE 的叠加, BORDER_NONE 便是整个控件的核心之一。为了实现“简洁”的效果,首先是要把文本框设为几乎不可见的样式,即只有提示字和输入的内容。边框与背景都要作处理。背景的处理在下文的代码中将有体现,
声明实例变量
# ConciseTextCtrl.py
def __init__(...):
...
self.sizer = BoxSizer(VERTICAL)
self.staticLine = StaticLine(parent)
很简单,声明了变量 sizer 和 staticLine 。staticLine 是控件的下划线, sizer 是放置文本框和下划线的布局管理器。
设置外观
# ConciseTextCtrl.py
def __init__(...):
...
self.SetBackgroundColour(parent.GetBackgroundColour())
self.SetHint(hint)
这里便是上文所述的设置背景色。 SetHint 是设置提示字。
添加到 BoxSizer
# ConciseTextCtrl.py
def __init__(...):
...
self.sizer.Add(self, flag=EXPAND)
self.sizer.Add(self.staticLine, flag=EXPAND | TOP, border=2)
将文本框加到了 BoxSizer 里,flag为 EXPAND 。而线的 flag 除了 EXPAND ,还有一个上边距,用于将输入的字与下划线分开以保证美观。
那么到此为止,一个基础的 ConciseTextCtrl 就完成了。其目前的完整代码如下。
# ConciseTextCtrl.py
from wx import *
class ConciseTextCtrl(TextCtrl):
def __init__(self, parent, id=ID_ANY, value=EmptyString,
hint=EmptyString, style=0, name=TextCtrlNameStr):
super(ConciseTextCtrl, self).__init__(parent, id=id, value=value, style=style | BORDER_NONE, name=name)
self.sizer = BoxSizer(VERTICAL)
self.staticLine = StaticLine(parent=parent)
self.SetBackgroundColour(parent.GetBackgroundColour())
self.SetHint(hint)
self.sizer.Add(self, flag=EXPAND)
self.sizer.Add(self.staticLine, flag=EXPAND | TOP, border=2)
功能添加
因为这种简洁的文本框没有外边框,用户在输入内容(聚焦)时可能无法及时得到反馈。为解决这种文本框的弊端,加入了关于字体颜色变化的功能。代码(更改部分)如下。
# ConciseTextCtrl.py
class ConciseTextCtrl(...):
def __init__(...):
...
self.focusColour = "BLUE"
self.normalColour = "BLACK"
...
self.Bind(EVT_SET_FOCUS, handler=self.onFocus)
self.Bind(EVT_KILL_FOCUS, handler=self.onNormal)
def SetFocusColour(self, normalColour, focusColour):
self.normalColour = normalColour
self.focusColour = focusColour
def onFocus(self, event):
self.SetForegroundColour(self.focusColour)
event.Skip()
def onNormal(self, event):
self.SetForegroundColour(self.normalColour)
event.Skip()
加入了聚焦时的文本变色功能。方法 SetFocusColour 对外设置两种状态下的文本颜色。
调用
调用之前还要为 ConciseTextCtrl 加入一个方法 GetBoxSizer ,在将这个空间加入到该控件父类时使用,因为这个类本身是一个文本框,而真正要加入到窗口里是 self.sizer ,即 BoxSizer 布局管理器。代码很简单:
# ConciseTextCtrl.py
from wx import *
class ConciseTextCtrl(TextCtrl):
def __init__(self, parent, id=ID_ANY, value=EmptyString,
hint=EmptyString, style=0, name=TextCtrlNameStr):
super(ConciseTextCtrl, self).__init__(parent, id=id, value=value, style=style | BORDER_NONE, name=name)
self.sizer = BoxSizer(VERTICAL)
self.staticLine = StaticLine(parent=parent)
self.focusColour = "BLUE"
self.normalColour = "GREY"
self.SetBackgroundColour(parent.GetBackgroundColour())
self.SetHint(hint)
self.sizer.Add(self, flag=EXPAND)
self.sizer.Add(self.staticLine, flag=EXPAND | TOP, border=2)
self.Bind(EVT_SET_FOCUS, handler=self.onFocus)
self.Bind(EVT_KILL_FOCUS, handler=self.onNormal)
def GetBoxSizer(self):
return self.sizer
def SetFocusColour(self, normalColour, focusColour):
self.normalColour = normalColour
self.focusColour = focusColour
def onFocus(self, event: Event):
self.SetForegroundColour(self.focusColour)
event.Skip()
def onNormal(self, event: Event):
self.SetForegroundColour(self.normalColour)
event.Skip()
class TextCtrlFrame(Frame):
def __init__(self):
super(TextCtrlFrame, self).__init__(
parent=None,
title="简洁文本框",
size=(300, 150)
)
self.Center()
self.panel = Panel(self)
self.sizer = BoxSizer(VERTICAL)
self.textCtrl = ConciseTextCtrl(self.panel, hint="用户名")
self.sizer.Add(self.textCtrl.GetBoxSizer(), proportion=1, flag=ALIGN_CENTER | SHAPED | LEFT | RIGHT, border=50)
self.panel.SetSizer(self.sizer)
self.panel.Layout()
class MainApp(App):
def OnInit(self):
frame = TextCtrlFrame()
frame.Show()
return True
if __name__ == "__main__":
app = MainApp()
app.MainLoop()
另一种方法
这里只是略提。
这个控件也可以继承自 BoxSizer ,而设置控件(文本框)样式的时候则需要调用 GetTextCtrl 方法,但在向窗口添加时无需使用 GetBoxSizer 。代码如下。
# ConciseTextCtrl.py
from wx import *
class ConciseTextCtrl(BoxSizer):
def __init__(self, parent, id=ID_ANY, value=EmptyString,
hint=EmptyString, style=0, name=TextCtrlNameStr):
super(ConciseTextCtrl, self).__init__(VERTICAL)
self.textCtrl = TextCtrl(parent, id=id, value=value, style=style | BORDER_NONE, name=name)
self.staticLine = StaticLine(parent=parent)
self.focusColour = "BLUE"
self.normalColour = "GREY"
self.textCtrl.SetBackgroundColour(parent.GetBackgroundColour())
self.textCtrl.SetHint(hint)
self.Add(self.textCtrl, flag=EXPAND)
self.Add(self.staticLine, flag=EXPAND | TOP, border=2)
self.textCtrl.Bind(EVT_SET_FOCUS, handler=self.onFocus)
self.textCtrl.Bind(EVT_KILL_FOCUS, handler=self.onNormal)
def GetTextCtrl(self):
return self.textCtrl
def SetFocusColour(self, normalColour, focusColour):
self.normalColour = normalColour
self.focusColour = focusColour
def onFocus(self, event: Event):
self.textCtrl.SetForegroundColour(self.focusColour)
event.Skip()
def onNormal(self, event: Event):
self.textCtrl.SetForegroundColour(self.normalColour)
event.Skip()
class TextCtrlFrame(Frame):
def __init__(self):
super(TextCtrlFrame, self).__init__(
parent=None,
title="简洁文本框",
size=(300, 150)
)
self.Center()
self.panel = Panel(self)
self.sizer = BoxSizer(VERTICAL)
self.textCtrl = ConciseTextCtrl(self.panel, hint="用户名")
self.sizer.Add(self.textCtrl, proportion=1, flag=ALIGN_CENTER | SHAPED | LEFT | RIGHT, border=50)
self.panel.SetSizer(self.sizer)
self.panel.Layout()
class MainApp(App):
def OnInit(self):
frame = TextCtrlFrame()
frame.Show()
return True
if __name__ == "__main__":
app = MainApp()
app.MainLoop()
逻辑基本一样,不作过多讲解。
后记
自定义这个控件的两种方法已经介绍完毕了,其大致是这样的:
继承一个控件(可以继承控件根类 Control ,也可继承 Window 或 Object(非 object),更多的是继承与之相似的控件
↓
编写初始化 __init__ ,实现基础控件外观及功能
↓
添加功能
这篇文章中实现的文本框还有缺陷,主要在于下划线使用的是分割线 StaticLine ,无法设置颜色等,不如画出的线。这方面在 wxPython 专栏中以后讲解至画笔时可能会将该示例稍作修复。
另外,有一个方法 GetStyle 文中没有提及。由于这个文本框的 Style 包含一个本不该出现的 BORDER_NONE ,所以理应当重写,即 return 的是 self 原本的 style 位异或 BORDER_NONE 。这个重写的方法可以自行添加~