我可以排除特定控制器不参与标准选项卡遍历吗?
我做了一个自定义对话框,里面有一系列的文本控件。每个文本控件旁边都有几个按钮,方便用户快速添加特定的值。我不希望这些按钮在用户通过按Tab键切换对话框中的控件时获得焦点,因为大多数情况下,用户并不需要使用这些按钮。
有没有什么简单的方法可以让特定的控件不参与标准的Tab键切换呢?
3 个回答
这不是一个完美的解决方案,但有一种方法可以做到这一点,就是让你的控件在半秒后再重新获得焦点。
这样你的主窗口可以重新获得焦点,按钮仍然可以使用。我这样做是因为我希望主窗口能处理所有的按键,但同时又想在上面放一些可以用鼠标点击的按钮。
所以你需要在那个想要保持焦点的控件上绑定一个叫做 KILL_FOCUS 的事件,并创建一个控件列表,这些控件不能从它那里获得焦点。
首先,写一个辅助函数来获取所有的子控件:
def GetAllChildren(control):
children = []
for c in control.GetChildren():
children.append(c)
children += GetAllChildren(c)
return children
在我的情况下,我希望窗口的所有子控件都不能获得焦点。
self.not_allowed_focus = GetAllChildren(self)
self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
在我的 KILL FOCUS 处理函数中,我会在半秒后请求重新获得焦点。
def OnKillFocus(self,evt):
print "OnKillFocus"
win = evt.GetWindow()
if win in self.not_allowed_focus:
wx.CallLater(500,self.SetFocus)
如果你在用C++,这个问题有个简单的解决办法,下面会详细说明。不过在wxPython中,似乎你不能对wxWidgets的类进行特殊处理,这对我来说是个致命的问题。
你可以创建一个按钮控件的特殊版本,通过重写AcceptsFocusFromKeyboard()方法,让它返回FALSE,这样在按Tab键时就不会选中这个按钮。
http://docs.wxwidgets.org/trunk/classwx_window.html#a2370bdd3ab08e7ef3c7555c6aa8301b8
下面的C++代码运行得很好:按下Tab键时,焦点会从第一个按钮跳到第三个按钮。
class cNoTabButton : public wxButton
{
public:
cNoTabButton(wxWindow *parent,
wxWindowID id,
const wxString& label = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = 0 )
: wxButton(parent,id,label,pos,size,style)
{}
bool AcceptsFocusFromKeyboard() const {
return false;
}
};
MyFrame::MyFrame(const wxString& title)
: wxFrame(NULL, wxID_ANY, title)
{
// set the frame icon
SetIcon(wxICON(sample));
wxPanel * panel = new wxPanel(this,-1,wxPoint(0,0),wxSize(500,500));
new wxButton(panel,-1,"TabPlease",wxPoint(20,20));
new cNoTabButton(panel,-1,"NoTabThanks",wxPoint(100,20));
new wxButton(panel,-1,"OKWithMe",wxPoint(200,20));
}
如果你想让一个按钮在用键盘操作时不被选中,可以从 wx.lib.buttons.GenButton
或 wx.lib.buttons.ThemedGenButton
这两个类来创建自己的按钮。这两个类是基于 wx.PyControl
的,支持你重写 AcceptsFocusFromKeyboard()
这个方法。
class NoFocusButton(wx.lib.buttons.ThemedGenButton):
def __init__(self, parent, id=wx.ID_ANY, label=wx.EmptyString, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, validator=wx.DefaultValidator, name=wx.ButtonNameStr):
wx.lib.buttons.ThemedGenButton.__init__(self,parent,id,label,pos,size,style,validator,name)
def AcceptsFocusFromKeyboard(self):
return False # does not accept focus
如果你需要更复杂的导航规则或者控件,可以处理 wx.EVT_NAVIGATION_KEY
事件,自己管理导航。要获取可以导航的窗口列表,可以使用 self.GetChildren()
。当前被选中的窗口在 wx.WindowList
中的索引可以通过 .index(mywindow)
来获取。这样一来,每当用户按下“导航键”时,你就可以在这个列表中进行导航,并将焦点设置到下一个合适的控件上,跳过那些你不想让它获得焦点的控件。
为了让在列表中导航更简单,你可以创建一个生成器:
def CycleList(thelist,index,forward):
for unused in range(len(thelist)): # cycle through the list ONCE
if forward:
index = index+1 if index+1 < len(thelist) else 0
else:
index = index-1 if index-1 >= 0 else len(thelist)-1
yield thelist[index]
在对话框中处理 wx.EVT_NAVIGATION_KEY
事件:
self.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKey)
def OnNavigationKey(self,event):
children = self.GetChildren() # list of child windows
focused = self.FindFocus() # current focus
# avoid accessing elements that do not exist
if not focused or focused not in children:
event.Skip() # use default behavior
return
index = children.index(focused)
for child in CycleList(children,index,event.GetDirection()):
# default behavior:
if child.AcceptsFocusFromKeyboard():
child.SetFocus()
return
上面的例子模拟了默认的行为:它会在可以获得焦点的控件之间循环(跳过那些不能获得焦点的控件,比如静态文本)。你可以扩展检查,排除特定的控件,或者创建一个自定义按钮类,实现 AcceptsFocusFromKeyboard
方法并返回 False。
注意:虽然 wx.PyWindow
、wx.PyPanel
和 wx.PyControl
实现了允许重写 AcceptsFocusFromKeyboard
的机制,但标准的 wxPython 控件 并不支持。不过,处理 wx.EVT_NAVIGATION_KEY
事件并在 Python 端检查 AcceptsFocusFromKeyboard
,可以访问实际的 Python 对象,从而调用重写的方法。