65.9K
CodeProject 正在变化。 阅读更多。
Home

面向设计的向导控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (141投票s)

2004年9月6日

Ms-PL

11分钟阅读

viewsIcon

664437

downloadIcon

8805

专为设计时开发而设计的向导控件。

Sample screenshot

引言

市面上有很多向导控件:Wizard Control.NET Wizard control,当然还有magic library。这里是另一个,也许能在其他控件不能满足你的需求时满足。

背景

去年,我正在写一个需要向导控件的应用程序。我找了找,发现没有一个控件完全符合我的需求,于是我写了自己的。然而,在工作源代码和CVS树都丢失后,我现在不得不从头开始重写我以前已经做过的事情。这种方法的优点是我的原型真的深入研究了我可能遇到的每一个问题,我相信我留下了一个漂亮、整洁、干净的结果。

它包含三个控件,第一个Wizard处理向导的所有功能,并提供一个WizardPage集合供你编写。按下“后退”和“前进”按钮会像你期望的那样在页面之间移动(即使在设计时也是如此)。

为了提供正确的视觉效果,还提供了另外两个控件,InfoPageHeader。它们从System.Colors命名空间获取正确的颜色,并以轻量级和简单的控件渲染预期的Wizard外观。这种方法意味着GUI可以通过继承或实现完全独立的控件来定制,当然,这些控件可以在你应用程序的其他形式中提供UI,而无需使用Wizard控件。

关于向导,有一点非常重要,那就是它们应该支持单个任务并使用户轻松完成。很容易在向导中添加额外的功能,而实际上你应该将任务分解成多个向导。考虑一下,向导可以用来创建/编辑一个对象,但不能删除它。

此向导的v1.1版本增加了一个可能被误用的功能。这就是提供多个完成页面(见下文)。完成页面通常只是一个文本摘要,说明点击“完成”后将发生什么,是最后一次改变主意的机会;然而,可以在单独的页面上构建此摘要,并有一个唯一的返回路径。

使用代码

此后,我在几个应用程序中复用了这段代码;你可以创建DLL或将其复制到同一个项目中,随意选择。

如果你将代码移出当前项目,你会得到三个编译错误

The type or namespace name 'ParentControlDesigner' could not be found 
(are you missing a using directive or an assembly reference?).

只需在项目中添加对System.Design.dll的引用即可轻松修复这些错误。

设计时功能

首先,我们通过设计器来看看漂亮的部分。稍后我们会讲到实际代码、事件处理和手动调用方法。

将向导添加到窗体

ToolBox showing the three controls

编译后,您的工具箱中将添加三个用户控件。如果它们没有出现,您可能需要简单地在设计模式下打开它们中的每一个。

首先,将一个Wizard控件添加到您的窗体。下面显示的这个控件是刚刚拖到窗体上的,还没有停靠或锚定到窗体上。

请注意,首次添加后,您没有控件表面来添加控件(如控件内缺少网格所示)。

Sample form with undocked Wizard

添加页面后,将Docked属性更改为Full,或通过Wizard属性设置AnchorsSizePosition

向向导添加页面

WizardPage Collection Editor

要添加页面,请转到Pages属性并单击省略号(...)按钮。这将打开集合编辑器,可用于将页面添加到集合中。示例已预先添加了三个页面。

在撰写本文时,我意识到我尚未为控件实现AddTabRemoveTab动词。由于此解决方案中已实现WizardDesigner,因此添加动词并绑定事件应该是一项相对较小的工作,我将很快发布。

每个WizardPage都是一个重写的Panel控件,因此任何其他WindowsControl都可以直接拖放到WizardPage上。这允许您根据需要构建复杂的向导界面;但是,此控件目前缺少您所知的向导的标准外观。

定义完成页面

向导的最后一页始终是完成页面。这意味着“下一步”按钮将显示“完成”字样,点击它将返回DialogResult.OK

如果您设计的向导需要多个不同的完成页面,那么可以通过为所需的WizardPage设置IsFinishPage=true来使其他页面也成为完成页面。

添加向导外观

An InfoPage Control

剩余的三个控件现在用于为向导添加正确的外观。向导中的开始和结束页面通过使用InfoPageInfoContainer控件来处理。

InfoPage包含一个全高右侧停靠的图像、一个标题和一个描述性文本部分。它不容易支持包含的控件。主要感兴趣的属性是ImagePageTextPageTitleImage接受一个与标准系统向导的左侧图像大小相同的图像,即宽度为164像素。但是,如果您的Form比图像高,它会在底部重复。项目中包含了一个示例图像(wizBigOnlineFolder.gif),以便您修改它。PageTitle是出现在标题部分中的文本,PageText是出现在下面的文本。

