我可以排除特定控制器不参与标准选项卡遍历吗?

2 投票
3 回答
1716 浏览
提问于 2025-04-17 13:21

我做了一个自定义对话框,里面有一系列的文本控件。每个文本控件旁边都有几个按钮,方便用户快速添加特定的值。我不希望这些按钮在用户通过按Tab键切换对话框中的控件时获得焦点,因为大多数情况下,用户并不需要使用这些按钮。

有没有什么简单的方法可以让特定的控件不参与标准的Tab键切换呢?

3 个回答

0

这不是一个完美的解决方案,但有一种方法可以做到这一点,就是让你的控件在半秒后再重新获得焦点。

这样你的主窗口可以重新获得焦点,按钮仍然可以使用。我这样做是因为我希望主窗口能处理所有的按键,但同时又想在上面放一些可以用鼠标点击的按钮。

所以你需要在那个想要保持焦点的控件上绑定一个叫做 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)
0

如果你在用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));


}
3

如果你想让一个按钮在用键盘操作时不被选中,可以从 wx.lib.buttons.GenButtonwx.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.PyWindowwx.PyPanelwx.PyControl 实现了允许重写 AcceptsFocusFromKeyboard 的机制,但标准的 wxPython 控件 并不支持。不过,处理 wx.EVT_NAVIGATION_KEY 事件并在 Python 端检查 AcceptsFocusFromKeyboard,可以访问实际的 Python 对象,从而调用重写的方法。

撰写回答