向导的替代方案
我正在制作一个程序,完美地符合向导的概念;用户可以通过步骤来创建一个游戏角色。
不过,我发现向导的局限性让设计“优雅”的逻辑流程变得困难。例如,因为向导的所有页面都是同时初始化的,所以我不能把一个页面输入的值直接用到下一个页面。我必须在每个页面上放一个按钮,以便从前一个页面获取值,而不能简单地让字段自动填充。
我考虑过不使用向导的替代方案。我觉得最好的主意是在一个面板上放一些按钮,这些按钮可以改变另一个面板上的信息,比如使用一个分割窗口。
但是,我在wxPython中找不到如何动态改变面板的文档。目前找到的资料都比较静态,所以才会使用向导。甚至《wxPython in Action》这本书也没有提到这个。
有没有关于制作“动态面板”或更好管理向导的教程呢?
5 个回答
我觉得应该完全去掉向导功能。它们是我用过的最让人不爽的东西。
需要用到向导的应用,通常是因为用户需要点击“下一步”,但其实这可能是一个可以用更好的用户界面来解决的问题。与其弹出一个让人烦的“下一步”按钮,不如这样做:
直接显示一个页面。当用户在页面上输入信息时,可以根据输入的内容来调整页面的长度。如果你的应用需要处理一些事情才能继续,而且处理后无法撤回,那就创建一个新页面,或者禁用当前页面的某些部分。当你不再需要用户输入,或者应用完成时,可以显示一个按钮,或者启用一个已经存在的按钮。
我不是说所有的功能都要在浏览器里实现。可以简单做一个可以滚动的容器,里面放按钮和标签,呈现成一个平面的列表。
好处是:用户只需点击一个标签,你也可以把所有的处理放在填写页面的最后。
wxPython的示例中有一个“动态”向导的例子。这个向导的页面可以通过重写GetNext()和GetPrev()方法来动态显示。这展示了基本的技巧;你可以在这个基础上扩展,添加和删除页面,实时更换页面,甚至动态调整页面的顺序。
不过,这个向导类只是为了方便使用。你可以对它进行修改,或者自己实现一个新的。现在比较流行的一种风格是使用基于HTML的展示;如果你的应用程序只在Windows上运行,可以使用wxHtml控件或者IEHtmlWindow控件来模拟这种效果。
这里有一个简单的例子。这样你可以让你的“向导”像一个有限状态机一样工作,其中每个状态就是不同的页面,这些页面根据需要进行初始化。同时,页面之间的数据是可以共享的。
import wx
import wx.lib.newevent
(PageChangeEvent, EVT_PAGE_CHANGE) = wx.lib.newevent.NewEvent()
class Data:
foo = None
bar = None
class Page1(wx.Panel):
def __init__(self, parent, data):
wx.Panel.__init__(self, parent)
self.parent = parent
self.data = data
sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(sizer)
label = wx.StaticText(self, label="Page 1 - foo")
self.foo = wx.TextCtrl(self)
goto_page2 = wx.Button(self, label="Go to page 2")
for c in (label, self.foo, goto_page2):
sizer.Add(c, 0, wx.TOP, 5)
goto_page2.Bind(wx.EVT_BUTTON, self.OnPage2)
def OnPage2(self, event):
self.data.foo = self.foo.Value
wx.PostEvent(self.parent, PageChangeEvent(page=Page2))
class Page2(wx.Panel):
def __init__(self, parent, data):
wx.Panel.__init__(self, parent)
self.parent = parent
self.data = data
sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(sizer)
label = wx.StaticText(self, label="Page 2 - bar")
self.bar = wx.TextCtrl(self)
goto_finish = wx.Button(self, label="Finish")
for c in (label, self.bar, goto_finish):
sizer.Add(c, 0, wx.TOP, 5)
goto_finish.Bind(wx.EVT_BUTTON, self.OnFinish)
def OnFinish(self, event):
self.data.bar = self.bar.Value
wx.PostEvent(self.parent, PageChangeEvent(page=finish))
def finish(parent, data):
wx.MessageBox("foo = %s\nbar = %s" % (data.foo, data.bar))
wx.GetApp().ExitMainLoop()
class Test(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
self.data = Data()
self.current_page = None
self.Bind(EVT_PAGE_CHANGE, self.OnPageChange)
wx.PostEvent(self, PageChangeEvent(page=Page1))
def OnPageChange(self, event):
page = event.page(self, self.data)
if page == None:
return
if self.current_page:
self.current_page.Destroy()
self.current_page = page
page.Layout()
page.Fit()
page.Refresh()
app = wx.PySimpleApp()
app.TopWindow = Test()
app.TopWindow.Show()
app.MainLoop()