InfoContainerInfoPage非常相似,但仅显示标题和图像。顾名思义,它在设计时支持包含的控件,因此允许添加任何控件组合来构建您的初始/最终页面。如果窗体是可调整大小的,我建议注意正确锚定包含的控件,并且使用BorderStyle=NoneReadOnly=trueBackground=WhiteScrollbars=VerticalRichTextBox作为标签,以显示可能很长的文本。

Header Control

Header用于任何不包含InfoPageWizardPage的顶部。同样,有趣的属性是ImageTitleDescriptionImage将在可能的情况下调整大小以最好地适应图像。Title以较大的字体显示,Description以较小的字体显示在下面。

这两个控件都会调整大小,同时尽可能准确地模仿Windows XP中使用的向导。

代码功能

本节重点介绍Wizard控件,因为其他控件实际上只是GUI占位符。

向导操作期间引发的事件

为了支持最复杂的向导,Wizard会抛出五个事件,四个来自WizardPage,一个来自wizard控件。

当有人点击“取消”按钮时,wizard控件会引发一个事件。如果您决定阻止向导关闭,请将CancelEventCancel设置为true

 ///<summary>
/// Called when the cancel button is pressed, before the form is 
/// closed. Set e.Cancel to true if 
/// you do not wish to close the wizard.
/// </summary>
public event CancelEventHandler CloseFromCancel;

然而,按下“下一步”或“上一步”会从WizardPage引发两个事件,一个关闭现有页面,另一个打开新页面。

/// <summary>
/// Event called before this page is closed when the back button is 
/// pressed. If you don't want to show the next page then set 
/// e.page to be the new page that you wish to show
/// </summary>
public event PageEventHandler CloseFromBack;
/// <summary>
/// Event called before this page is closed when the next button is 
/// pressed. If you don't want to show the previous page then set 
/// e.page to be the new page that you wish to show 
/// </summary>
public event PageEventHandler CloseFromNext;
/// <summary>
/// Event called after this page is shown when the back button is pressed.
/// </summary>
public event EventHandler ShowFromBack;
/// <summary>
/// Event called after this page is shown when the next button is pressed. 
/// </summary>
public event EventHandler ShowFromNext;

正如您所见,Closing事件传递了一个更复杂的eventArgs。Closing事件允许您覆盖在当前Page关闭后将显示的下一个PagePageEventArgs为您提供了通过索引(不建议用于安全代码)或通过传递WizardPage本身来设置下一个Page的选项。

/// <summary>
/// Arguments passed to an application when Page is 
/// closed in a wizard. The Next page to be displayed 
/// can be changed, by the application, by setting 
/// the NextPage to a wizardPage which is part of the 
/// wizard that generated this event.
/// </summary>
public class PageEventArgs : EventArgs
{
    /// <summary>
    /// Gets/Sets the wizard page that will be displayed 
    /// next. If you set this it must be to a wizardPage 
    /// from this wizard.
    /// </summary>
    public WizardPage Page [..]
    
    /// <summary>
    /// Gets the index of the page 
    /// </summary>
    public int PageIndex [..]
}

页面验证

CloseFromBack/CloseFromNext事件为开发人员提供了最后一次机会来验证WizardPage上的每个控件。理想情况下,您已经使用了ErrorProvider向用户指示了每个控件验证的问题,但在该事件期间,您可以仔细检查值,允许或拒绝移动到下一个WizardPage

private void wpLogin_CloseFromNext(object sender, Gui.Wizard.PageEventArgs e)
{
    if (<field_1_Validation> == true && <field_2_Validation> == true)
    {
        //All successful, do nothing
    }
    else
    {
       //Tell the user Why
       MessageBox.Show(“Failed Validation”);
       //Stay on the correct page
       e.Page = wpLogin;
    }
}

注意:我发现编写检查成功的逻辑比检查失败更容易阅读。您可以自由使用DeMorgan定理并反转它来检查失败,如果您愿意的话。

控制向导按钮

Wizard还能够根据您的需求启用或禁用其按钮。由于这些按钮的状态预计会随着每个页面的变化而变化,因此状态不会作为设计过程的一部分保存。它们将始终默认为启用,并且您可以在每个页面的Show事件中选择禁用它们。

/// <summary>
/// Gets/Sets the enabled state of the Next button. 
/// </summary>
[Category("Wizard")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool NextEnabled [..]
/// <summary>
/// Gets/Sets the enabled state of the Back button. 
/// </summary>
[Category("Wizard")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool BackEnabled [..]
/// <summary>
/// Gets/Sets the enabled state of the Cancel button. 
/// </summary>
[Category("Wizard")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool CancelEnabled [..]

此示例代码的第二页试图通过众所周知的许可协议页面来演示这一点。添加到窗体的唯一代码如下:

private void wizardPage2_ShowFromNext(object sender, System.EventArgs e)
{
    wizard1.NextEnabled = checkBox1.Checked;
}
private void checkBox1_CheckedChanged(object sender, System.EventArgs e)
{
    wizard1.NextEnabled = checkBox1.Checked;
}

非向导行为处理

最后,向导中最近添加了四个方法。Next()Back()分别提供了一个以编程方式转到前一页或下一页的手段。这些方法超出了向导的正常处理范围,因此,如果您可以通过上述方法实现所需的结果,我建议您这样做。

稍微危险的是NextTo(WizardPage)BackTo(WizardPage)。这些方法会跳过引发WizardPageClose事件,当然这意味着您的验证不会执行。我这样做的原因是Close事件允许您指定要转到的页面。因此,如果您首先通过调用NextTo()指示向导显示特定页面,并且调用了Closing事件并告知向导转到另一个页面,我应该转到哪个页面?我假设,作为编码员,我们足够理解,会先进行验证,然后只调用NextToBackTo。演示项目中的倒数第二页包含了一个例子。

考虑到我发出了上述警告,为什么还要将这些附加方法发布出去呢?因为它们解决了几个不寻常的情况。

执行处理页面和Next()示例

有时,您希望显示一个向导页面并让它执行处理。处理完成后,您可能希望自动切换到下一页。(我确实看到过用户等待进度控件完成,然后屏幕上显示“按下一步继续...”)。

在向导的第三页查看此示例的实际运行情况。(注意:使用计时器在向导中执行处理和接收更新不是一个很好的方法,但对于这个例子来说,它很容易编码。)

跳转向导的按钮

最后一个示例页面有几个按钮,允许您直接访问向导中的页面。99%的情况下,这可以通过在Close事件中设置Page属性来避免,我强烈建议您这样做。请注意使用NextToBackTo与其他任何导航方式的区别。

一种(不好的)用法是,当您使用向导构建一个新对象并且必须选择某个内容时,不同的选择将导致不同的页面。(这不是一件好事,因为您还必须弄清楚如何导航回正确的页面。相反,最好有一个新的窗体/向导来处理选择我们要处理的对象类型,以及另一个向导来对该对象执行操作,就像Windows 2000+中的Add Printer一样)。

关注点

使用集合编辑器

添加页面带来了许多问题。PageCollection的实现和捕获事件对于与VS.NET集合编辑器配合使用至关重要。如果您未能捕获事件,您将得到Controls,这些Controls不会被正确添加到窗体中,并且通常只在代码中有效。

在设计时检测“下一步”和“上一步”按钮

这同样带来了许多问题,并有几种解决方法,直到我查看了文章开头列出的其他替代向导背后的代码。感谢这些示例(以及许多其他示例)的作者在CodeProject上发布。

更新(v1.1)

这现在已从头重写。请查看WizardDesigner以获取完整代码,但它基本上通过重载ControlDesigner.GetHitTest来检测按钮的位置,并允许生成MouseDownMouseUp事件。

protected override bool GetHitTest(Point point)
{
    Wizard wiz = this.Control as Wizard; 
    if (wiz.btnNext.Enabled && 
      wiz.btnNext.ClientRectangle.Contains(wiz.btnNext.PointToClient(point))) 
    {
        //Next can handle that
        return true; 
    }
    if(wiz.btnBack.Enabled && 
      wiz.btnBack.ClientRectangle.Contains(wiz.btnBack.PointToClient(point)))
    {
        //Back can handle that
        return true; 
    } 
    //Nope not interested
    return false; 
}

在向导中,我们为btnBackbtnNext添加了MouseDown处理程序,这些处理程序仅在DesignMode下工作。

private void btnBack_MouseDown(object sender, 
System.Windows.Forms.MouseEventArgs e) { if (DesignMode == true) Back(); }

历史

  • v1.1 - 2004年12月15日。重写以更好地利用设计器功能。现在使用GetHitTest而不是WndProc。用户Validate验证与SharpDevelop兼容。
  • v1.0.1 - 2004年12月9日。添加了多个完成页面。未发布到CodeProject。
  • v1.0 - 2004年11月15日。认为稳定。(10,000+页面浏览量,4.54评分)。根据用户vbnetuk的建议,更新了InfoContainer,并添加了验证以防止在向导(而非页面)中放置控件。
  • v0.1b - 2004年9月7日。修正了NextNextToBackBackTo的描述,并添加了主图。
  • V0.1a - 2004年9月6日。初始发布。
© . All rights reserved.