2005年08月17日

1.清空日志 
DUMP  TRANSACTION  库名  WITH  NO_LOG     

2.截断事务日志: 
BACKUP LOG 数据库名 WITH NO_LOG 

3.收缩数据库文件(如果不压缩,数据库的文件不会减小 
企业管理器–右键你要压缩的数据库–所有任务–收缩数据库–收缩文件 
–选择日志文件–在收缩方式里选择收缩至XXM,这里会给出一个允许收缩到的最小M数,直接输入这个数,确定就可以了 
–选择数据文件–在收缩方式里选择收缩至XXM,这里会给出一个允许收缩到的最小M数,直接输入这个数,确定就可以了 

http://www-128.ibm.com/developerworks/cn/rational/524_rsa/?ca=dwcn-newsletter-rational

2005年08月02日

http://msdn.microsoft.com/library/chs/default.asp?url=/library/CHS/cpref/html/frlrfsystemwindowsformsdatagridtablestyleclasstopic.asp

 在DataGrid控件的TableStyles属性
  - 新建一个DataGridTableStyle
  - 设置DataGridTableStyle的MappingName为DataSet中的Table名
  - 在DataGridTableStyle新建DataGridColumnStyle,数目与要显示的字段数目一致
  - 设置DataGridColumnStyle的HeaderText属性,设为要显示的文本
  - 设置DataGridColumnStyle的MappingName属性,设为DataTable中Column的名称

2005年07月27日

http://www.microsoft.com/china/msdn/library/langtool/vcsharp/winforms03162004.mspx 

每个开发人员现在应该下载的十种必备工具

发布日期: 7/20/2004 | 更新日期: 7/20/2004

本文自发布以来已经增加了新信息。

请参阅下面的编辑更新。

本文讨论:

用于编写单元测试的 NUnit

用于创建代码文档资料的 NDoc

用于生成解决方案的 NAnt

用于生成代码的 CodeSmith

用于监视代码的 FxCop

用于编译少量代码的 Snippet Compiler

两种不同的转换器工具:ASP.NET 版本转换器和 Visual Studio .NET 项目转换器

用于生成正则表达式的 Regulator

用于分析程序集的 .NET Reflector

本文使用了下列技术:

.NET、C# 或 Visual Basic .NET、Visual Studio .NET

除非您使用能够获得的最佳工具,否则您无法期望生成一流的应用程序。除了像 Visual Studio®.NET 这样的著名工具以外,还可以从 .NET 社区获得许多小型的、不太为人所知的工具。在本文中,我将向您介绍一些目前可以获得的、面向 .NET 开发的最佳免费工具。我将引导您完成一个有关如何使用其中每种工具的快速教程 — 一些工具在许多时候可以使您节约一分钟,而另一些工具则可能彻底改变您编写代码的方式。因为我要在本篇文章中介绍如此之多的不同工具,所以我无法详尽讨论其中每种工具,但您应该了解到有关每种工具的足够信息,以便判断哪些工具对您的项目有用。

*
本页内容
Snippet Compiler Snippet Compiler
Regulator Regulator
CodeSmith CodeSmith
生成自定义模板 生成自定义模板
NUnit NUnit
编写 NUnit 测试 编写 NUnit 测试
FxCop FxCop
Lutz Roeder 的 .NET Reflector Lutz Roeder 的 .NET Reflector
NDoc NDoc
NAnt NAnt
实际运行的 NAnt 实际运行的 NAnt
转换工具 转换工具
小结 小结


Snippet Compiler

Snippet Compiler 是一个基于 Windows® 的小型应用程序,您可以通过它来编写、编译和运行代码。如果您具有较小的代码段,并且您不希望为其创建完整的 Visual Studio .NET 项目(以及伴随该项目的所有文件),则该工具将很有用。

例如,假设我希望向您说明如何从 Microsoft?.NET 框架中启动另一个应用程序。在 Snippet Compiler 中,我将通过新建一个能够创建小型控制台应用程序的文件开始。可以在该控制台应用程序的 Main 方法内部创建代码片段,而这正是我要在这里做的事情。下面的代码片段演示了如何从 .NET 框架中创建记事本实例:

System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo.FileName= "notepad.exe";
proc.Start();
proc.WaitForExit();

当然该代码片段本身无法编译,而这正是 Snippet Compiler 的用武之地。图 1 显示了 Snippet Compiler 中的这一代码示例。


1 Snippet Compiler

要测试该代码片段,只须按 play(运行)按钮(绿色三角形),它就会在调试模式下运行。该代码片段将生成一个弹出式控制台应用程序,并且将显示记事本。当您关闭记事本时,该控制台应用程序也将关闭。

就我个人而言,我是在尝试为某位向我求助的人士创建一个小型示例时,才发现 Snippet Compiler 是如此宝贵的 — 如果不使用该工具,则我通常必须新建一个项目,确保每个部分都能编译通过,然后将代码片段发送给求助者,并删除该项目。Snippet Compiler 使得这一过程变得更加容易、更加愉快。

Snippet Compiler 由 Jeff Key 编写,并且可以从 http://www.sliver.com/dotnet/SnippetCompiler 下载。

返回页首返回页首


Regulator

Regulator 是最后一个添加到我的头等工具清单中的。它是一种很有特色的工具,能够使生成和测试正则表达式变得很容易。人们对正则表达式重新产生了兴趣,因为它们在 .NET 框架中受到很好的支持。正则表达式用来基于字符、频率和字符顺序定义字符串中的模式。它们最常见的用途是作为验证用户输入有效性的手段或者作为在较大字符串中查找字符串的方法 — 例如,在 Web 页上查找 URL 或电子邮件地址。

Regulator 使您可以输入一个正则表达式以及一些针对其运行该表达式的输入内容。这样,在应用程序中实现该正则表达式之前,您可以了解它将产生什么效果以及它将返回哪些种类的匹配项。图 2 显示了带有简单正则表达式的 Regulator。

文档中包含该正则表达式 — 在该示例中,它是 [0-9]*,应该匹配一行中任意数量的数字。右下侧的框中含有针对该正则表达式的输入,而左下侧的框显示了该正则表达式在输入内容中找到的匹配项。在这样的单独应用程序中编写和测试正则表达式,要比尝试在您的应用程序中处理它们容易得多。

Regulator 中的最佳功能之一是能够在 regexlib.com 搜索联机正则表达式库。例如,如果您在搜索框中输入字符串“phone”,您将找到 20 种以上能够匹配各种电话号码的不同的正则表达式,包括用于英国、澳大利亚的表达式以及其他许多电话号码。Regulator 由 Roy Osherove 编写,并且可以在 http://royo.is-a-geek.com/regulator 下载。

返回页首返回页首


CodeSmith

CodeSmith 是一种基于模板的代码生成工具,它使用类似于 ASP.NET 的语法来生成任意类型的代码或文本。与其他许多代码生成工具不同,CodeSmith 不要求您订阅特定的应用程序设计或体系结构。使用 CodeSmith,可以生成包括简单的强类型集合和完整应用程序在内的任何东西。

当您生成应用程序时,您经常需要重复完成某些特定的任务,例如编写数据访问代码或者生成自定义集合。CodeSmith 在这些时候特别有用,因为您可以编写模板自动完成这些任务,从而不仅提高您的工作效率,而且能够自动完成那些最为乏味的任务。CodeSmith 附带了许多模板,包括对应于所有 .NET 集合类型的模板以及用于生成存储过程的模板,但该工具的真正威力在于能够创建自定义模板。为了使您能够入门,我将快速介绍一下如何生成自定义模板。

返回页首返回页首


生成自定义模板

CodeSmith 模板只是一些可以在任意文本编辑器中创建的文本文件。它们的唯一要求是用 .cst 文件扩展名来保存它们。我将要生成的示例模板将接受一个字符串,然后基于该字符串生成一个类。创建模板的第一步是添加模板头,它可声明模板的语言、目标语言以及简要模板说明:

<%@ CodeTemplate Language="C#"
   TargetLanguage="C#"
   Description="Car Template" %>

模板的下一部分是属性声明,在这里可声明将在模板每次运行时指定的属性。就该模板而言,我要使用的唯一属性只是一个字符串,因此属性声明如下所示:

<%@ Property Name="ClassName" Type="String" Category="Context"
    Description="Class Name" %>

该属性声明将使 ClassName 属性出现在 CodeSmith 属性窗口中,以便可以在模板运行时指定它。下一步是实际生成模板主体,它非常类似于用 ASP.NET 进行编码。您可以在图 3 中查看该模板的主体。[编辑更新 — 6/16/2004:图 3 中的代码已被更新,以便对多线程操作保持安全。]

正如您所见,该模板接受字符串输入并使用该类名生成单独的类。在模板主体中,使用与 ASP.NET 中相同的起始和结束标记。在该模板中,我只是插入属性值,但您还可以在这些标记内部使用任意类型的 .NET 代码。在该模板完成之后,您就可以通过双击它或者从 CodeSmith 应用程序中打开它将其加载到 CodeSmith 中。图 4 显示了已经加载到 CodeSmith 中的该模板。

您可以看到左侧的属性正是我在该模板中声明的属性。如果我输入“SingletonClass”作为类名,并单击 Generate 按钮,则将生成图 3 的底部显示的类。

CodeSmith 使用起来相当容易,如果能够正确应用,则可以产生一些令人难以置信的结果。面向代码生成的应用程序中最常见的部分之一是数据访问层。CodeSmith 包括一个名为 SchemaExplorer 的特殊的程序集,可用来从表、存储过程或几乎任何其他 SQL Server? 对象生成模板。

CodeSmith 由 Eric J. Smith 编写,并且可以在 http://www.ericjsmith.net/codesmith 下载。

返回页首返回页首


NUnit

NUnit 是为 .NET 框架生成的开放源代码单元测试框架。NUnit 使您可以用您喜欢的语言编写测试,从而测试应用程序的特定功能。当您首次编写代码时,单元测试是一种测试代码功能的很好方法,它还提供了一种对应用程序进行回归测试的方法。NUnit 应用程序提供了一个用于编写单元测试的框架,以及一个运行这些测试和查看结果的图形界面。

返回页首返回页首


编写 NUnit 测试

作为示例,我将测试 .NET 框架中 Hashtable 类的功能,以确定是否可以添加两个对象并且随后检索这些对象。我的第一步是添加对 NUnit.Framework 程序集的引用,该程序集将赋予我对 NUnit 框架的属性和方法的访问权。接下来,我将创建一个类并用 TestFixture 属性标记它。该属性使 NUnit 可以知道该类包含 NUnit 测试:

using System;
using System.Collections;
using NUnit.Framework;

namespace NUnitExample
{
    [TestFixture]
    public class HashtableTest {
        public HashtableTest() {

        }
    }
}

下一步,我将创建一个方法并用 [Test] 属性标记它,以便 NUnit 知道该方法是一个测试。然后,我将建立一个 Hashtable 并向其添加两个值,再使用 Assert.AreEqual 方法查看我是否可以检索到与我添加到 Hashtable 的值相同的值,如下面的代码所示:

[Test]
public void HashtableAddTest()
{
    Hashtable ht = new Hashtable();

    ht.Add("Key1", "Value1");
    ht.Add("Key2", "Value2");

    Assert.AreEqual("Value1", ht["Key1"], "Wrong object returned!");
    Assert.AreEqual("Value2", ht["Key2"], "Wrong object returned!");
}

这将确认我可以首先向 Hashtable 中添加值并随后检索相应的值 — 这是一个很简单的测试,但能够表现 NUnit 的功能。存在许多测试类型以及各种 Assert 方法,可使用它们来测试代码的每个部分。

要运行该测试,我需要生成项目,在 NUnit 应用程序中打开生成的程序集,然后单击 Run 按钮。图 5 显示了结果。当我看到那个大的绿色条纹时,我有一种兴奋和头晕的感觉,因为它让我知道测试已经通过了。这个简单的示例表明 NUnit 和单元测试是多么方便和强大。由于能够编写可以保存的单元测试,并且每当您更改代码时都可以重新运行该单元测试,您不仅可以更容易地检测到代码中的缺陷,而且最终能够交付更好的应用程序。


5 NUnit

NUnit 是一个开放源代码项目,并且可以从 http://www.nunit.org 下载。还有一个优秀的 NUnit Visual Studio .NET 外接程序,它使您可以直接从 Visual Studio 中运行单元测试。您可以在 http://sourceforge.net/projects/nunitaddin 找到它。有关 NUnit 及其在测试驱动开发中的地位的详细信息,请参阅文章“Test-Driven C#: Improve the Design and Flexibility of Your Project with Extreme Programming Techniques”(MSDN ®Magazine 2004 年 4 月刊)。

返回页首返回页首


FxCop

.NET 框架非常强大,这意味着存在创建优秀应用程序的极大可能,但是也同样存在创建劣质程序的可能。FxCop 是有助于创建更好的应用程序的工具之一,它所采用的方法是:使您能够分析程序集,并使用一些不同的规则来检查它是否符合这些规则。FxCop 随附了由 Microsoft 创建的固定数量的规则,但您也可以创建并包括您自己的规则。例如,如果您决定所有的类都应该具有一个不带任何参数的默认构造函数,则可以编写一条规则,以确保程序集的每个类上都具有一个构造函数。这样,无论是谁编写该代码,您都将获得一定程度的一致性。如果您需要有关创建自定义规则的详细信息,请参阅 John Robbins 的有关该主题的 Bugslayer 专栏文章(MSDN ® Magazine 2004 年 6 月刊)。

那么,让我们观察一下实际运行的 FxCop,并且看一下它在我一直在处理的 NUnitExample 程序集中找到哪些错误。当您打开 FxCop 时,您首先需要创建一个 FxCop 项目,然后向其添加您要测试的程序集。在将该程序集添加到项目以后,就可以按 Analyze,FxCop 将分析该程序集。图 6 中显示了在该程序集中找到的错误和警告。

FxCop 在我的程序集中找到了几个问题。您可以双击某个错误以查看详细信息,包括规则说明以及在哪里可以找到更多信息。(您可以做的一件有趣的事情是在框架程序集上运行 FxCop 并查看发生了什么事情。)

FxCop 可以帮助您创建更好的、更一致的代码,但它无法补偿低劣的应用程序设计或非常简单拙劣的编程。FxCop 也不能替代对等代码检查,但是因为它可以在进行代码检查之前捕获大量错误,所以您可以花费更多时间来解决严重的问题,而不必担心命名约定。FxCop 由 Microsoft 开发,并且可以从 http://www.gotdotnet.com/team/fxcop 下载。

返回页首返回页首


Lutz Roeder 的 .NET Reflector

下一个必不可少的工具称为 .NET Reflector,它是一个类浏览器和反编译器,可以分析程序集并向您展示它的所有秘密。.NET 框架向全世界引入了可用来分析任何基于 .NET 的代码(无论它是单个类还是完整的程序集)的反射概念。反射还可以用来检索有关特定程序集中包含的各种类、方法和属性的信息。使用 .NET Reflector,您可以浏览程序集的类和方法,可以分析由这些类和方法生成的 Microsoft 中间语言 (MSIL),并且可以反编译这些类和方法并查看 C# 或 Visual Basic ®.NET 中的等价类和方法。

为了演示 .NET Reflector 的工作方式,我将加载和分析前面已经显示的 NUnitExample 程序集。图 7 显示了 .NET Reflector 中加载的该程序集。


7 NUnitExample 程序集

在 .NET Reflector 内部,有各种可用来进一步分析该程序集的工具。要查看构成某个方法的 MSIL,请单击该方法并从菜单中选择 Disassembler。

除了能够查看 MSIL 以外,您还可以通过选择 Tools 菜单下的 Decompiler 来查看该方法的 C# 形式。通过在 Languages 菜单下更改您的选择,您还可以查看该方法被反编译到 Visual Basic .NET 或 Delphi 以后的形式。以下为 .NET Reflector 生成的代码:

public void HashtableAddTest()
{
    Hashtable hashtable1;
    hashtable1 = new Hashtable();
    hashtable1.Add("Key1", "Value1");
    hashtable1.Add("Key2", "Value2");
    Assert.AreEqual("Value1", hashtable1["Key1"],
       "Wrong object returned!");
    Assert.AreEqual("Value2", hashtable1["Key2"],
       "Wrong object returned!");
}

前面的代码看起来非常像我为该方法实际编写的代码。以下为该程序集中的实际代码:

public void HashtableAddTest()
{
    Hashtable ht = new Hashtable();

    ht.Add("Key1", "Value1");
    ht.Add("Key2", "Value2");

    Assert.AreEqual("Value1", ht["Key1"],
        "Wrong object returned!");
    Assert.AreEqual("Value2", ht["Key2"],
        "Wrong object returned!");
}

尽管上述代码中存在一些小的差异,但它们在功能上是完全相同的。

虽然该示例是一种显示实际代码与反编译代码之间对比的好方法,但在我看来,它并不代表 .NET Reflector 所具有的最佳用途 — 分析 .NET 框架程序集和方法。.NET 框架提供了许多执行类似操作的不同方法。例如,如果您需要从 XML 中读取一组数据,则存在多种使用 XmlDocument、XPathNavigator 或 XmlReader 完成该工作的不同方法。通过使用 .NET Reflector,您可以查看 Microsoft 在编写数据集的 ReadXml 方法时使用了什么,或者查看他们在从配置文件读取数据时做了哪些工作。.NET Reflector 还是一个了解以下最佳实施策略的优秀方法:创建诸如 HttpHandlers 或配置处理程序之类的对象,因为您可以了解到 Microsoft 工作组实际上是如何在框架中生成这些对象的。

.NET Reflector 由 Lutz Roeder 编写,并且可以从 http://www.aisto.com/roeder/dotnet 下载。

返回页首返回页首


NDoc

编写代码文档资料几乎总是一项令人畏惧的任务。我所说的不是早期设计文档,甚至也不是更为详细的设计文档;我说的是记录类上的各个方法和属性。NDoc 工具能够使用反射来分析程序集,并使用从 C# XML 注释生成的 XML 自动为代码生成文档资料。XML 注释仅适用于 C#,但有一个名为 VBCommenter 的 Visual Studio .NET Power Toy,它能够为 Visual Basic .NET 完成类似的工作。此外,下一版本的 Visual Studio 将为更多语言支持 XML 注释。

使用 NDoc 时,您仍然在编写代码的技术文档,但您是在编写代码的过程中完成了文档编写工作(在 XML 注释中),而这更容易忍受。使用 NDoc 时,第一步是为您的程序集打开 XML 注释生成功能。右键单击该项目并选择 Properties | Configuration Properties | Build,然后在 XML Documentation File 选项中输入用于保存 XML 文件的路径。当该项目生成时,将创建一个 XML 文件,其中包含所有 XML 注释。下面是 NUnit 示例中的一个用 XML 编写了文档的方法:

/// <summary>
/// This test adds a number of values to the Hashtable collection
/// and then retrieves those values and checks if they match.
/// </summary>
[Test]
public void HashtableAddTest()
{
    //Method Body Here
}

有关该方法的 XML 文档资料将被提取并保存在 XML 文件中,如下所示:

<member name="M:NUnitExample.HashtableTest.HashtableAddTest">
  <summary>This test adds a number of values to the Hashtable collection
    and then retrieves those values and checks if they match.</summary>
</member>

NDoc 使用反射来考察您的程序集,然后读取该文档中的 XML,并且将它们进行匹配。NDoc 使用该数据来创建任意数量的不同文档格式,包括 HTML 帮助文件 (CHM)。在生成 XML 文件以后,下一步是将程序集和 XML 文件加载到 NDoc 中,以便可以对它们进行处理。通过打开 NDoc 并单击 Add 按钮,可以容易地完成该工作。

在将程序集和 XML 文件加载到 NDoc 中并且使用可用的属性范围自定义输出以后,单击 Generate 按钮将启动生成文档资料的过程。使用默认的属性,NDoc 可以生成一些非常吸引人并且实用的 .html 和 .chm 文件,从而以快速有效的方式自动完成原来非常乏味的任务。

NDoc 是一个开放源代码项目,并且可以从 http://ndoc.sourceforge.net 下载。

返回页首返回页首


NAnt

NAnt 是一个基于 .NET 的生成工具,与当前版本的 Visual Studio .NET 不同,它使得为您的项目创建生成过程变得非常容易。当您拥有大量从事单个项目的开发人员时,您不能依赖于从单个用户的座位进行生成。您也不希望必须定期手动生成该项目。您更愿意创建每天晚上运行的自动生成过程。NAnt 使您可以生成解决方案、复制文件、运行 NUnit 测试、发送电子邮件,等等。遗憾的是,NAnt 缺少漂亮的图形界面,但它的确具有可以指定应该在生成过程中完成哪些任务的控制台应用程序和 XML 文件。注意,MSBuild(属于 Visual Studio 2005 的新的生成平台)为每种健壮的生成方案进行了准备,并且由基于 XML 的项目文件以类似的方式驱动。

返回页首返回页首


实际运行的 NAnt

在该示例中,我将为前面创建的 NUnitExample 解决方案创建一个 NAnt 版本文件。首先,我需要创建一个具有 .build 扩展名的 XML 文件,将其放在我的项目的根目录中,然后向该文件的顶部添加一个 XML 声明。我需要添加到该文件的第一个标记是 project 标记:

<?xml version="1.0"?>
<project name="NUnit Example" default="build" basedir=".">
    <description>The NUnit Example Project</description>
</project>

项目标记还用于设置项目名称、默认目标以及基目录。Description 标记用于设置该项目的简短说明。

接下来,我将添加 property 标记,该标记可用于将设置存储到单个位置(随后可以从文件中的任意位置访问该位置)。在该例中,我将创建一个名为 debug 的属性,我可以随后将其设置为 true 或 false,以反映我是否要在调试配置下编译该项目。(最后,这一特定属性并未真正影响如何生成该项目;它只是您设置的一个变量,当您真正确定了如何生成该项目时将读取该变量。)

接下来,我需要创建一个 target 标记。一个项目可以包含多个可在 NAnt 运行时指定的 target。如果未指定 target,则使用默认 target(我在 project 元素中设置的 target)。在该示例中,默认 target 是 build。让我们观察一下 target 元素,它将包含大多数生成信息:

<target name="build" description="compiles the source code">
</target>

在 target 元素内,我将把 target 的名称设置为 build,并且创建有关该 target 将做哪些工作的说明。我还将创建一个 csc 元素,该元素用于指定应该传递给 csc C# 编译器的数据。让我们看一下该 csc 元素:

<csc target="library" output=".\bin\debug\NUnitExample.dll"
    debug="${debug}">
<references>
    <includes name="C:\program files\NUnit V2.1\bin\NUnit.Framework.dll"/>
</references>
    <sources>
       <includes name="HashtableTest.cs"/>
    </sources>
</csc>

首先,我必须设置该 csc 元素的 target。在该例中,我将创建一个 .dll 文件,因此我将 target 设置为 library。接下来,我必须设置 csc 元素的 output,它是将要创建 .dll 文件的位置。最后,我需要设置 debug 属性,它确定了是否在调试中编译该项目。因为我在前面创建了一个用于存储该值的属性,所以我可以使用下面的字符串来访问该属性的值:${debug}。Csc 元素还包含一些子元素。我需要创建两个元素:references 元素将告诉 NAnt 需要为该项目引用哪些程序集,sources 元素告诉 NAnt 要在生成过程中包含哪些文件。在该示例中,我引用了 NUnit.Framework.dll 程序集并包含了 HashtableTest.cs 文件。图 8 中显示了完整的生成文件。(您通常还要创建一个干净的 target,用于删除生成的文件,但为了简洁起见,我已经将其省略。)

要生成该文件,我需要转到我的项目的根目录(生成文件位于此处),然后从该位置执行 nant.exe。如果生成成功,您可以在该应用程序的 bin 目录中找到 .dll 和 .pdb 文件。尽管使用 NAnt 肯定不像在 Visual Studio 中单击 Build 那样简单,但它仍然是一种非常强大的工具,可用于开发按自动计划运行的生成过程。NAnt 还包括一些有用的功能,例如能够运行单元测试或者复制附加文件(这些功能没有受到当前 Visual Studio 生成过程的支持)。

NAnt 是一个开放源代码项目,并且可以从 http://nant.sourceforge.net 下载。

返回页首返回页首


转换工具

我已经将两个独立的工具合在一起放在标题“转换工具”下面。这两个工具都非常简单,但又可能极为有用。第一个工具是 ASP.NET 版本转换器,它可用于转换 ASP.NET(虚拟目录在它下面运行)的版本。第二个工具是 Visual Studio Converter,它可用于将项目文件从 Visual Studio .NET 2002 转换到 Visual Studio .NET 2003。

当 IIS 处理请求时,它会查看正在请求的文件的扩展名,然后基于该 Web 站点或虚拟目录的扩展名映射,将请求委派给 ISAPI 扩展或者自己处理该请求。这正是 ASP.NET 的工作方式;将为所有 ASP.NET 扩展名注册扩展名映射,并将这些扩展名映射导向 aspnet_isapi.dll。这种工作方式是完美无缺的,除非您安装了 ASP.NET 1.1 — 它会将扩展名映射升级到新版本的 aspnet_isapi.dll。当在 ASP.NET 1.0 上生成的应用程序试图用 1.1 版运行时,这会导致错误。要解决该问题,可以将所有扩展名映射重新转换到 1.0 版的 aspnet_isapi.dll,但是由于有 18 种扩展名映射,所以手动完成这一工作将很枯燥。这正是 ASP.NET 版本转换器可以发挥作用的时候。使用这一小型实用工具,可以转换任何单个 ASP.NET 应用程序所使用的 .NET 框架的版本。


9 ASP.NET 版本转换器

图 9 显示了实际运行的 ASP.NET 版本转换器。它的使用方法非常简单,只须选择相应的应用程序,然后选择您希望该应用程序使用的 .NET 框架版本。该工具随后将使用 aspnet_regiis.exe 命令行工具将该应用程序转换到所选版本的框架。随着将来版本的 ASP.NET 和 .NET 框架的发布,该工具将变得更为有用。

ASP.NET 版本转换器由 Denis Bauer 编写,并且可以从 http://www.denisbauer.com/NETTools/ASPNETVersionSwitcher.aspx 下载。

Visual Studio .NET 项目转换器(参见图 10)非常类似于 ASP.NET 版本转换器,区别在于它用于转换 Visual Studio 项目文件的版本。尽管在 .NET 框架的 1.0 版和 1.1 版之间只有很小的差异,但一旦将项目文件从 Visual Studio .NET 2002 转换到 Visual Studio .NET 2003,将无法再把它转换回去。虽然这在大多数时候可能不会成为问题(因为在 .NET 框架 1.0 版和 1.1 版之间几乎没有什么破坏性的更改),但在某些时刻您可能需要将项目转换回去。该转换器可以将任何解决方案或项目文件从 Visual Studio 7.1 (Visual Studio .NET 2003) 转换到 Visual Studio 7.0 (Visual Studio .NET 2002),并在必要时进行反向转换。


10 Visual Studio .NET 项目转换器

Visual Studio .NET 项目转换器由 Dacris Software 编写。该工具可以从 http://www.codeproject.com/macro/vsconvert.asp 下载。

返回页首返回页首


小结

本文采用走马观花的方式介绍了上述工具,但我已经试图起码向您提供足够的信息以激起您的好奇心。我相信本文已经让您在某种程度上领悟了几个免费工具,您可以立即开始使用这些工具来编写更好的项目。同时,我还要敦促您确保自己拥有所有其他可以获得的合适工具,无论是最新版本的 Visual Studio、功能强大的计算机还是免费的实用工具。拥有合适的工具将使一切变得大不相同。

James Avery 是一位使用 .NET 和其他 Microsoft 技术的顾问。他已经撰写了许多书籍和文章,他的最新著作是《ASP.NET Setup and Configuration Pocket Reference》(Microsoft Press, 2003)。您可以通过 javery@infozerk.com 向他发送电子邮件,并且在 http://www.dotavery.com/blog 阅读他的网络日记。

本文摘自 MSDN Magazine2004 年 7 月刊。

该杂志可在各地的报摊购买,也可以订阅

转到原英文页面

http://www.microsoft.com/china/msdn/library/langtool/vcsharp/winforms03162004.mspx 

使用 ClickOnce 部署 Windows 窗体应用程序

发布日期: 1/12/2005 | 更新日期: 1/12/2005

Mauro Sant’Anna
Microsoft Regional Director

摘要:查看 ClickOnce 技术、将其与其他部署技术进行比较以及展示如何在应用程序中使用该技术。

*

简介

ClickOnce 是 Visual Studio 2005 中即将包含的全新的 Windows 窗体部署技术。该技术使用智能客户端简化 Web 应用程序的安装和升级。自 .NET Framework 的第一个版本以来,Windows 窗体应用程序部署就一直可以通过 HTTP 进行,并且自那时起就一直处于发展之中。本文讨论了 Windows 窗体应用程序的优势以及引发 ClickOnce 出现的窗体应用程序的技术进展。我还将使用发布的 Visual Studio 2005 Beta 1 版展示一个简单的示例。

为什么要使用 Windows 窗体?

由于万维网的出现,大多数用户和开发人员都对 Web 应用程序比“常规的” Windows 应用程序表现出更大的兴趣。除了“酷炫因素”之外,Web 应用程序还具有一些引人入胜的特殊特性:

在全球任何具有 Internet 连接的地方,都可以访问 Web 应用程序,客户端甚至不需要运行 Windows。当您的应用程序需要常见的访问时,“纯 Web”应用程序是一种非常好的技术。

Web 应用程序易于部署和更新:只需要将应用程序文件复制到 Web 服务器的目录中,您的所有客户端可以立即开始使用新的应用程序。没有 DLL 地狱、没有纠缠不清的注册表项、没有需要注册的 COM 类,它就能很好地工作!

本文重点讨论上述第二项:部署。Web 应用程序使用的 HTTP 协议相对于传统的 Windows 应用程序部署具有多种优势。

另一方面,正如我热爱 Web 一样,很难说用户对 Web 的体验是第一流的。与 Windows 应用程序相比,Web 应用程序也存在几个不足之处:

其用户界面非常简单。在 Windows 应用程序中一些理所当然的功能(如拖放和右键单击鼠标),在 Web 应用程序中也非常难于操作甚至不能实现。

即使我们要设法实现丰富的界面技巧,也通常需要大量的客户端脚本(一种非常难编写和调试的代码)。

Web 应用程序使用大量的服务器资源、带宽以及用户的耐心,因为大部分事情都在服务器上执行,需要往返行程和等候。

打印也受到“打印屏幕技术”的限制,由于字体、页边距以及纸张大小不同,对许多操作(如分页)都几乎不能控制。

尽管其中有些问题可以通过使用插件或 ActiveX 控件得到缓解,但是反过来又可能存在一些部署问题,它们与非 Web 应用程序过去常有的部署问题相同。

如果我们将 Web 应用程序的易于分发与 Windows 应用程序的丰富客户端体验相结合,将会怎么样?好,我们可以将它们相结合。从 .NET Framework 的第一个版本起,它就允许通过 HTTP 分发 Windows 窗体应用程序,而没有出现常见的问题。

这种类型的应用程序尤其适用于不要求常见访问的 Intranet/Extranet 环境,我们假设最终用户的计算机都将安装有 Internet Explorer 和 .NET Framework。

.NET Framework 1.x: HREFing .EXEs

.NET Framework 1.0 和 1.1 版都是“开包即用”,能够通过 HTTP 部署 Windows 窗体应用程序。您只要使用 "HREF" 标记指向托管的 .EXE。Internet Explorer 和 .NET Framework 运行库可以按需下载并运行可执行文件及其所需的 DLL。这种类型的部署昵称为“hrefing EXE”。

以下是这种标记的一个示例:

<a href="MainProject.exe">Call MainProject</a>

标记非常简单,推荐参阅以下几篇文章,它们已经对此进行了讨论:

Security and Versioning Models in the Windows Forms Engine Help You Create and Deploy Smart Clients 作者:Chris Sells;也可以参阅他的 smart client Wahoo! sample

Death of the Browser? 作者: Billy Hollis

我的文章 Deploy Windows Forms on the Web

Using Windows Forms Controls in Internet Explorer

因为 .NET 程序集(.EXE 或 .DLL)是基本的部署单元,所以您要将应用程序拆分成主 .EXE 和多个 DLL。这样,如果对单个 DLL 进行简单修改,则只需要下载此 DLL。

听起来似乎很容易,但是要使其正确工作,仍然需要一些技巧:

客户端上此前必须已经安装了 .NET Framework(尽管可以从 http://msdn.microsoft.com/vstudio/downloads/tools/bootstrapper/ 下载插件,使安装 Framework 和您的应用程序变得更简单)。

您的应用程序将作为部分受信任的代码在客户端上运行。一方面,这样做非常合适,因为应用程序在沙箱中运行,而且可限制它对客户端计算机的操作。另一方面,如果需要诸如打开本地文件或调用 COM 对象等功能,则必须以某种方式在客户端上设置安全策略,这一点很重要。

默认情况下,可执行文件很有可能试图加载多个含有本地化资源的 DLL;由于当前实现中的某些问题,性能将会受到影响(尤其在较慢的 Internet 链接中)。

更新以文件到文件为基础进行;例如,无法保证确实已下载了全部 10 个更新文件;客户端可能无法摆脱“半更新的”应用程序。

只有用户在 Internet Explorer 中手动设置为“脱机工作”时,才能脱机使用应用程序;应用程序本身不能对其进行控制。

默认情况下,不能使用与程序关联的 .config 文件(单击此处了解如何使用)。

您的应用程序不会在桌面或 Start 菜单中添加快捷方式。

更新应用程序块

为了解决以上提出的一些问题,Microsoft 创建了 更新应用程序块 (UAB)。该更新块是一个库,您可以将其添加到应用程序中,这样,就可以通过 HTTP 管理应用程序块的下载。

有别于最初的 Framework 实现,它具有以下一些优势:

它作为本地应用程序运行,并且可在任何时候使用,不会对性能造成影响。

更新是事务处理,即在新版本可用之前必须成功下载所有新版本的文件。

所有应用程序文件都以清单形式列出。

它作为完全受信任的应用程序运行,您不需要调整客户端安全策略。

您的应用程序会在 Start 菜单上添加快捷方式。

另一方面,还有一些不足:

为了使用应用程序,您必须对其进行较大的修改。

由于使用 BITS 下载应用程序块,因此它不能在 Windows 98/ME 中运行,而只能在 Windows 2000 或更高版本中运行。

它作为完全受信任的本地应用程序运行,因此,它几乎忽略代码访问的安全性。

Microsoft 没有为它提供支持。

有关 UAB 的更多信息,请参阅 Jamie Cool 撰写的 .NET Application Updater Component。您也可以参阅 Gotdotnet 上的 the home of the UAB。虽然 UAB 没有获得“官方的”支持, 但是 www.gotdotnet.com 网站上有一个论坛。无论如何,UAB 带有完整的源代码,您可以对其进行修改,以修补其中的一些限制(如需要 BITS 和 Windows 2000)。

ClickOnce

很明显,UAB 只是一种过渡措施,期间 Microsoft 开发了一个最终的解决方案。此解决方案就是 ClickOnce。ClickOnce 基本上具有 UAB 的所有优势,但却几乎没有其不足,而且还外加 了一些新增功能。在我看来,ClickOnce 的一个主要优势就是它恢复了代码访问的安全性。

与 HREF EXE 相比,ClickOnce 应用程序具有如下优势:

更新是事务处理(即,要么完全执行,要么根本不执行)。

该应用程序不但可以脱机工作,而且还可以对其进行某种程度的控制;有一些 API 可使应用程序发现它是联机或脱机;它还可以控制其自身的更新过程;

它可以与 Visual Studio .NET 进行良好的集成,包括能够生成合适的额外文件和工具,帮助您找到运行应用程序所需的安全权限。

它具有一个可下载必需组件(甚至 .NET Framework 自身)的 Win32“bootstraper”可执行文件。

可以按需要或以批处理方式下载应用程序文件;

它可在 Start 菜单中添加快捷方式;

ClickOnce 是 Visual Studio 2005(以前的开发代码名为“Whidbey”)和 .NET Framework 2.0 的一个功能。让我们来看使用“Community Preview Beta 1”(Framework 2.0.40607 版)的一个示例。

一个 ClickOnce 应用程序

我们遵循以下步骤来创建一个简单的 ClickOnce 应用程序。

1.

启动 Visual Studio 2005。

2.

选择 File,然后单击 New Project

3.

选择一种语言(C# 或 Visual Basic .NET)并选定 Windows 应用程序。

4.

将项目命名为 MyClickOnceApp 并单击“确定”。

5.

向窗体中添加一个按钮,并将其 Text 属性更改为 About

6.

双击该按钮。在代码窗口中输入以下代码。

Visual Basic .NET:

     MsgBox("My First ClickOnce Application")

C#:

     MessageBox.Show("My First ClickOnce Application");

按下 F5 键运行并测试应用程序。

在 Visual Studio 2005 中,所有的 Windows 应用程序在 Project | MyClickOnceApp Properties 中都有一个 Publish 页,可控制部署详细信息:

clickonce_fig01

图 1. 配置发布设置

Publishing Location 表示将从其部署应用程序的位置。它可以是一个 Web 服务器 (HTTP) 位置(如前所示),但是,也可以是一个常规的网络路径。

Install Mode and Settings 控制多种部署详细信息,例如:

应用程序是仅支持联机使用还是也支持脱机使用。

Application Files:单个文件的安装位置。

前提条件:安装程序是否必须安装其他组件,例如 Windows Installer 2.0、.NET Framework 2.0、J# Redistributable Package、SQL Server 2005 Express、Crystal Reports 以及 Microsoft Data Access Components 2.8。

clickonce_fig02

图 2. 配置前提条件

Updates:控制应用程序应在何时检查更新以及如何向客户端提供这些更新。

clickonce_fig03

图 3. 配置更新

选项:调整详细信息,例如应用程序语言、Start 菜单快捷方式资源名称、用于 Web 部署的 HTML 页以及部署策略票据。

clickonce_fig04

图 4. 配置发布选项

Publish Version 调整应用程序版本号;每次部署时版本号可以自动增加。

Publish Wizard 允许设置不同的发布选项。该向导还可以通过 Build | Publish 菜单调用。所有的 ClickOnce 应用程序必须以加密方式进行签名;该向导请求一个现有密钥(推荐)或生成一个新密钥。

clickonce_fig05

图 5. 对应用程序进行签名

在运行一次该向导之后,就可以单击 Publish Now 发布更新。在我们的示例中将显示一个 Web 页。

clickonce_fig06

图 6. 发布的应用程序

该 Web 页包含一个脚本,它检查在安装应用程序之前必须安装哪一个额外的“前提条件”安装包。如果没有“前提条件”,则会在应用程序首次运行时安装。

在用户单击 Install 链接后,会出现多个可能要求用户干预的对话框(至少首次运行时要求用户干预):

Windows XP SP2 警告

软件许可协议

缺少发布方签名认证

部署详细信息

Visual Studio .NET 2005 将为应用程序创建一个具有多个文件和文件夹的新 Web。

clickonce_fig07

图 7. 部署文件夹

dotnetfx 文件夹包含可重新发布的 .NET Framework(目前为一个 25 MB 的可执行文件)。

默认情况下,Visual Studio 将在每次部署应用程序时增加版本号;每个版本将获得一个与增加版本号相对应的新文件夹。

.application 文件是 HTML 页 publish.htm 中显示链接的目标。它是一个包含信息(如与应用程序当前版本和当前数字签名相对应的文件夹)的 XML 文件。

Publish.htm 是一个 Web 页,不仅包含指向 .application 文件的链接,而且包含一些客户端脚本可以进行某些版本检查并显示相应的信息。例如,如果您的计算机没有 .NET Framework,Install MyClickOnceApp 将会显示不同的消息。

Setup.exe 是一个 Win32 可执行文件,不仅可以安装应用程序,而且可以按正确的次序安装必需的组件(如 .NET Framework 自身和 MDAC 2.8)。

每个应用程序文件夹包含应用程序的文件和清单文件。清单是一个 XML 文件,主要包含如下信息:

所有应用程序文件的确切身份。该身份包含文件名、版本号、区域性以及处理器体系结构(本示例中为“msil”)。

应用程序所需的所有权限。

数字签名。

运行应用程序

应用程序下载完成后,可以运行它而不用再次下载。在我们的示例中,可以通过单击指向 Web 页的链接或 Start 菜单中的快捷方式启动应用程序。在这两种情况下,根据 Application Updates 项目选项中的设置可以检查是否存在新版本。需要时可以下载新版本。

要检查更新功能,请按如下步骤操作:

1.

对应用程序进行一次可见的更改,例如更改窗体上按钮的位置。

2.

再次构建按钮并部署。

3.

运行应用程序并检查下载过程。

最后请注意:该信息是基于 Visual Studio .NET Beta 1 版的,这一点非常重要;新版本可能具有不同的功能。

比较图表
HREF .EXE UAB ClickOnce

应用程序不要求更改

X

X

应用程序隔离

X

X

完全支持

X

X

较低的系统影响

X

X

保留代码访问安全

X

X

按需文件下载

X

X

以声明方式列出的必需文件的清单

X

X

以加密方式签名的清单

X

批处理文件下载

X

X

脱机工作

(*)

X

X

事务处理安装

X

X

最佳性能

X

X

Windows 2000 或更高版本要求

X

(**)

Windows 外壳集成

X

X

对更新过程的正确控制

X

X

脱机工作和控制下载过程的 API

X

X

可选包的自动安装

X

(*) 要求用户干预

(**) 对测试版产品来说还为时过早

小结

ClickOnce 是一种功能非常强大的应用程序部署技术。它是以前所用的部署模式的自然发展,为具有丰富客户端体验的 Windows 窗体应用程序提供了可靠性、安全性、性能和灵活性。

关于作者

Mauro Sant’Anna (mas_mauro@hotmail.com) 是 Microsoft 的一名区域经理,MCSD、MCSE、开发人员和培训师。自从.NET 在奥兰多 PDC 2000 上公开发布以来,他是第一个信徒。

转到原英文页面

键盘用户界面设计指南

http://www.microsoft.com/china/msdn/library/langtool/vcsharp/winforms03162004.mspx

键盘用户界面设计指南

发布日期: 12/20/2004 | 更新日期: 12/20/2004

Microsoft Corporation

适用于: Microsoft Win32 应用程序
Microsoft Active Accessibility

摘要:本文描述设计用于 Microsoft Windows 应用程序的键盘用户界面的指南。

*
本页内容
概述 概述
键盘 UI 在可访问性方面的角色 键盘 UI 在可访问性方面的角色
键盘 UI 设计基础 键盘 UI 设计基础
设计键盘 UI 设计键盘 UI
输入焦点位置 输入焦点位置
用于键盘 UI 的 Windows 系统参数 用于键盘 UI 的 Windows 系统参数
其他资源 其他资源
Windows 快捷键 Windows 快捷键


概述

本文描述设计用于 Microsoft® Windows® 应用程序的键盘用户界面 (UI) 的指南。键盘 UI 让用户可以通过只使用键盘或结合使用鼠标(或者另一个指针设备)来浏览应用程序并操作 UI 元素。遵循这些指南将有助于设计一个使您的应用程序对残疾人来说更易于访问的键盘 UI。

返回页首返回页首


键盘 UI 在可访问性方面的角色

设计良好的键盘 UI 是软件可访问性的一个重要方面。它使盲人用户或有某种行动障碍的用户能够浏览应用程序并与其功能交互。这类用户可能不能够操作鼠标,并且可能依赖于辅助技术(例如,键盘增强工具、屏幕键盘、屏幕放大器、屏幕阅读器和语音输入工具),所有这些技术都要依靠应用程序的键盘 UI。

许多辅助技术以编程方式使用键盘 UI,即使用户没有使用标准键盘输入设备也是如此。尽管一些辅助技术可能有能够使操作更简单的自定义键盘快捷键,但是在键盘 UI 设计上(针对辅助技术以及用于可访问性的所有方面),一致性和标准化对避免使用户产生混淆是很重要的。

Windows UI 设计最大化了输入灵活性。控制面板中的辅助功能选项 (Accessibility Options) 提供了增强键盘功能的工具,如粘滞键、筛选键、切换键和鼠标键。这些工具可以帮助用户用键盘操作应用程序。但是,它们不会取代由设计良好的键盘 UI 提供的辅助功能。

返回页首返回页首


键盘 UI 设计基础

设计良好的应用程序包括两个基本要求:

即使当键盘是唯一的输入设备时它们也必须是可使用的。

它们还必须是实用而且用户友好的。

以下几节提供了在您开始设计键盘 UI 前应该熟悉的信息。

键盘 UI 设计规则

要设计一个有效的键盘 UI,请使用下列设计规则:

提供对所有功能的键盘访问,并且为键盘界面提供文档。

建议键盘能够执行与鼠标设备相同的任务。在产品手册以及联机帮助中提供键盘 UI 的文档。确保输入焦点位置(在 UI 中,输入在该点被定向)以编程方式直观地提供。

有关更多信息,请参阅输入焦点位置

保持键盘导航快速简单。

提供简单的键盘导航方法。键盘导航方法应该像鼠标导航方法一样简单易懂。为普通和重复的任务提供可编程的、可自定义的快捷方法。进行关于应用程序键盘 UI 设计的可用性研究,不使用鼠标而确保易于使用。

有关更多信息,请参阅设计键盘 UI

避免意外的键盘 UI 更改。

某个任务所需的击键不应该意外变更。例如,无论选择或者其他模式状态如何,一个命令的下划线访问键应该是相同的。

提供一个一致的键盘导航模型。

应用程序应该与基本的键盘导航一致以避免系统问题。避免输入焦点中的意外的更改。

有关更多信息,请参阅符合标准键盘导航

提供一致的键盘快捷键。

应用程序间一致的键盘 UI 帮助用户了解新应用程序并与之交互。应用程序一定不能影响系统范围内的任何快捷键(用于执行某一命令的一个键或一个键组合),如打开 Start 菜单的 Windows 徽标键 (

atg_keyboardshortcuts_11

)。应用程序还必须遵循公认的快捷键分配,例如,用于复制的 CTRL+C 和用于撤消的 CTRL+Z。

尽管如此,这并不意味着所有快捷键都需要被标准化或者应用程序必须总是被强制去支持相同的快捷键。例如,不是所有的应用程序都需要支持 CTRL+P 用于打印。

有关更多信息,请参阅分配快捷键Windows 快捷键

避免同时发生多个键输入。

除了修改键 SHIFT、CTRL、ALT 和 Windows 徽标键,应用程序不应该要求用户同时按多个键。Windows 操作系统中的粘滞键辅助功能可以通过允许用户按连续顺序按键来模拟键组合。

唯一原始设备制造商 (OEM) 功能键(如便携机键盘上的 Fn 键)是不作为组合键被支持的。

避免妨碍 Windows 支持的辅助功能选项或模拟输入设备。

应用程序不能妨碍 Windows 支持的键盘和鼠标辅助功能选项,如粘滞键、筛选键、切换键和鼠标键。来自模拟输入设备的输入必须与标准设备的输入相同。应用程序必须能够接受来自键盘或鼠标模拟设备的输入,如用一个屏幕键盘或者串行键设备。

有关更多信息,请参阅GetAsyncKeyState检索键的状态

常见键盘 UI 设计错误和限制

理想情况下,所有应用程序功能都必须可以通过键盘访问。但是,这可能不总是可行的。有时,对一个功能的键盘访问强制用户遵循难以使用的或复杂的步骤。或者要通过该键盘 UI 来实现一个功能可能是编程方式上的一种挑战。

下面的列表描述了与创建键盘 UI 相关联的问题和挑战。

对话框中不一致的导航顺序。

导航顺序必须是合乎逻辑的并且是按照语言的阅读方向的。当一个新控件添加到一个现有的对话框中时,这个顺序经常遭到破坏。在一组控件中,每个控件之间的导航必须是连续并且有序的。

输入焦点在每个单个的选项按钮上停顿。

输入焦点必须只在选定的项上,而且用户必须能够通过使用箭头键选择其他选项。

窗格和视图之间的导航不一致或丢失。

窗格和视图用于划分单个窗口以显示多个信息视图。窗格和视图之间的导航必须是合乎逻辑并且对键盘用户可用的。

自定义树控件中不一致的导航。

树控件是 Windows 中的一种导航模型,键盘模型通常通过公用控件库可用。自定义树控件必须是合乎逻辑的并且遵循标准的导航模型。

设计欠佳的自定义控件。

通过结合不同的控件并保持与现有控件相似的外观来开发一个新的控件也许更容易。必须使输入焦点既对用户直观可用又对开发人员以编程方式可用。在开发控件时,请使用 Microsoft Active Accessibility® 动态批注 API。

键盘 UI 冲突。

您可以通过使用标准键和组合键的组合以多种方法分配快捷键。但是,这会导致与为专有功能提供全局键序列的应用程序冲突,与许多辅助技术也会冲突。为避免冲突,不要使用 Windows 全局键序列或用于快捷键的常见序列。

键盘 UI 在辅助技术间变化很大,并且可能与某一应用程序的快捷键冲突。因此,只为重要的击键提供自定义快捷键,并且避免常用键。例如,在许多屏幕阅读器中,用户按 CTRL 键来停止语音评述过程。因此,您应该对最近分配的快捷键和可自定义快捷键进行可用性研究,来避免与系统和其他应用程序冲突。

用于鼠标操作的键盘 UI。

用户可以使用其他通过键盘或用另一个输入设备模拟鼠标操作的工具。但是,这些工具不会替换一个设计良好的键盘界面。例如,一个简单的拖放操作可能需要超过 40 次复杂的击键,而一个盲人用户可能会发现要用击键来执行视觉操作是困难或者不可能的。

一个需要广泛使用鼠标的应用程序可以通过使用逻辑绘图对象和控制命令提供一个有效的键盘 UI。应用程序会通过对选定对象使用诸如 RotateFlipMove 等命令提供有效且合乎逻辑的导航,而非在一个像素接一个像素的基础上操作控件。

关于常用快捷键的更多信息,请参见 Windows 快捷键

返回页首返回页首


设计键盘 UI

在您了解了设计键盘 UI 的基本规则后,您可以开始考虑用户将如何使用键盘 UI 来浏览您的应用程序并与其功能及 UI 元素交互。

符合标准键盘导航

用于创建一个设计良好的键盘界面的优秀实践是,让其模仿用户已经熟悉的界面,以及与其他常用应用程序和控件兼容的界面。下面一节描述在设计键盘 UI 时要牢记的标准键盘导航实践。

以合乎逻辑的顺序浏览

用户移动或浏览一个 UI 的元素的顺序必须合乎逻辑,并且与用于编写该 UI 的自然语言一致。在一个设计良好的 UI 中,导航顺序从最常用的控制开始并按语言被阅读的方向流动。例如,在一个用英语编写的 UI 的对话框中,导航顺序从左到右、从上到下移动。

在 Windows 应用程序中,用户通过按 TAB 键将输入焦点从一个 UI 元素移动到另一个来进行浏览。他们按 SPACEBAR 或 ENTER 键来选择当前选定的活动区域,或者激活一个控件或命令。按 SHIFT+TAB 键组合反转导航顺序,将输入焦点经由那些元素向后移动;按箭头键将输入焦点按指定的方向在一组元素内移动。关于输入焦点的更多信息,请参阅输入焦点位置

下面的图 1 阐释了通过一组控件的导航顺序。它有两个选项按钮:Default PrinterFile;以及两个命令按钮:OKCancelFile 选项按钮是一个独立控件,而文本框是一个依赖控件,这意味着只有当 File 按钮已经被选定时才可使用。

这类控件的导航顺序总是从选定的选项按钮(如 File)开始,然后按其依赖控件的顺序进行。接着它移动到到窗体中的其它控件,如 OK 命令按钮。正确地分配导航顺序以确保所有控件在正确的时间以一个合乎逻辑的序列可用是很重要的。

atg_keyboardshortcuts_01

1. 通过一组控件的导航顺序

直观导航顺序通过禁用(变灰)所有当前依赖于未选定的选项按钮被强制使用。当一个选项按钮被选定时,其所有的依赖控件跟着被启用。要避免使用户产生混淆,当一个用户选择某一有依赖控件的选项按钮时,输入焦点绝对不能自动移动到第一个依赖控件。

在 Windows 窗格间浏览

当一个窗口被划分为两个或更多个窗格时,键盘用户可以通过按 TAB 键或 SHIFT+TAB 键组合在它们之间移动。它们可以按箭头键来在一个单窗格中的一组元素内移动输入焦点。如果某一窗口有许多活动的 UI 元素,考虑分层排列每个窗格。

下列击键用于在一个窗口内的窗格之间移动:

只有在 TAB 键没有被窗口内的任何其他控件使用时,它才将输入焦点移动到活动窗格的下一个区域。

CTRL+TAB 快捷键或 F6 功能键将输入焦点移动到下一个窗格或调色板。

CTRL+F6 组合将输入焦点移动到一组相关窗口中或多文档界面 (MDI) 窗口间的下一个窗口。

始终为分层导航的快捷键提供文档,它们是应用程序专用的并且提供一个唯一的键盘界面。

在字段内浏览

通过对话框或窗体上的控件的导航应该是连续且无障碍的,而且不会导致用户更改值或接收到错误消息,这是很重要的。对于在无论何时用户离开它们时验证其内容的控件来说,这可能是困难的。在这些实例中,窗体或控件必须遵循下列约定之一:

在导航期间决不要求用户更改控件的初始值。所有在退出时验证的控件必须以有效值开始。例如,用户必须能够在不应用新值的情况下在控件间移动输入焦点,直到他们按 Submit 按钮为止。

只在控件值改变并且用户试图将输入焦点移出该控件时验证控件值。定位到(或者离开)一个字段不会更改其值。

当用户选择 OK 或提交更改的等效命令时,更改以验证字段。这种情况下,您必须允许用户取消操作或选择一个命令来恢复默认的值。

通过匹配字符导航

应用程序必须为使用匹配导航在一个项列表中移动输入焦点的用户提供反馈。在匹配导航中,用户通过键入匹配列出的项的前几个字母的字符定位一个列表。有两种方法来向用户展示匹配字符串缓冲区中的字母:

当用户将输入焦点移动到一个基于部分匹配的项时,输入焦点指示符是在匹配字符而不是整个项的周围,来展示已经键入的是什么。

一个工具提示在输入焦点附近打开以展示匹配字符串缓冲区内容。

应用程序必须提供反馈以帮助用户正确地完成匹配导航。要确保应用程序提供这种类型的反馈,请使用下列指南:

当匹配字符串缓冲被清除(在一段时间没有输入后或当用户按 BACKSPACE 键)时,输入焦点指示符围绕在整个项周围。

如果缓冲区内容没有任何项,该缓冲被清除并且 PlaySound 函数被调用。这会播放一个指示无效输入的默认声音。

用户执行另一个操作时(如单击鼠标或按一个非打印字符来离开一个控件,或者手动将输入焦点移动到另一个项),缓冲必须被清空。

关于匹配导航的更多信息,请参阅提供自动完成

与 UI 元素交互

设计良好的键盘 UI 允许用户与所有 UI 功能和元素交互。下一节描述当考虑如何为您的应用程序提供键盘访问时要牢记的功能和任务。

访问菜单和对话框

菜单和对话框是 Windows 操作系统中两个最常见和最标准化的 UI 元素。菜单将相关命令分组到列表内,对话框将重要功能分组到窗口中。要确保键盘用户能够通过按 ALT 键进入和退出菜单。为了防止菜单变得太长且难以使用,对其进行配置以便多数命令从视图看是隐藏的,除非用户要求访问完整的列表。

拆分窗口

许多应用程序支持拆分条,它允许用户将一个窗口拆分成多个窗格。确保用户能够使用键盘命令定位并调整拆分块。实现这一点的一个常见方法是提供用于添加、移动及重新定位拆分条的菜单命令。将这些命令放在 WindowView 菜单下,或者放在一个快捷菜单中。例如,在 Microsoft Word 中,用户可以通过使用 Window 菜单中的 Split 命令定位或删除拆分条。

调整元素的大小和位置

多数窗口都有允许用户调整 UI 元素和对象的位置和大小的 MoveSize 命令。这些命令位于 SystemControl 菜单中。可以使用 ALT+SPACEBAR 键组合访问 Control 菜单,这个菜单出现在标题栏的左端。它使用户能够使用键盘来执行 Control 菜单操作。这些命令也可以提供给菜单栏或对象的快捷菜单上的应用程序特定的对象。

如果 Size 命令能够以逻辑或数字方式完成,它会更加强大。例如,在 Microsoft Excel 中,用户可以使用 Format 菜单来以逻辑或数字方式更改列或行的宽度。在列表视图中,数字键盘上的快捷键 CTRL+ (+) 键调整所有列宽以装下单元格的内容。

控制复杂属性

一个设计良好的键盘界面还允许用户通过调整对象属性表上的值来操作对象。例如,图 2 展示了出现在 Microsoft Visual Basic® 窗体旁边的属性表。想要调整 OK 按钮的左边缘的用户可以使用键盘来更改属性表中的 Left 属性的值。

atg_keyboardshortcuts_02

2. Visual Basic 属性表

访问工具栏

对鼠标用户来说,工具栏是一个强大的 UI 解决方案,但是它对键盘用户通常是不可访问的。确保您的应用程序通过使用菜单、键盘快捷键或工具按钮包含相等或相似的工具栏功能。例如,打印功能可以使用工具栏上的 Print 按钮或者 CTRL+P 快捷键来完成。

Microsoft Office 有一个能够被自定义并且使键盘可访问的扩展工具栏界面。

访问选项卡控件

当对话框有大量选项或 UI 元素时,您可以使用选项卡控件通过分类来组织它们。如图 3 所示,选项卡控件可以将控件划分到几个页面,通过快捷键(如 CTRL+TAB、CTRL+PAGE UP 和 CTRL+ PAGE DOWN)来访问。通常,选项卡控件在活动 UI 元素的导航顺序内。当输入焦点在控件上时,用户可以通过使用箭头键浏览不同的元素。访问每个元素的顺序必须是合乎逻辑的,这样用户才不会感到困惑。

atg_keyboardshortcuts_03

3. 选项对话框选项卡控件

设置定时控件

键盘 UI 必须为调整计时、禁用超时或共享计时的系统范围设置提供选项。这使不能快速响应的用户能够访问这些控件。

用户可以通过控制面板设置几个不同的键盘行为,如 Keyboard PropertiesAccessibility Options。基于 Windows 的应用程序必须支持由其用户设置的键盘选项。使用 SystemParametersInfo 函数来读取设置。

有关更多信息,请参阅用于键盘 UI 的 Windows 系统参数

显示工具提示

工具提示是当用户将光标定位在按钮或 UI 控件上时出现的一个文本批注框。工具提示包含必须显示给键盘用户的重要信息。当用户将输入焦点保持在一个 UI 控件上一段时间时,使用相同的鼠标计时标准 (SPI_GETMOUSEHOVERTIME) 来使工具提示显示出来。

创建快捷键和访问键

键盘用户可以使用单个键和键组合作为快捷键来跳过菜单并执行命令。这些键被称为快捷键和访问键,即便是鼠标用户也发现使用它们比使用鼠标单击屏幕对象会更快捷并且更不易疲劳。下列指南描述如何创建快捷键和访问键。

分配快捷键

快捷键是一个单个键或者键组合,它让用户能够通过使用键盘快速访问频繁执行的操作。例如,应用程序键 (

atg_keyboardshortcuts_10

) 让用户能够显示快捷菜单来移动或复制项。如图 4 所示,当输入焦点在 Windows 资源管理器中的一个文件夹上时按这个键,将打开一个使用户能够将文件有效率地移动或发送到其他位置的快捷菜单。这与拖放鼠标的操作相似。

atg_keyboardshortcuts_04

4. Windows 资源管理器和快捷菜单

在创建快捷键时,CTRL+ 字母 组合和功能键(F1 到 F12)通常是最好的选择。例如,对 CutCopyPaste 命令的键盘替代快捷键分别是 CTRL+X、CTRL+C 和 CTRL+P。

国与国之间的键盘设计不同,所以应该避免使用不能跨不同语言使用的字符或者由系统保留的快捷键组合中的字符。建议国际应用程序中的快捷键是可自定义的,这是因为记忆关联在一些语言中可能是没有用的,比如用 CTRL+B 设置粗体。

使用下列指南来设计快捷键:

分配简单并且一致的键组合。

使快捷键可以自定义。

对表示大范围影响的操作,使用带有 CTRL 键的快捷键,如用于保存当前文档的 CTRL+S。

对扩展或补充标准快捷键操作的操作使用 SHIFT+ 组合。例如,ALT+TAB 快捷键显示正在运行的应用程序的主窗口。或者,SHIFT+ALT+TAB 键组合允许您向后浏览前面已经访问过的当前运行中的应用程序。

使用 SPACEBAR 键作为控件的默认操作,如按一个按钮控件或切换复选框控件的状态。这与单击鼠标的左键或主键是相似的。

使用 ENTER 键(如果可用)作为对话框的默认操作。

使用 ESC 键来停止或撤消一个操作。

避免将已修改字母或区分大小写的字母用作快捷键。

避免将下列字符用作快捷键:@ ?ï¿_ $ {} [] \ ~ | ^ ‘ < >

避免 ALT+ 字母 组合,这是因为它们可能与访问键冲突。此外,系统将很多特殊键组合用作专用输入,例如,ALT+~ 调用一个日语输入编辑器。

避免 CTRL+ALT 组合,这是因为在一些语言版本中系统将这个组合解释为生成字母数字字符的 ALTGR 键。

避免分配由系统保留或定义的组合,或者其他应用程序经常使用的组合。

不要将 Windows 徽标键作为组合键用于非系统级的功能。

关于快捷键的更多信息,请参阅 Windows 快捷键

选择访问键

访问键是与 ALT 键一起使用以激活控件的字母数字键。如图 5 所示,访问键是常见而高度可视的,并且显示为菜单或 UI 控件上的带下划线的字母。

atg_keyboardshortcuts_05

5. 菜单访问键

随着用户开始熟悉一个应用程序,他们更可能去使用访问键来加速常见操作。这个趋势在辅助技术用户中更为常见。例如,屏幕阅读器依次显示 UI,这样用户可以阅读或倾听以发现系统选择了什么 UI 元素。访问键在这个复杂过程中是很有用的,尤其是在一个组有很多控件的时候。

在 Microsoft Win32® 应用程序中,您可以通过在分配给访问键的字母前放置一个“&”符为公共控件分配访问键。例如,用于已经分配了 &Settings 的按钮控件的访问键是 S。在 HTML 文档或应用程序中,您可以通过指定 ACCESSKEYTABINDEX 属性指定快捷键和导航顺序。

下列设计规则帮助确保访问键提供一个固定的可访问性解决方案:

使访问键可发现。

访问键必须是用户容易找到的。为访问键选择尽可能靠近标签开头的字母 — 例如,Settings。下面是一些好的字母选择:

标签的第一或第二个单词开头的字母。

标签中与众不同的辅音或元音字母。

宽度较宽的字母,如 w、m 和大写字母。

避免使用当它们被分配为访问键时难以看清的字母。这些字母包括:

带下伸的字母,如 p、g、q 或 y。

在带下伸的字母旁边的字母。

只有一个像素宽的字母,如 i 或 l。

避免重复访问键。

使访问键分配保持唯一;重复的访问键会使用户产生混淆。如果绝对有必要,您可以分配一个重复的访问键,但是要确保它不会妨碍用户的键盘操作。一些控件允许输入焦点在相同范围内的带有重复访问键的控件之间移动,如标准菜单和列表视图控件。

如果标签中没有字符可用,您可以重命名控件或者在标签末尾附加一个用括号括起来的访问键 — 例如,Next (>)。或者,您可以省略一些分配给一些 UI 控件的访问键,这取决于命令的使用频率。

不要将访问键用于默认按钮或对话框模板中的按钮。更具体一些注意事项有:

不要将访问键分配给 OKFinishCancel 命令按钮。OKFinish 通常被分配给 ENTER 键用于对话框默认操作,而 Cancel 被分配给 ESC 键。

不要将访问键分配给对话框模板中的按钮(如 OKCancelApply 按钮)。不允许它们与特定页面上的那些控件冲突。

将这些按钮按对话框的 TAB 键顺序定位,或将默认和取消操作分配给 ENTER 和 ESC,是可以接受的解决方案。

带下划线的访问键

在 Microsoft Windows 2000 之前的 Windows 版本中,带下划线的访问键在任何时候都可见。在 Windows 2000 和以后的版本中,如果用户不使用它们,就可以将其隐藏。

如果您的应用程序正在使用标准菜单和对话框,这个行为就是自动的。但是,绘制其自己的自定义控件的应用程序(如菜单和对话框)必须适当地显式处理这一点。当您正在决定到哪里为访问键设置下划线时,请牢记下面的情形:

设置系统参数以显示访问键的下划线。

用户可以通过控制面板调整下划线的默认设置。如果系统参数指示访问键总是带有下划线的,那么应用程序将始终在访问键下面显示下划线。(默认设置隐藏下划线。)

关于这些设置的更多信息,请参阅 Platform SDK 中的 SystemParametersInfo 函数的 SPI_GETMENUUNDERLINES 或 SPI_SETMENUUNDERLINES 参数。

通过使用键盘与菜单或对话框交互。

用户可以做下面的事情:

通过按一个键组合(如 ALT、F10 或 SHIFT+F10)调用某一菜单。

通过使用鼠标调用某一菜单,但随后使用键盘在菜单间浏览。在用户通过使用键盘与某一菜单交互后,直到菜单关闭为止,所有访问键都必须带有下划线。

按一个键来激活另一个控件,如按 TAB 键时将输入焦点移动到另一个控件或按 SPACEBAR 键时选择一个选项控件。下划线必须保持显示,直到用户关闭对话框或容器。

提供可自定义的键盘 UI

要适应不同的可访问性需要,应用程序必须有一个易于自定义的键盘 UI。您可以通过在键盘 UI 中包括下列功能来提供这种可自定义能力:

自定义快捷键。

这使用户可以为频繁使用的命令分配新的快捷键。用户也可以更改默认快捷键,以便其不会与辅助技术冲突。

自定义菜单。

这些菜单允许用户添加他们自己的命令。另外,它们可以为用户提供一个选择:有多个级别的菜单。

用于自定义宏的键盘快捷键。

应用程序可以允许用户为自定义宏分配个人键盘快捷键。从而,用户可以通过使用单一击键执行多个命令。

系统范围控件(如组合框或列表视图)自动支持完整的键盘辅助功能。但是,当您在做下列工作时,确保提供自定义键盘界面:

创建自定义窗口类或控件。

改变标准窗口或控件的正常行为。

在窗口或控件(如对话框)中分配键盘导航。

将语音识别技术用于用户输入或命令。

通过使用标记语言创建控件。

将客户端脚本或插件用于用户输入或命令。

自定义键盘 UI 必须与相似的应用程序的 UI 一致并进行彻底测试。更改过的控件必须支持等同于标准控件行为的键盘行为。

定位并扩展选择

设计良好的键盘 UI 使键盘用户能够做扩展选择,如在一个列表选择两个或更多项或突出显示几段文字。扩展选择的初始点叫做定位点。输入焦点的最终点叫做终点。当用户做出扩展选择时(如通过按住 SHIFT 键并移动光标突出显示一段文字),在定位点和终点间的所有项将会处在选定状态。通常,除非按住 CTRL 键,否则所有其他项将不会被选定。

一个单个可选择控件可能需要支持 SPACEBAR、CTRL + 箭头键或者箭头键。在一个多列表视图控件中,SPACEBAR 键与修改键以及箭头键的组合是选择、输入焦点和定位点导航所需的。

如需更多信息,请参阅对话框和公共控件快捷键

线性扩展选择示例

用户可以做出的扩展选择的一种类型是线性扩展选择。Windows 资源管理器的详细视图模式是线性或单一维度中的多选列表的一个示例。图 6 展示一个由 5 个文件夹(标记为 A 到 E)组成的多选列表视图。下面的过程阐释当浏览图 6 中展示的列表时,定位点和扩展选择的行为:

1.

通过只选择文件夹 A 定位初始输入焦点。

2.

按向下箭头键。文件夹 A 变成未选定,文件夹 B 变成已选定,并且收到定位点和输入焦点。

3.

按 SHIFT+ 向下箭头键。文件夹 B 保持选定并保留定位点,同时文件夹 C 变成已选定并获得输入焦点。(定位点没有移动,这是因为用扩展选择操作选定了文件夹 C)。

4.

按 CTRL+ 向下箭头键。文件夹 B 和 C 保持选定并且 B 保留定位点。文件夹 D 获得输入焦点。(因为按了 CTRL 键,所以文件夹 D 没有被选定)。

5.

按 CTRL+ 向下箭头键。文件夹 B 和 C 保持选定,文件夹 B 保留定位点,文件夹 E 获得输入焦点。

6.

按 SHIFT+SPACEBAR 以扩展选择。文件夹 B、C、D 和 E 变成选定,文件夹 B 保留定位点,文件夹 E 保有输入焦点。

7.

按 CTRL+SPACEBAR 来取消选定文件夹 E。文件夹 B、C 和 D 保持选定,但是定位点移动到保留了输入焦点的文件夹 E。

8.

按 CTRL+ 向上箭头键两次。文件夹 C 获得输入焦点,文件夹 E 保留定位点,文件夹 B 到 D 保持选定。

9.

按 SHIFT+SPACEBAR。选择从定位点位置(未选定的文件夹 E)扩展到文件夹 C。文件夹 C、D 和 E 变为选定,但是文件夹 B 变为未选定。

列表视图是灵活的,而且与键盘 UI 和辅助技术兼容。虽然多选列表框与扩展选择列表框在视觉上没有区分,您可以考虑将它们设计成显示为所有者描述的复选框的可滚动列表(如图 6 中图像右侧所示)。复选框对键盘用户来说有时更容易浏览和选择。您可以将由公共控件(如 LVS_EX_CHECKBOXES)支持的窗口样式用于列表视图。或者您可以使用 Microsoft Active Accessibility 来包含额外的所有者描述的图形的信息。

atg_keyboardshortcuts_06

6. 多选列表

二维扩展选择的示例

用户可以做的扩展选择的另一种类型是二维扩展选择。下面的表阐释一个由 3 行 4 列组成的数组。

(1,1)

(1,2)

定位点:

按 CTRL 时,输入焦点开始。

(1,3)

(1,4)

(2,1)

(2,2)

(2,3)

Input focus is moved here using CTRL+ arrow keys.

(2,4)

(3,1)

(3,2)

(3,3)

(3,4)

下列过程阐释选择单元格(1,2),并扩展选择以包含单元格(2,3)的两种方法:

选择定位点和当前输入焦点(单元格(1,2)、(1,3)、(2,2)和(2,3)将被选定)之间的矩形区域中的所有项。在某行的右边缘没有连接到下一行的左边缘的情况下使用这种方法(比如在表网格控件中)。

选择从定位点到当前输入焦点的所有项,按逻辑顺序读取(在以英文编写的对话框中从左到右,从上到下)。单元格(1,2)、(1,3)、(1,4)、(2,1)、(2,2)和(2,3)将被选定。在右箭头键从某行的右边缘折到下一行的左边缘的情况下使用这种方法。对大多数编辑控件来说这是典型的行为,虽然一些控件可能支持与线形扩展选择示例相似的块选定

非法的多选操作

任何对创建一个不受支持的扩展选择或不连贯选择的尝试(如在一个列表的最后项之外滚动)都必须激活一个生成警告声音的错误事件。此外,当前的选择绝对不能通过操作以任何方式更改。例如,在应用程序中,如果用户在只有两项被允许的时候试图选择第三个项,就必须发生一个错误事件。

一些应用程序忽略旨在使选择扩展或不连贯的修改键。对可用性来说这是很关键的,由于残疾或者项被滚出了视图,用户不能察觉到视觉反馈,因此他们可能没有意识到选择是无意的。

要避免非法多选操作,请遵循这些指南:

在列表末端提供反馈。

当用户在一个项列表(如列表视图中的图标,或对话框中的列表控件)中导航时,该用户需要知道它们什么时候已经到达了列表的末端。

如果用户试图在列表中的最后项之外浏览,应用程序绝对不能移动输入焦点,并且必须调用 PlaySound 函数来播放通常发出信号表示无效输入的默认声音。通常,最好在停止时给以反馈。将输入焦点移回到列表中的第一个对象而没有反馈可能会有问题。例如,盲人或低视力用户可能没有意识到它们已经回到了列表的起点。

激活警告事件。

只要用户按了一个无效的键或者导航键不能移动输入焦点,应用程序必须激活警告事件。例如,当插入点在一个文本字段的左端并且用户按左箭头键试图将输入焦点移动到字段外时,应用程序必须激活警告事件。当激活该警告事件时,调用 PlaySound 函数来播放默认声音。

提供自动完成

自动完成是当用户从键盘输入一个文本字符串时,自动完成它或者建议可能匹配的文本字符串的一种功能。如果您的应用程序提供自动完成,请确保它符合下列标准:

应用程序不应该试图完成一个字符串,除非有足够的信息在可用值或预定义值中标识该字符串。

在字符串已经标识之后,字符串剩余的部分应该以一种选定的状态紧接在已经输入的部分之后显示。因此,用户键入的任何其他字符将替换由自动完成功能建议的文字。在该点,将会建议另外的文字。它也能表现得恰如在一个编辑控件中有一个部分选定的字符串一般。

检索键的状态

除非绝对必要,否则请避免使用 GetAsyncKeyState 函数检索一个键或者一个鼠标按钮的状态。GetAsyncKeyState 查询硬件驱动程序来确定物理键或按钮的状态,但是忽略任何被人为按下或被辅助功能模拟的键。

如果可能,您必须使用 GetKeyState 函数,它正确地反映任何模拟输入。不过,可以在某些情况下调用 GetAsyncKeyState,如当用户击一个键来中断一个需时较长的处理任务时。

在处理鼠标拖操作时,您必须避免用 GetAsyncKeyState 函数检测鼠标按钮何时被放开。相反,您必须使用 SetCapture 函数并等待一个键抬起消息。

如果您使用 GetAsyncKeyState 函数,那么结果可能与使用其他 Windows 函数和消息获得的结果不同。这会导致您的应用程序与系统上的其他软件行为不一致。

返回页首返回页首


输入焦点位置

大多数辅助技术需要了解输入焦点的位置。例如,屏幕阅读器用它来确定用户正在使用的文本或对象,屏幕放大实用工具用它来确定要对哪些对象取景并放大。其他实用工具,如屏幕键盘,用它来重新安排窗口以避免覆盖选定的对象。

对辅助功能来说,应用程序既将输入焦点对用户一致显示又将其对开发人员以编程方式显示是很重要的。基于 Windows 的应用程序中的标准控件对输入焦点已经有了相应的支持,而且通常不需要额外工作。但是,所有者描述的自定义控件可能需要额外的工作来公开附加信息或最新添加的行为。

在设计键盘 UI 时,关于输入焦点,请使用下列指导原则:

使输入焦点显而易见。

以一种明显、直观并且易于看到的方法使输入焦点在视觉上可区分。避免使用不明显的指示器,或者提供一个选项来用高度可视的指示器替换它们。在 Microsoft Windows XP 中,系统输入焦点矩形的大小是可调的,设置值存储在 SPI_GETFOCUSBORDERWIDTH 和 SPI_GETFOCUSBORDERHEIGHT 中。对于自定义控件,调用 DrawFocusRect 函数,或按系统参数设置的大小画线。

显示输入焦点指示器。

在窗口首次打开或变为活动时,显示输入焦点指示器。

关于这个指南的例外情况,请参阅输入焦点例外

只在活动窗口显示输入焦点。

当窗口丢失输入焦点时,应用程序必须隐藏任何输入焦点指示器。如果输入焦点到了一个屏幕外的控件,该窗口控件必须滚入一个可以显示输入焦点的视图中。

每次只显示一个输入焦点指示器。

任何时候输入焦点都只能在一个位置。如果一个窗口有单独的窗格,必须只有一个窗格有输入焦点。其他窗格中的输入焦点指示器必须隐藏。

在没有输入焦点的窗口或窗格中显示不活动的 选择通常是很重要的。但是该输入焦点指示器必须从不显示在不活动的窗口或窗格中。

输入焦点和选择之间的区别。

设计您的 UI 从而使输入焦点和选择分别显示 — 即使它们可能通常被链接起来。这允许应用程序在需要时支持多选或不连贯选择,并且能够允许应用程序在没有输入焦点的窗口或窗格中显示不活动的选择。例如,在列表视图中,用一个围绕它的虚线矩形突出显示选定的项。

以编程方式公开输入焦点

Windows 公共控件自动公开输入焦点,但是您必须使用 Microsoft Active Accessibility 来为您应用程序中的自定义控件或窗口内容公开输入焦点位置。使用下列过程来在您的应用程序中以编程方式公开输入焦点:

1.

当输入焦点移动到一个不是整个窗口的对象时,调用 NotifyWinEvent

2.

当用来查询输入焦点对象时,处理 WM_GETOBJECT 消息。

3.

支持与 Microsoft Active Accessibility IAccessible COM 界面一起显示的自定义 UI 元素的 IAccessible::get_accFocus 属性。有关更多信息,请参阅 Microsoft Active Accessibility

输入焦点例外

在下列情况下,选定的窗口不必显示输入焦点指示器:

击键应用于整个窗口区域,如当唯一有效的操作是要激活一个菜单时。

窗口第一次打开,只要按 TAB 键就会使输入焦点显示在窗口中的第一个对象上。这在下列情况中不适用:

设置了键盘首选项标志。

输入焦点正在返回一个窗口,该窗口先前在一个对象上显示了输入焦点。

按 TAB 键导致输入焦点移动到窗口中的第二个项。

输入焦点变化

有时扩展或不连续的选择让确定输入焦点位置很困难。您应该考虑输入焦点独立于选择,即便是当它们定位在相同的位置时。下列主题提供有助于阐明这个区别的示例。

文本中的插入条

当用户在文本中移动插入条时,它通常显示为一个被称为系统光标的闪动的垂直条,如图 7 所示。该光标可以被放置在屏幕上的任何位置,做成任何形状或大小,而且如果需要,可以使其不可见。当应用程序画出其自己的插入条,它必须仍不可见地移动光标,跟踪可视指示器的位置和边框。

atg_keyboardshortcuts_07

7. 带有闪动光标的文本

Windows 2000 操作系统及更高版本支持系统参数通过 SPI_GETCARETWIDTH 指定光标的宽度。用户可以通过控制面板中的 Accessibility Options 更改设置。绘制其自己插入点的应用程序必须遵循为系统指定的设置。

光标必须从基插入点向选定文本方向加宽。这个方向可能会随编辑框的方向或用户正在键入的语言而改变。在支持从右到左语言的 Microsoft Windows 和 Office 中,一个小指示器显示插入点的方向。

光标必须被调整大小并定位以覆盖屏幕元素的边框,这样屏幕放大工具就能够放大对象的任何部分。这帮助用户和辅助功能工具查找正确的标签和大小。

调整系统光标的大小和形状

1.

调用 CreateCaret 函数来设置焦点大小和形状。

2.

调用 SetCaretPos 函数来将光标移动到可视输入焦点指示器所在的位置,如突出显示的单元格、图标或按钮。

3.

如果需要,调用 ShowCaret 函数来使光标可见。应用程序可以通过不调用 ShowCaret 函数保持光标不可见。

4.

当输入焦点被移动到一个不同大小的对象时,应用程序必须调用 DestroyCaret 函数,并从 CreateCaret 函数重新开始来调整大小并定位其新的输入焦点位置。

当窗口不再是活动的或者没有可视输入焦点指示器时,应用程序必须删除可视指示器并调用 DestroyCaret 函数。

输入焦点和对象选择

当用户作出扩展选择时,选择的一个结尾是活动的或者移动的结尾。这表示最终输入焦点位置。

应用程序必须始终在选择的活动结尾显示一个可见插入点以指示输入焦点位置,如图 8 所示。

atg_keyboardshortcuts_08

8. 在选择的活动结尾显示可见插入点的文本

当用户将输入焦点移动到一个对象(如图标或带有有邻近标签的位图)时,应用程序必须定位系统插入标记或在该对象上的不可见光标以及可视指示器。

如果输入焦点是在一组可多选图形对象内,应用程序必须用突出显示指示选定的对象,以及带有虚线的焦点的对象。被选定并有输入焦点的对象必须与其他没有焦点但可能既有突出显示又有虚线属性的选定对象区分开来。要以编程方式定位输入焦点,应用程序必须定位不可见系统光标以覆盖对象。

不连贯选择通常在不连续项中被支持,且有时在一个文档中被支持。输入焦点可以被定位在一个对象上,而且可见输入焦点必须与选择明确地区分开来。选定的项可以被定位在选择内或选择外。

自定义无窗口控件上的输入焦点

当应用程序有一个没有其自己的窗口句柄的自定义控件(如无窗口的自定义命令按钮)时,输入焦点与整个控件关联。一个不可见系统光标必须完全覆盖输入焦点。如果此类控件包含多个表现为单个控件的 UI 组件,那么输入焦点就必须充分定位在单个子组件上。

如果控件有其自己的窗口句柄,可能就没有必要通过使用系统光标来标识位置了。如果控件是一个无窗口控件,则使用来自 Microsoft Active Accessibility 的工具来测试输入焦点。

有关更多信息,请参阅复合自定义控件中的输入焦点输入焦点验证

复合自定义控件中的输入焦点

复合控件(如列表框)在更大控件内的单个元素上定位输入焦点。在复合控件中,应用程序必须将输入焦点的位置显示为有输入焦点的元素的边框。即使应用程序可能认为项的集合是一个单一控件,但为了公开输入焦点,每个项必须被作为一个单独的控件元素来处理。

下拉列表是一个典型的带有编辑框和列表框的复合控件。当控件打开下拉列表时,焦点被定位在列表中突出显示的项上,如图 9 所示。

atg_keyboardshortcuts_09

9. 下拉列表框

网格控件或电子表格中的输入焦点

在电子表格或网格控件内,输入焦点通常放在一个单元格上,而不是放在在编辑栏中。粗的单元格边框指示输入焦点,而且应用程序必须将输入焦点位置显示为选定单元格上的边框。如果用户开始编辑一个单元格的内容,应用程序必须视内容文字或图形而相应地指示输入焦点。

在只对鼠标设备可用的对象上的输入焦点

虽然应用程序必须提供对所有功能的键盘访问,但是一些对象只能使用鼠标来操作。只要鼠标选择对象而且对象能接收键盘输入,应用程序必须用相应的系统输入焦点显示边框。当输入焦点被移动到可能获取未来键盘输入的另一个对象时,应用程序可以释放输入焦点。

输入焦点验证

有可用于验证输入焦点位置和大小的测试工具。在 Microsoft Active Accessibility SDK 中,Inspect Objects (Inspect.exe) 和 Accessible Event Watcher (AccEvent.exe) 可以被用来测试系统输入焦点的位置和被显示辅助技术的光标。

Microsoft 放大镜也使一个用于检查输入焦点和光标可用性的有用工具,但并不是始终都提供详细的信息。对于官方应用程序测试,建议使用在 Microsoft Active Accessibility SDK 中可用的测试工具。这些工具可以显示来自输入焦点的准确位置和事件。

关于 Inspect Objects 和 Accessible Event Watcher 测试工具的更多信息,请参阅测试工具。关于相关编程接口,请参阅 Active Accessibility 用户界面服务

返回页首返回页首


用于键盘 UI 的 Windows 系统参数

Windows 操作系统支持许多在应用程序和系统本身之间共享系统范围选项设置的系统参数。应用程序可以通过调用带有指定 SPI 值的 SystemParametersInfo 函数访问系统范围的设置。使用这些设置的应用程序必须监视 WM_SETTINGSCHANGE 消息以响应或充分地调整配置。

有许多键盘 UI — 在操作系统中可用的相关系统参数。,虽然由于大多数系统参数是通过控制面板来控制的,应用程序通常只读取那些设置,但许多系统参数值都有通过 API 的读写过程。系统参数及其相应的 API 名称和描述展示在下表中。

键盘 UI — 相关系统参数
系统参数 SPI 值 描述

键盘首选项

SPI_GETKEYBOARDPREF

获得用户键盘界面首选项的设置。

访问键下划线

SPI_GETKEYBOARDCUES

SPI_GETMENUUNDERLINES

获得访问键下划线的设置。

获得访问键下划线的设置。

焦点矩形边框

SPI_GETFOCUSBORDERHEIGHT

SPI_GETFOCUSBORDERWIDTH

获得焦点边框高度的设置。

获得焦点边框宽度的设置。

光标宽度

SPI_GETCARETWIDTH

获得系统光标宽度的设置。

Windows 辅助功能选项和键盘重复率

SPI_GETFILTERKEYS

SPI_GETMOUSEKEYS

SPI_GETSTICKYKEYS

SPI_GETTOGGLEKEYS

SPI_GETKEYBOARDDELAY

SPI_GETKEYBOARDSPEED

获得筛选键选项的设置。

获得鼠标键选项的设置。

获得粘滞键选项的设置。

获得切换键选项设置。

获得键输入的延迟时间的设置。

获得键输入重复率的设置。

工具提示

SPI_GETMOUSEHOVERTIME

获得要显示工具提示的延迟时间的设置。

SPI_GETSERIALKEYS 没有在这里列出,这是因为它只在早期的 Windows 平台(Windows 版本 9.x)中被支持。来自串行键设备的输入必须被应用程序以与接收来自标准键盘或鼠标输入设备的输入相同的方式接收。

关于键盘首选项标志

在键盘首选项标志 (SPI_GETKEYBOARDPREF) 被设置时,通常隐藏一些键盘 UI 元素或省略一些键盘机制的应用程序必须完全显示它们。该标志(由用户在控制面板中设置)忠告应用程序 — 用户对键盘的依赖胜于鼠标,所以在适当的时候必须提供额外的支持。

返回页首返回页首


其他资源

关于 UI 设计和开发的更多信息,请参阅用户界面Microsoft Windows 用户体验 (Microsoft Press;ISBN: 0-7356-0566-1)。

关于以名称和类别分类的关于辅助技术工具的更多信息,请参阅搜索辅助技术

返回页首返回页首


Windows 快捷键

正如在设计键盘 UI 一节中说明的,对应用程序来说,在使用快捷键时与其它应用程序保持一致是很重要的。本节介绍一系列在 Windows 中使用的快捷键。为了与其他基于 Windows 的应用程序一致,建议您以一种相似的方法映射快捷键。如需更多信息,请参阅应用程序通用快捷键

本节是关于常用快捷键的概述。如果您通过 Microsoft 终端服务客户端连接到 Windows,视您的配置而定,一些快捷键可能会有变化。一些键盘快捷键可能要求 Microsoft Natural® Keyboard(人体工程学键盘)或者另外的包含 Windows 徽标键 (

atg_keyboardshortcuts_10

和应用程序键 (

atg_keyboardshortcuts_10

)) 的兼容键盘。

系统范围的快捷键
atg_keyboardshortcuts_11

显示或隐藏 Start 菜单。

CTRL+ESC

显示或隐藏 Start 菜单(与

atg_keyboardshortcuts_11

相同)。

CTRL+ALT+DELETE

显示 Windows 安全屏幕或 Windows 任务管理器。

atg_keyboardshortcuts_11

+BREAK

显示 System Properties 对话框。

atg_keyboardshortcuts_11

+D

显示桌面。

atg_keyboardshortcuts_11

+B

将焦点设置在通知上。

atg_keyboardshortcuts_11

+M

最小化所有窗口。

atg_keyboardshortcuts_11

+Shift+M

恢复已经最小化的窗口。

atg_keyboardshortcuts_11

+E

用 Windows 资源管理器打开“我的电脑”。

atg_keyboardshortcuts_11

+F

搜索一个文件或文件夹。

CTRL+

atg_keyboardshortcuts_11

+F

搜索计算机。

atg_keyboardshortcuts_11

+F1

显示 Windows 帮助。

atg_keyboardshortcuts_11

+L

如果您连接到一个网络域就锁定您的计算机,或者如果您没有连接到网络域就切换用户。

atg_keyboardshortcuts_11

+R

打开 Run 对话框。

ALT+TAB(s)

在打开的窗口间切换。在按住 ALT 键时,您可以按几次 TAB 以浏览每个先前使用过的窗口的系统显示。

ALT+SHIFT+TAB(s)

与 ALT+TAB 相似,在打开的窗口间向后切换。您可以通过按住或放开 SHIFT 键在向前或向后移动之间切换。

ALT+ESC(s)

与 ALT+TAB 比较,使输入焦点按其打开的顺序顺序循环通过窗口。

ALT+SHIFT+ESC(s)

与 ALT+ESC 相似,使焦点向后循环通过窗口。您可以通过按住或放开 SHIFT 键在向前或向后移动之间切换。

PRINTSCREEN

复制屏幕图像。

ALT+PRINTSCREEN

复制当前窗口图像。

左 ALT+SHIFT/TD>

切换输入语言或键盘布局(当用户通过控制面板中的区域和语言选项安装多个键盘布局时可用并可配置)。

CTRL+SHIFT

切换键盘布局或输入语言(当用户通过控制面板中的区域和语言选项安装多个键盘布局时可用并可配置)。

CTRL 或左 ALT+SHIFT + ~,数字 (0~9),或重读符号键

输入语言的热键(当用户通过控制面板中的区域和语言选项安装多个键盘布局时可用并可配置)。

atg_keyboardshortcuts_11

+V

[语音识别]切换麦克风的侦听状态。

atg_keyboardshortcuts_11

+C

[语音识别]更正已识别的文本字符串。

atg_keyboardshortcuts_11

+T

[[语音识别]在语音听写模式之间切换。

atg_keyboardshortcuts_11

+H

[手写输入]打开或关闭手写板。

atg_keyboardshortcuts_11

+ 数字

保留给 OEM 使用。

系统范围辅助功能选项和工具快捷键

按住右 SHIFT 8 秒钟

切换筛选键开关。

左 ALT + 左 SHIFT+PRINT SCREEN

切换高对比度开关。

左 ALT + 左 SHIFT+NUM LOCK

切换鼠标键开关。

SHIFT 5 次

切换粘滞键开关。

按住 NUM LOCK 5 秒钟

切换切换键开关。

atg_keyboardshortcuts_11

+U

打开实用工具管理器。

应用程序通用快捷键

F1

显示应用程序帮助。

SHIFT+F1

显示选定控件附近的技巧帮助(区分上下文帮助)。

atg_keyboardshortcuts_10

显示选定项的快捷菜单。

SHIFT+F10

显示选定项的快捷菜单(与

atg_keyboardshortcuts_10

相同)。

CTRL+C

复制选定的项。

CTRL+X

剪切选定的项。

CTRL+V

粘贴已剪切或复制的项。

CTRL+Z

撤消上次操作。

CTRL+Y

重复上次操作。

ESC

取消当前任务。

DELETE

删除选定项。

导航和窗口控件快捷键

CTRL+F4

关闭允许您同时打开多个文档的应用程序中的活动文档。

ALT+F4

关闭活动项,或退出活动应用程序。

CTRL+TAB

移动到下一个窗格或调色板。

CTRL+SHIFT+TAB

移动到前一个窗格或调色板。

F6

移动到下一个窗格或调色板(与 CTRL+TAB 相同)。

SHIFT+F6

移动到前一个窗格或调色板(与 CTRL+SHIFT+TAB 相同)。

CTRL+F6

移动到一组相关窗口中(或 MDI 文档窗口之间)的下一个窗口。

CTRL+SHIFT+F6

移动到一组相关窗口中(或 MDI 文档窗口之间)的前一个窗口。

菜单控件快捷键

ALT+SPACE

显示活动窗口的快捷菜单。

ALT+"-" (Hyphen)

显示活动子窗口(MDI 应用程序)的快捷菜单。

ALT

激活菜单条并进入菜单模式。

F10

激活菜单条并进入菜单模式(与 ALT 相同)。

ALT+(菜单上的访问键或选定的对话框)

激活菜单条并对访问键打开相应菜单。

访问键

[菜单模式]执行相应的命令。

向下箭头

[菜单模式]打开菜单项,移动到下方的一项,或者如果焦点定位在菜单的底端,移动到菜单的顶端。

向上箭头

[菜单模式]移动到上方的一个菜单项,或者如果焦点定位在菜单的顶端,移动到底端。

右箭头

[菜单模式]打开右侧的下一个菜单,或打开一个子菜单。

左箭头

[菜单模式]打开左侧的下一个菜单,或关闭一个子菜单。

编辑框快捷键

HOME

将光标移动到行首。

END

将光标移动到行尾。

CTRL+A

全选。

CTRL + RIGHT 或左箭头

将光标移动到下一个或前一个词的开头。

CTRL + DOWN 或向上箭头

将光标移动到下一个或前一个段落的段首。

CTRL+HOME

将光标移动到文档的顶端。

CTRL+END

将光标移动到文档的结尾。

按住 SHIFT + 光标移动(箭头、HOME 或 END 键)

选择或扩展选定内容。

按住 SHIFT + CTRL + 光标移动

按单词或文本块选择或扩展选定内容。

INSERT

切换插入模式。

对话框和公共控件快捷键

从下一个表中省略用于常规导航的箭头键、PAGE UP 或 PAGE DOWN、HOME 以及 END 键。

控件类型

常规

TAB

SHIFT+TAB

在选项间向前移动。

在选项间向后移动

对话框

ALT+ 访问键

ENTER

SPACE

箭头键

选择或执行相应的命令或控件。

执行对话框的默认命令或选定控件的命令。

切换选择状态或者执行选定的选项或控件命令。

在一组控件或项中移动光标或选择。

选项卡控件

CTRL + TAB

CTRL + SHIFT+TAB

CTRL + PAGE DOWN

CTRL + PAGE UP

箭头键

在选项卡间向前移动。

在选项卡间向后移动。

在选项卡间向前移动(与 CTRL+TAB 相同)。

在选项卡间向后移动(与 CTRL+SHIFT+TAB系统)。

当焦点在控件上时,在选项卡间移动。

组合框

F4

ALT + DOWN 或 UP

任何可打印键

显示或隐藏活动列表中的项。

显示或隐藏活动列表中的项(与 F4 相同)。

将选择移动到与标题开头的前缀字母的匹配的项。有关更多信息,请参阅通过匹配字符定位

列表视图

SPACE

SHIFT+SPACE

CTRL+SPACE

箭头键

CTRL+箭头键

任何可打印键

CTRL + "+" (数字键盘)

定位项的新选择和定位点。

将选择从定位点扩展到项。

调用附加选择或取消选择并将定位点移动到选定的项。

移动焦点并移除所有选择以及先前做出的定位点。

移动焦点而不移动选择或定位点。

将选择移动到与标签开头的前缀字母匹配的项。有关更多信息,请参阅 通过匹配字符定位

调整所有列的宽度以适应其内容。

树视图

*(数字键盘)

+(数字键盘)

-(数字键盘)

右箭头

左箭头

CTRL+ 向上箭头

CTRL+向下箭头

任何可打印键

显示选定项下的所有子项。

显示在选定项正下的子项。

折叠在选定的项组正下方的项。

显示在选定项正下方的子项(与 + 相同)。

折叠选定的项组并将焦点移动到组叶根。

滚动视图而不更改选择。

滚动视图而不更改选择。

将选择移动到与标题开头的前缀字母匹配的项。如需更多信息,请参阅通过匹配字符定位

日期和时间选择器

F4

ALT+向下箭头

ALT+向上箭头

PAGE UP

PAGE DOWN

CTRL+PAGE UP

CTRL+PAGE DOWN

在控件下方显示日历。

在控件下方显示日历。

在控件下方隐藏日历。

在日历中移动到下一个月。

在日历中移动到前一个月。

在日历中移动到下一年。

在日历中移动到上一年。

复选框

SPACE

-

+

切换选定选项。

清除选项。

选择选项。

滑块

箭头键

向上翻页和向下翻页

将滑块移动到下一个值。通过选择滑块控件样式 LBS_DOWNISLEFT,可以反转方向。

将滑块移动到指定的增量中的下一个值。通过选择滑块控件样式 LBS_DOWNISLEFT,可以反转方向。

Windows 资源管理器快捷键

下个表中的快捷键只作为示例展示,并不代表所有可用快捷键。

F2

重命名选定的项。

F3

搜索一个文件或文件夹。

F4

显示地址栏列表。

F5

刷新活动窗口或重新加载活动窗口中的文档。

F6

循环通过窗口中或桌面上的屏幕元素。

ALT+右箭头

移动到下一页或查看的文件夹。

ALT+左箭头

移动到前一页或查看的文件夹。

ALT+ENTER

查看选定项目的属性。

SHIFT+DELETE

永久删除选定的项而不将该项放入回收站。

HOME

显示活动窗口的顶端。

END

显示活动窗口的底端。

BACKSPACE

查看上一级文件夹。

Microsoft Office XP 快捷键

注意在下一个表中的快捷键只作为示例展示,并不代表所有可用的快捷键。每个 Microsoft Office 应用程序的联机文档中都提供完整的快捷键列表。

INSERT

切换插入模式。

F6

在任务窗格和文档窗口间移动。

SHIFT+F6

在任务窗格和文档窗口间向后移动。

CTRL+F6

当打开一个以上窗口时,切换到下一个窗口。

CTRL+SHIFT+F6

当打开一个以上窗口时,向后切换到下一个窗口。

CTRL+TAB

在菜单模式中在菜单和工具条之间移动焦点。

F1

显示助手批注框(如果打开了 Office 助手的话)。

ALT+SHIFT+F10

显示输入焦点附近的智能标记的菜单或消息。

CTRL + O

打开 Open 对话框。

CTRL + N

打开一个新的空白文档。

CTRL + F

打开 Find 对话框。

CTRL + P

打开 Print 对话框。

CTRL + S

保存当前有输入焦点的文档。

CTRL + A

全选。

使用自定义验证组件库扩展 Windows 窗体

http://www.microsoft.com/china/msdn/library/langtool/vcsharp/winforms03162004.mspx

 

使用自定义验证组件库扩展 Windows 窗体

发布日期: 8/24/2004 | 更新日期: 8/24/2004

Michael Weinhardt

http://www.mikedub.net/

摘要:数据验证是确保正常的数据捕获以及后续处理和报告的关键步骤。本文介绍了 Windows 窗体固有的程序验证基础结构,并以此为基础开发了用于提供更高效验证功能的自定义验证组件库,该验证功能与使用 ASP.NET 的验证控件相似。

下载 winforms03162004_sample.msi 示例文件。

*
本页内容
引言 引言
Windows 窗体验证的主要功能 Windows 窗体验证的主要功能
程序验证与声明性验证 程序验证与声明性验证
建立设计时支持 建立设计时支持
模仿是最真诚的恭维 模仿是最真诚的恭维
必需字段验证程序简介 必需字段验证程序简介
BaseValidator:分治法 BaseValidator:分治法
一个放便士,一个放英镑 一个放便士,一个放英镑
已完成的自定义验证基础结构 已完成的自定义验证基础结构
结论 结论
其他自定义验证解决方案 其他自定义验证解决方案
C# 与 Visual Basic .NET C# 与 Visual Basic .NET
Genghis Genghis
致谢 致谢
参考资料 参考资料


引言

大家都把我称作怪人,我最喜欢的影片之一是 Amazon Women on the Moon,该片模仿了二十世纪五十年代的 B 级科幻电影,风格同 The Kentucky Fried Movie 一样,由几个喜剧短片组成。其中的一个短片“两份身份证明”,讲述了 Karen 和 Jerry(分别由 Rosanna Arquette 和 Steve Guttenberg 扮演)在他们初次相会的晚上发生的故事。故事从 Jerry 到达 Karen 的公寓开始。经过几分钟轻松友好的谈话后,Karen 突然要求 Jerry 出示两份身份证明-一张信用卡,一份有效的驾驶执照。Karen 通过身份证明对 Jerry 进行约会检查,正是这种突然的变化构成了该喜剧的主线。遗憾的是,Jerry 没有通过身份检查,Karen 因此拒绝了此次约会。除了滑稽可笑以外,这个故事还具有现实意义,Windows 窗体开发人员从中可以清楚地认识到:应始终对数据进行验证,以免出现意外情况,就像对剧中的 Steve Guttenberg 进行约会检查一样。相反,成功的数据验证就像对剧中的 Rosanna Arquette 进行约会检查一样,这对于该短剧的作者来说会是一件相当愉快的事情。


Windows 窗体验证的主要功能

简单地说,验证是指在进行后续处理或存储之前,确保数据的完整性和准确性的过程。尽管验证在数据层和业务规则应用程序层中都可以实现,但应将其包含在表示层中,以构成前沿验证防御。利用 UI,开发人员通常可以为最终用户构造一个更具人性化、响应性更高并提供更多信息的验证过程,同时还可以避免出现类似于跨 N 层应用程序进行不必要的双向网络通信这样的问题。验证可以包含类型、范围和业务规则检查,图 1 中的窗体可以应用所有这些类型的验证。


1. 要求验证的 Add New Employee 窗体

该窗体需要验证以下内容:

必须输入“姓名”、“出生日期”和“电话号码”

“出生日期”必须是有效的日期/时间值

“电话号码”必须符合澳大利亚电话号码格式:(xx) xxxx xxxx

新员工的年龄至少为 18 周岁

实现此验证需要一个相应的基础结构,Windows 窗体提供了该基础结构,并将其直接内置于每个控件中。为指示控件支持验证,将控件 CausesValidation 的属性设置为 True,即所有控件的默认值。 如果某个控件的 CausesValidation 属性设置为 True,则当焦点切换到另一个 CausesValidation 值也设置为 True 的控件时,将引发前一个控件的 Validating 事件。随后,应处理 Validating 以实现验证逻辑,如确保提供“姓名”。

void txtName_Validating(object sender, CancelEventArgs e) {
  // 要求输入姓名
  if(txtName.Text.Trim() == "" ) {
    e.Cancel = true;
    return;
  }
}

Validating 提供了一个 CancelEventArgs 参数,通过设置该参数的 Cancel 属性可以指示该字段是否有效。如果 Cancel 为 True(无效字段),则焦点将停留在无效控件上。如果设置为默认值 False(有效字段),则将引发 Validated 事件,同时焦点将切换到新控件。图 2 演示了此过程。


2. Windows 窗体验证过程

您所要做的是以可视方式通知用户其输入数据的有效性,直觉告诉我们使用状态栏实现此功能比较合适。从可视化角度而言,该技术存在两个问题:

1.

状态栏只能显示一个错误,而一个窗体可能包含多个无效控件。

2.

由于状态栏通常位于窗体底部,因此很难确定错误消息究竟针对哪个控件。

经证实,在针对一个或更多控件提供错误通知方面,ErrorProvider 组件是一个更出色的机制。该组件组合使用了图标和工具提示向用户发出错误通知,并在相关控件的旁边显示相应的消息,如图 3 所示。


3. ErrorProvider 演示

启用该组件很简单,只需将 ErrorProvider 组件拖到一个窗体并配置其图标、闪烁速率和闪烁样式,然后即可将 ErrorProvider 合并到验证代码:

void txtName_Validating(object sender, CancelEventArgs e) {
  // 需要输入姓名
  if(txtName.Text.Trim() == "" ) {
    errorProvider.SetError(txtName "Name is required");
    e.Cancel = true;
    return;
  }
  // 姓名有效
  errorProvider.SetError(txtName, "");
}

CausesValidationValidatingErrorProvider 提供了实现按控件验证的基础结构,可将其实现方式重复用于其他控件,如 txtDateOfBirthtxtPhoneNumber

void txtDateOfBirth_Validating(object sender, CancelEventArgs e) {
  DateTime dob;
  // 需要输入 DOB,且 DOB 必须是有效日期
  if( !DateTime.TryParse(txtDateOfBirth.Text, out dob) ) {
    errorProvider.SetError(txtDateOfBirth, "Must be a date/time value");
    e.Cancel = true;
    return;
  }
  // 年龄至少为十八周岁
  if( dob > DateTime.Now.AddYears(-18) ) {
    errorProvider.SetError(txtDateOfBirth, "Must be 18 or older");
    e.Cancel = true;
    return;
  }
  // DOB 有效
  errorProvider.SetError(txtDateOfBirth, "");
}
void txtPhoneNumber_Validating(object sender, CancelEventArgs e) {
  // 需要输入电话号码,且格式必须符合澳大利亚电话号码格式
  Regex re = new Regex(@"^\(\d{2}\) \d{4} \d{4}$");
  if( !re.IsMatch(txtPhoneNumber.Text) ) {
    errorProvider.SetError(txtPhoneNumber, "Must be (xx) xxxx xxxx");
    e.Cancel = true;
    return;
  }
  // 电话号码有效
  errorProvider.SetError(txtPhoneNumber, "");
}

窗体范围的验证

Validating 事件和 ErrorProvider 组件的组合提供了一个优秀的解决方案,可以在需要时(即当用户输入数据时)动态验证每个控件。遗憾的是,对 Validating 事件的依赖使该解决方案无法自动扩展为支持窗体范围的验证(当用户单击 OK 按钮完成数据输入时,需要此验证)。单击 OK 按钮前,一个或更多个控件可能没有焦点,因此不引发其 Validating 事件。窗体范围的验证通过手动调用捆绑在每个 Validating 事件中的验证逻辑来实现,方法是枚举窗体中的所有控件,为每个控件设置焦点,然后调用该窗体的 Validate 方法,如下所示:

void btnOK_Click(object sender, System.EventArgs e) {
  foreach( Control control in Controls ) {
    // 在控件上设置焦点
    control.Focus();
    // 验证导致引发控件的验证事件,
    // 如果 CausesValidation 为 True
    if( !Validate() ) {
      DialogResult = DialogResult.None;
      return;
    }
  }
}

Cancel 按钮不需要实现窗体范围的验证,因为它只用于关闭窗体。如果用户当前所在的控件无效,则用户将无法单击 Cancel 按钮,原因是 Cancel 按钮的 CausesValidation 属性在默认情况下也设置为 True,因此焦点被保留。将 Cancel 按钮的 CausesValidation 设置为 False,可以轻松地避免该情况,从而避免在焦点已从中转移的控件中引发 Validating,如图 4 所示。


4. 阻止验证

Add New Employee 窗体中大约包含 60 行代码,它支持基本的验证功能。


程序验证与声明性验证

从工作效率的角度来看,该解决方案存在一个根本性的问题,即大型应用程序通常包含多个窗体,每个窗体通常比本文的小程序示例包含更多的控件,因此需要更多的验证代码。在 UI 日益复杂的情况下编写越来越多的代码是一项不具伸缩性的技术,因此应尽可能地避免。解决方案之一是标识通用的验证逻辑,并将其封装到可重复使用的类型。这有助于减少用于创建和配置所需的验证对象实例的客户端代码。然而,尽管向正确的方向迈进了一步,该解决方案仍需要编写程序代码。但它提供了一种 Windows 窗体 UI 通用的模式-代表 Windows 窗体执行大多数任务的类型(大多数客户端代码用于配置这些类型的属性,这对于 Windows 窗体组件或控件来讲是理想的选择方案。以这种方式封装代码,开发人员的工作转换为将组件或控件从 Toolbox 拖放到窗体中,从设计时功能(如“属性浏览器”)中配置它,然后让 Windows 窗体设计器实施将设计时目标转换成保留在 InitializeComponent 中的代码这项工作量繁重的任务。这样,编程性工作转换为声明性工作,其中声明性是高效率的同义词。这对 Windows 窗体验证来说是一种理想的情况。


建立设计时支持

第一步是建立设计时支持,这要求实现派生自三种类型的设计时组件之一:System.ComponentModel.ComponentSystem.Windows.Forms.ControlSystem.Windows.Forms.UserControl。如果逻辑不需要 UI,则 Component 是正确的选择;否则,ControlUserControl 是正确的选择。ControlUserControl 之间的区别在于 UI 的呈现方式。前者由您编写的自定义绘图代码呈现,后者则由其他控件和组件呈现。现有验证代码不进行自身绘制,因为它将该任务传递给了 ErrorProvider。因此,Component 成为封装验证类的最佳选择。


模仿是最真诚的恭维

下一步是弄清所需的验证程序类型。首先,应了解 ASP.NET 实现的验证程序。为什么是这样呢?本人是一致性的忠实推崇者,并且在我的前额刺有“不要多此一举”的字样(当然是反着刺上去的,所以在刷牙的时候能在镜子里面看到它)。因此,我认为主要目标是创建在公开的类型和成员方面与 ASP.NET 的验证程序保持一致的 Windows 窗体验证程序(如果该解决方案适用于 Windows 窗体)。除了在技术方面向 ASP.NET 小组表示敬意之外,该选择还可以增进应用不同开发模式的开发人员之间的了解。ASP.NET 当前提供了下表列出的验证程序。

验证控件 说明

RequiredFieldValidator

确保字段包含值

RegularExpressionValidator

使用正则表达式验证字段的格式

CompareValidator

对目标字段和其他字段或值进行等同性测试

RangeValidator

测试字段属于某种特殊的类型并/或在指定的范围内

CustomValidator

为复杂程度超过其他验证程序处理能力的自定义验证逻辑创建一个服务器端事件处理程序

此外,我认为另一个目标是具有可扩展性,以便当提供的验证程序不能满足要求时,开发人员只需进行少量的工作便能将他们自己的自定义验证程序并入基础结构中。最后,为减少工作量,实现应利用我们先前讨论的现有 Windows 窗体验证基础结构。


必需字段验证程序简介

明确以上目标之后,现在我们开始了解具体的操作细节。首先,我们将创建一个最简单的验证程序,即 RequiredFieldValidator

Visual Studio .NET 解决方案

我们将需要一个 Visual Studio® .NET 解决方案来实现 RequiredFieldValidator。生成组件或控件库时,我倾向于采用“两个项目”方法,即将解决方案拆分为一个组件或控件项目和一个相应的测试工具项目,方法是:

1.

创建一个解决方案 (winforms03162004_CustomValidation)

2.

向解决方案 CustomValidationSample 中添加一个 Windows Forms Application 项目

3.

向解决方案 CustomValidation 中添加一个 Windows Forms Control 项目

4.

CustomValidation 项目中删除默认的 UserControl1

5.

添加一个名为 RequiredFieldValidator 的新类,并使其从 System.ComponentModel.Component 中派生

注意 由于这些验证组件不局限于 Whidbey,因此我使用了 Visual Studio .NET 2003.NET Framework 1.1

接口

保持一致性要求实现同样适用于 Windows 窗体的接口。这意味着实现由 ASP.NET RequiredFieldValidator 公开的相同成员:

class RequiredFieldValidator : Component {
  string ControlToValidate {get; set;}
  string ErrorMessage {get; set;}
  string InitialValue {get; set;}
  bool IsValid {get; set;}
  void Validate();
}

下表详细描述了每个成员。

成员 说明

ControlToValidate

指定要验证的控件。

ErrorMessage

ControlToValidate 无效时显示的消息。默认值是 ""。

InitialValue

强制值并不一定意味着 "" 以外的值。某些情况下,默认的控件值可能用于提示,例如在 ListBox 控件中使用 "[Choose a value]"。这种情况下,所需的值必须不同于初始值 "[Choose a value]".。InitialValue 支持此项要求。默认值为 ""。

IsValid

调用 Validate 后,报告 ControlToValidate’s 数据是否有效。同 ASP.NET 实现一样,当需要覆盖 Validate 时,可以从客户端代码设置 IsValid。默认值为 True。

Validate

验证 ControlToValidate’s 值并将结果存储到 IsValid 中。

在 ASP.NET 中,ControlToValidate 是一个用于为服务器端控件实例的 ViewState 编制索引的字符串在处理 Web 应用程序的基于请求的无连接特性时需要这种间接的方法。由于在 Windows 窗体中不需要做同样的考虑,因此可以直接引用该控件。此外,由于 ErrorProvider 将在内部使用,因此我们将添加一个 Icon 属性以供开发人员进行配置。明确以上要求后,需要对接口进行如下调整:

class RequiredFieldValidator : Component {
  ...
  Control ControlToValidate {get; set;}
  Icon Icon {get; set;}
  ...
}

实现

以下是最终的实现:

class RequiredFieldValidator : Component {
  // 专用字段
  ...
  Control ControlToValidate
    get { return _controlToValidate; }
    set {
      _controlToValidate = value;
      // 运行时(即不从 VS.NET 中)关联 ControlToValidate 的
      // Validating 事件
      if( (_controlToValidate != null) && (!DesignMode) ) {
        _controlToValidate.Validating +=
        new CancelEventHandler(ControlToValidate_Validating);
      }
    }
  }
  string ErrorMessage {
    get { return _errorMessage; }
    set { _errorMessage = value; }
  }
  Icon Icon {
    get { return _icon; }
    set { _icon = value; }
  }
  string InitialValue {
    get { return _initialValue; }
    set { _initialValue = value; }
  }
  bool IsValid {
    get { return _isValid; }
    set { _isValid = value; }
  }
  void Validate() {
    // 如果与初始值(该值不一定是空字符串)不同,
    // 则有效
    string controlValue = ControlToValidate.Text.Trim();
    string initialValue;
    if( _initialValue == null ) initialValue = "";
    else initialValue = _initialValue.Trim();
    _isValid = (controlValue != initialValue);
    // 如果 ControlToValidate 无效,则显示错误消息
    string errorMessage = "";
    if( !_isValid ) {
      errorMessage = _errorMessage;
      _errorProvider.Icon = _icon;
    }
    _errorProvider.SetError(_controlToValidate, errorMessage);
  }
  private void ControlToValidate_Validating(
    object sender,
    CancelEventArgs e) {
    // 无效时不取消,因为我们不希望在无效时
    // 强制焦点保留在 ControlToValidate 上
    Validate();
  }
}

该实现的关键是如何将其关联到 ControlToValidateValidating 事件:

Control ControlToValidate {
  get {...}
  set {
    ...
    // 运行时(即不在 VS.NET 设计时)关联 ControlToValidate 的
    //  Validating 事件
    if( (_controlToValidate != null) && (!DesignMode) ) {
      _controlToValidate.Validating +=
        new CancelEventHandler(ControlToValidate_Validating);
  }
}

这使得能够在输入数据时动态执行所需的字段验证,如同标准的 Windows 窗体实现一样。此外,还获得了并不明显的额外好处。在初始实现中,焦点保留在控件中,直到该控件有效,即直到它的 Validating 事件的 CancelEventArgs.Cancel 属性设置为 False(即在该控件中捕获用户)。数据输入 UI 在任何地方都不应捕获用户,该设计思想在 ControlToValidate_Validating 中得到反映,即 ControlToValidate_Validating 作为其自身验证的引发器(而不是作为与基础验证结构集成的机制)来处理该事件。图 5 显示了已更新的进程。


5. 已更新的验证进程

设计时集成

除设计时方面以外,该实现的功能性方面是完整的。所有优秀的 Visual Studio .NET 设计时成员都使用各种功能指定它们的设计时行为。例如,RequiredFieldValidator 使用 ToolboxBitmapAttribute 指定在 Toolbox Component Tray 中显示的自定义图标,并使用 CategoryAttributeDescriptionAttribute 使 RequiredFieldValidator 的公共属性在“属性浏览器”中更具有描述性:

[ToolboxBitmap(
  typeof(RequiredFieldValidator),
  "RequiredFieldValidator.ico")]
class RequiredFieldValidator : Component {
  ...
  [Category("Behaviour")]
  [Description("Gets or sets the control to validate.")]
  Control ControlToValidate {...}
  [Category("Appearance")]
  [Description("Gets or sets the text for the error message.")]
  string ErrorMessage {...}
  [Category("Appearance")]
  [Description("Gets or sets the Icon to display ErrorMessage.")]
  Icon Icon {...}
  [Category("Behaviour")]
  [Description("Gets or sets the default value to validate against.")]
  string InitialValue  {...}
}

完整的实现使用其他几个设计时功能(这些功能超出了本文介绍的范畴),包括:

指定从“属性浏览器”中可以为 ControlToValidate 选择的控件。 该实现允许使用 TextBoxListBoxComboBoxUserControl 控件

从“属性浏览器”中隐藏 IsValid,因为它是只限运行时属性(所有公共属性在设计时自动显示在 Property Browser 中的)。

要了解有关设计时的详细介绍,请参阅以下文章:

Building Windows Forms Controls and Components with Rich Design-Time Features, Part 1

Building Windows Forms Controls and Components with Rich Design-Time Features, Part 2

使用 RequiredFieldValidator

要使用 RequiredFieldValidator,首先应将其添加到 Toolbox,然后才能将它拖动到窗体并对其进行配置。为此,应执行以下操作:

1.

重新生成解决方案。

2.

在 Visual Studio .NET 中打开测试窗体。

3.

右键单击 Toolbox 并选择“添加选项卡”。

4.

创建一个名为 Custom Validation 的选项卡并选中它。

5.

右键单击 Toolbox 并选择“添加/删除项”。

6.

浏览到新生成的组件程序集 (CustomValidation.dll) 并选中它。

通过以上步骤可以在 Windows 窗体设计器中该使用组件,如图 6 所示。


6. 包含运行时属性的 RequiredFieldValidator 演示

我对 Add New Employee 窗体中的所有字段都应用了 RequiredFieldValidator,并包含有关用户可以在每个字段中输入的值类型的提示。图 7 显示它在运行时的工作方式。


7. 运行时的乐趣


BaseValidator:分治法

有了 RequiredFieldValidator,便可以轻松地实现其余的验证程序。但也许并不会如此轻松。RequiredFieldValidator 将特定的“必需”验证逻辑与每个验证程序需要实现的逻辑的余下主要部分(如指定要验证的控件)紧密关联在一起。这种情况下,一个适当的解决方案是将 RequiredFieldValidator 分成两个新类型,即 BaseValidator 和由其派生且比较简单的 RequiredFieldValidator。严格地说,从一致性角度考虑,我们还应创建 IValidator 接口并通过与 ASP.NET 相同的方式实现 BaseValidator。然而,该方法提供的可扩展性的程度超出了我们当前的需要。BaseValidator 实现与原始的 RequiredFieldValidator 相类似的验证逻辑的可重用部分:

abstract class BaseValidator : Component, IValidator {
  Control ControlToValidate {...}
  string ErrorMessage {...}
  Icon Icon {...}
  bool IsValid {...}
  void Validate() {...}
  private void ControlToValidate_Validating(...) {...}
}

尽管 IsValidValidate 是所有验证程序所共有的,但它们在新基础结构中只是作为一种形式存在,从而将特定的验证留给了每个派生的验证程序。因此,BaseValidator 需要一种机制,通过该机制它能够“询问”派生的验证程序,它是否有效。这是通过一个额外的抽象方法 EvaluateIsValid 实现的。EvaluateIsValidBaseValidator 都是抽象的,以确保每个派生的验证程序都知道它们能够回答这个问题。新添加部分要求一些重构,如下所示:

abstract class BaseValidator : Component {
  ...
  void Validate() {
    ...
    _isValid = EvaluateIsValid();
    ...
  }
  protected abstract bool EvaluateIsValid();
}

结果是 BaseValidator 只能由派生方法使用,因此必须实现 EvaluateIsValidValidate 方法已经过更新,可以向下调用派生的 EvaluateIsValid 以查询其是否有效。该项技术还应用于 ASP.NET 控件,从而增强了所需的一致性。

RequiredFieldValidator:Redux

完成 BaseValidator 后,我们现在需要重构 RequiredFieldValidator,以实现其自身的功能,即从 BaseValidator 派生并在 EvaluateIsValidInitialValue 中实现指定的必须字段验证位。本着以简代蘩的原则,编写了下面这个更简单的新 RequiredFieldValidator

[ToolboxBitmap(
  typeof(RequiredFieldValidator),
  "RequiredFieldValidator.ico")]
class RequiredFieldValidator : BaseValidator {
  string InitialValue  {...}
  protected override bool EvaluateIsValid() {
    string controlValue = ControlToValidate.Text.Trim();
    string initialValue;
    if( _initialValue == null ) initialValue = "";
    else initialValue = _initialValue.Trim();
    return (controlValue != initialValue);
  }
}


一个放便士,一个放英镑

在基类与派生类之间合理地分隔普通验证功能与特定验证功能使我们能够将注意力只集中在特定的验证上。这不但适用于 RequiredFieldValidator,而且对其余几个用于与 ASP.NET 保持一致而应实现的验证程序更为适用,这些验证程序包括:

RegularExpressionValidator

CustomValidator

CompareValidator

RangeValidator

让我们了解一下每个验证程序以及用于实现它们的逻辑。

RegularExpressionValidator

正则表达式是一个基于大量令牌而构建的功能强大的文本模式匹配技术。当字段需要特定形式的值时,正则表达式是优秀的字符串操作替代方法,尤其是对于重要的文本模式。例如,确保电话号码符合澳大利亚格式((xx) xxxx xxxx)可以简化为一个正则表达式:

^\(\d{2}\) \d{4} \d{4}$

其好处是为什么 ASP.NET 实现 RegularExpressionValidator,并且我们也会实现:

using System.Text.RegularExpressions;
...
[ToolboxBitmap(
  typeof(RegularExpressionValidator),
  "RegularExpressionValidator.ico")]
class RegularExpressionValidator : BaseValidator {
  ...
  string ValidationExpression {...}
  protected override bool EvaluateIsValid() {
    // 如果为空,则不验证
    if( ControlToValidate.Text.Trim() == "" ) return true;
    // 如果与 ControlToValidate 的整个文本匹配,则成功
    string field = ControlToValidate.Text.Trim();
    return Regex.IsMatch(field, _validationExpression.Trim());
  }
}

在设计时,开发人员可以通过属性浏览器提供验证表达式,如图 8 所示。


8. 指定验证的正则表达式

CustomValidator

有时,特定验证程序无法解决特定的验证问题,尤其是当复杂的业务规则为需要数据库访问时。这种情况下,可以编写自定义代码,且 CustomValidator 使我们能够编写该自定义代码并确保它与自定义验证基础结构集成在一起,这对于以后的窗体范围的验证是很重要的。CustomValidator 包含的 Validating 事件和 ValidatingCancelEventArgs 可以满足此要求:

[ToolboxBitmap(typeof(CustomValidator), "CustomValidator.ico")]
class CustomValidator : BaseValidator {
  class ValidatingCancelEventArgs {...}
  delegate void ValidatingEventHandler(object sender, ValidatingCancelEventArgs e);
  event ValidatingEventHandler Validating;
  void OnValidating(ValidatingCancelEventArgs e) {
    if( Validating != null ) Validating(this, e);
  }

  protected override bool EvaluateIsValid() {
    // 将验证进程传递至事件处理程序并等待响应
    ValidatingCancelEventArgs args =
      new ValidatingCancelEventArgs(false,ControlToValidate);
    OnValidating(args);
    return args.Valid;
  }
}

要处理 CustomValidatorValidating 事件,在“属性浏览器”中双击该事件,如图 9 所示:


9. 为自定义的复杂验证创建验证处理程序

然后,只需添加相应的验证逻辑,以下示例中的验证逻辑确保新员工的年龄大于或等于 18 岁:

class AddNewEmployeeForm : Form
  void cstmDOB_Validating(object sender, ValidatingCancelEventArgs e) {
  try {
    // 必须大于或等于 18 岁
    DateTime dob = DateTime.Parse(((Control)e.ControlToValidate).Text);
    DateTime  legal = DateTime.Now.AddYears(-18);
    e.Valid = ( dob < legal );
  }
  catch( Exception ex ) { e.Valid = false; }
}

注意CustomValidator 根据 ASP.NET 的服务器端处理特性命名其等价事件 ServerValidate。我们之所以使用 Validating,是因为我们不需要做这种区分,且 BaseValidator 已经使用了 Validate 派生于的 Validate

BaseCompareValidator

该验证程序只能处理一个字段。然而,在某些情况下,验证可能涉及多个字段和/或值、是否确保某个字段的值在最大/最小值范围内 (RangeValidator) 或将一个字段与另一个字段进行比较 (CompareValidator)。不论在哪种情况下,都需要进行额外的工作以执行类型检查、转换和比较,其中包括验证时必须参照的类型。该功能应封装在新类型 BaseCompareValidator 中,从该类型可以派生出 RangeValidatorCompareValidator 以节省某些额外的工作。BaseCompareValidator 自身派生自 BaseValidator,以获取基本的验证支持并实现四个新成员:

abstract class BaseCompareValidator : BaseValidator {
  ...
  ValidationDataType Type {...}
  protected TypeConverter TypeConverter {...}
  protected bool CanConvert(string value) {...}
  protected string Format(string value) {...}
}

ValidationDataType 是自定义枚举,用于指定支持比较和范围验证的类型:

enum ValidationDataType {
  Currency,
  Date,
  Double,
  Integer,
  String
}

RangeValidator

在需要确定某个字段值是否在指定范围内的情况下,可以使用 RangeValidator。它允许开发人员输入最小值和最大值以及适用于所有相关值的类型。在以下示例中,RangeValidator 派生自 BaseCompareValidator

[ToolboxBitmap(typeof(RangeValidator), "RangeValidator.ico")]
class RangeValidator : BaseCompareValidator {
  ...
  string MinimumValue {...}
  string MaximumValue {...}
  protected override bool EvaluateIsValid() {
      // 如果为空,则不验证,除非要求进行验证
      // 验证并转换最大值
      // 验证并转换最大值
      // 检查最小值是否 <= 最大值
      // 检查并转换 ControlToValue
      // 是否最小值 <= 值 <= 最大值
    }
  }

“属性浏览器”用于配置范围和类型属性,如图 10 所示。


10. 配置 RangeValidator

CompareValidator

最后介绍的是 CompareValidator(将它放在最后介绍并不意味着它的重要性最低),CompareValidator 用于针对 ControlToValidate 以及其它控件或值执行等同性测试。控件或值的等同性测试分别由 ControlToCompareValueToCompare 指定:

[ToolboxBitmap(typeof(CompareValidator), "CompareValidator.ico")]
class CompareValidator : BaseCompareValidator {
  ...
  Control ControlToCompare {...}
  ValidationCompareOperator Operator {...}
  string ValueToCompare {...}
  protected override bool EvaluateIsValid() {
    // 如果为空,则不验证,除非要求进行验证
    // 检查提供的 ControlToCompare
    // 检查提供的 ValueToCompare
    // 验证并转换 CompareFrom
    // 验证并转换 CompareTo
    // 执行比较,如 ==, >, >=, <, <=, !=
  }
}

Operator 定义在 ControlToValidateControlToCompareValueToCompare 中执行的等同性测试。OperatorValidationCompareOperator 指定:

enum ValidationCompareOperator {
  DataTypeCheck,
  Equal,
  GreaterThan,
  GreaterThanEqual,
  LessThan,
  LessThanEqual,
  NotEqual
}

正如您所知,还可以使用 CompareValidator 以验证 ControlToValidate 是否是特殊的数据类型(由 DataTypeCheck 值指定)。这是唯一一个既不需要 ControlToCompare 也不需要 ValueToCompare 的比较。图 11 显示如何配置 CompareValidator


11. 配置 CompareValidator


已完成的自定义验证基础结构

至此,所有与 ASP.NET 等价的验证程序组件均已列出并予以了说明。每个实现都直接或间接地从 BaseValidator 派生以继承基本的、可重复使用的验证功能。此类继承创建了可隐式扩展的基础结构,其他开发人员可以像我们在本文中那样轻松地使用该基础结构。图 12 显示了该自定义验证解决方案以及开发人员可以在哪些情况下使用它。必须使用该自定义验证解决方案的情况包括:

创建新的普通验证程序

创建业务规则特定的验证程序

向一个现有的验证程序中添加额外的相关逻辑

图 12 显示了该设计为这些情况提供的可扩展支持。


12. 自定义验证的可扩展支持


结论

首先,我们介绍了内置于 .NET Framework 的 Windows 窗体中的验证框架。然后,我们将验证逻辑重新封装到多个设计时组件中,以将最初的程序验证进程转换为更具声明性、且效率更高的进程。在此过程中,我们基于 BaseValidator 创建了可扩展的验证基础结构,以实现 RequiredFieldValidatorRegularExpressionValidatorCustomValidatorCompareValidatorRangeValidator。该设计允许其他开发人员能够轻松地创建新验证程序,方法也是从 BaseValidator 派生以继承普通功能,并将实现问题的范围缩小至特定的验证。本文主要介绍了按控件验证,并且只对窗体范围的验证提供了说明。下一篇文章将在自定义验证的基础之上并入窗体范围的验证,介绍如何组合现有的验证程序并添加 ASP.NET 验证控件集合的另一个主要部分,即 ValidationSummary 控件。现在,我建议您看一看 Amazon Women on the Moon,仅仅为找些笑料。


其他自定义验证解决方案

我最初为 Genghis 创建验证组件要追溯到 2002 年下半年。在研究所参考的 MSDN 杂志中有关设计时的文章时,我发现了 Billy Hollis 在他(以及 Rocky)的 MSDN Online 专栏中提供的验证程序片段,其网址是 Adventures in Visual Basic .NET。强烈建议您阅读一下该文章,创建一个出色的 Windows 窗体验证方案,以代替我创建的那一个,该文章还深入介绍了如何在实现设计时的扩展属性问题。


C# 与 Visual Basic .NET

我前面写的两篇文章强调了文中编写的代码片段只适用于 C#,而该专栏的许多读者是 Visual Basic® .NET 开发人员。本人对 C# 偏爱有加,但同时也很乐于为 Visual Basic.NET 编写程序。我原本希望同时提供 Visual Basic .NET 和 C# 两种版本的代码实例,但由于时间方面的原因,未能将两者合并在一起并写入各自的专栏。但我不想排除两者中的任何一个,作为一种折衷方法,我将在撰写的每一篇文章中,在 Visual Basic .NET 和 C# 代码之间进行转换。这会使得第一个针对 Visual Basic .NET 的程序片段显示在两篇文章中(下一个验证程序片段将继续从此处开始的 C# 工作)。在此项工作的时间内,很乐意保持有关两种代码的交流。该解决方案是否满足需要?请给我发邮件并告诉我您的想法。


Genghis

一个更完整并与该解决方案稍有不同的解决方案当前可以在 Genghis 中获得,Genghis 是一个在 Microsoft Foundation Classes (MFC) 上服务于 .NET Windows 窗体开发人员的共享源代码集合。有趣的是,我在编写本文时,有些地方需要进行改进,但这些改进将在 Genghis 的下一版中反映出来。但是,如果您已经迫不及待,则可以随时下载本文,如果您有时间,请将错误或希望改进的地方发送给我。


致谢

本文受到了 Ron Green、Chris Sells 和 Amazon Women on the Moon 的启发。


参考资料

Billy Hollis 所著的 Validator Controls for Windows Forms

有关如何开发面向设计时的组件和控件的深入介绍,请参阅:

Michael Weinhardt 和 Chris Sells 所著的 Building Windows Forms Controls and Components with Rich Design-Time Features, Part 1,MSDN Magazine,2003 年 4 月

Michael Weinhardt 和 Chris Sells 所著的 Building Windows Forms Controls and Components with Rich Design-Time Features, Part 2,MSDN Magazine,2003 年 5 月

如果您对正则表达式不太熟悉,请参阅以下资料:

Jeffery E. F. Friedl, O’Reilly 所著的 Mastering Regular Expressions, Second Edition,2003 年

Michael Weinhardt 和 Chris Sells 所著的 Regular Expressions in .NET,Windows Developer Magazine,2002 年 12 月

Michael Weinhardt 当前从事全职的不同 .NET 程序编写工作,包括与 Chris Sells 合作撰写 Windows Forms Programming in C#, 2nd Edition (Addison Wesley) 以及撰写该专栏。总而言之,Michael 迷恋 .NET,尤其是 Windows 窗体,并在可能的时候看 80 集电视剧。有关详细信息,请访问 http://www.mikedub.net/

转到原英文页面

© 2004 Microsoft Corporation 版权所有。保留所有权利。使用规定。

使用自定义验证组件库扩展 Windows 窗体

发布日期: 8/24/2004 | 更新日期: 8/24/2004

Michael Weinhardt

http://www.mikedub.net/

摘要:数据验证是确保正常的数据捕获以及后续处理和报告的关键步骤。本文介绍了 Windows 窗体固有的程序验证基础结构,并以此为基础开发了用于提供更高效验证功能的自定义验证组件库,该验证功能与使用 ASP.NET 的验证控件相似。

下载 winforms03162004_sample.msi 示例文件。

*
本页内容
引言 引言
Windows 窗体验证的主要功能 Windows 窗体验证的主要功能
程序验证与声明性验证 程序验证与声明性验证
建立设计时支持 建立设计时支持
模仿是最真诚的恭维 模仿是最真诚的恭维
必需字段验证程序简介 必需字段验证程序简介
BaseValidator:分治法 BaseValidator:分治法
一个放便士,一个放英镑 一个放便士,一个放英镑
已完成的自定义验证基础结构 已完成的自定义验证基础结构
结论 结论
其他自定义验证解决方案 其他自定义验证解决方案
C# 与 Visual Basic .NET C# 与 Visual Basic .NET
Genghis Genghis
致谢 致谢
参考资料 参考资料


引言

大家都把我称作怪人,我最喜欢的影片之一是 Amazon Women on the Moon,该片模仿了二十世纪五十年代的 B 级科幻电影,风格同 The Kentucky Fried Movie 一样,由几个喜剧短片组成。其中的一个短片“两份身份证明”,讲述了 Karen 和 Jerry(分别由 Rosanna Arquette 和 Steve Guttenberg 扮演)在他们初次相会的晚上发生的故事。故事从 Jerry 到达 Karen 的公寓开始。经过几分钟轻松友好的谈话后,Karen 突然要求 Jerry 出示两份身份证明-一张信用卡,一份有效的驾驶执照。Karen 通过身份证明对 Jerry 进行约会检查,正是这种突然的变化构成了该喜剧的主线。遗憾的是,Jerry 没有通过身份检查,Karen 因此拒绝了此次约会。除了滑稽可笑以外,这个故事还具有现实意义,Windows 窗体开发人员从中可以清楚地认识到:应始终对数据进行验证,以免出现意外情况,就像对剧中的 Steve Guttenberg 进行约会检查一样。相反,成功的数据验证就像对剧中的 Rosanna Arquette 进行约会检查一样,这对于该短剧的作者来说会是一件相当愉快的事情。

返回页首返回页首


Windows 窗体验证的主要功能

简单地说,验证是指在进行后续处理或存储之前,确保数据的完整性和准确性的过程。尽管验证在数据层和业务规则应用程序层中都可以实现,但应将其包含在表示层中,以构成前沿验证防御。利用 UI,开发人员通常可以为最终用户构造一个更具人性化、响应性更高并提供更多信息的验证过程,同时还可以避免出现类似于跨 N 层应用程序进行不必要的双向网络通信这样的问题。验证可以包含类型、范围和业务规则检查,图 1 中的窗体可以应用所有这些类型的验证。


1. 要求验证的 Add New Employee 窗体

该窗体需要验证以下内容:

必须输入“姓名”、“出生日期”和“电话号码”

“出生日期”必须是有效的日期/时间值

“电话号码”必须符合澳大利亚电话号码格式:(xx) xxxx xxxx

新员工的年龄至少为 18 周岁

实现此验证需要一个相应的基础结构,Windows 窗体提供了该基础结构,并将其直接内置于每个控件中。为指示控件支持验证,将控件 CausesValidation 的属性设置为 True,即所有控件的默认值。 如果某个控件的 CausesValidation 属性设置为 True,则当焦点切换到另一个 CausesValidation 值也设置为 True 的控件时,将引发前一个控件的 Validating 事件。随后,应处理 Validating 以实现验证逻辑,如确保提供“姓名”。

void txtName_Validating(object sender, CancelEventArgs e) {
  // 要求输入姓名
  if(txtName.Text.Trim() == "" ) {
    e.Cancel = true;
    return;
  }
}

Validating 提供了一个 CancelEventArgs 参数,通过设置该参数的 Cancel 属性可以指示该字段是否有效。如果 Cancel 为 True(无效字段),则焦点将停留在无效控件上。如果设置为默认值 False(有效字段),则将引发 Validated 事件,同时焦点将切换到新控件。图 2 演示了此过程。


2. Windows 窗体验证过程

您所要做的是以可视方式通知用户其输入数据的有效性,直觉告诉我们使用状态栏实现此功能比较合适。从可视化角度而言,该技术存在两个问题:

1.

状态栏只能显示一个错误,而一个窗体可能包含多个无效控件。

2.

由于状态栏通常位于窗体底部,因此很难确定错误消息究竟针对哪个控件。

经证实,在针对一个或更多控件提供错误通知方面,ErrorProvider 组件是一个更出色的机制。该组件组合使用了图标和工具提示向用户发出错误通知,并在相关控件的旁边显示相应的消息,如图 3 所示。


3. ErrorProvider 演示

启用该组件很简单,只需将 ErrorProvider 组件拖到一个窗体并配置其图标、闪烁速率和闪烁样式,然后即可将 ErrorProvider 合并到验证代码:

void txtName_Validating(object sender, CancelEventArgs e) {
  // 需要输入姓名
  if(txtName.Text.Trim() == "" ) {
    errorProvider.SetError(txtName "Name is required");
    e.Cancel = true;
    return;
  }
  // 姓名有效
  errorProvider.SetError(txtName, "");
}

CausesValidationValidatingErrorProvider 提供了实现按控件验证的基础结构,可将其实现方式重复用于其他控件,如 txtDateOfBirthtxtPhoneNumber

void txtDateOfBirth_Validating(object sender, CancelEventArgs e) {
  DateTime dob;
  // 需要输入 DOB,且 DOB 必须是有效日期
  if( !DateTime.TryParse(txtDateOfBirth.Text, out dob) ) {
    errorProvider.SetError(txtDateOfBirth, "Must be a date/time value");
    e.Cancel = true;
    return;
  }
  // 年龄至少为十八周岁
  if( dob > DateTime.Now.AddYears(-18) ) {
    errorProvider.SetError(txtDateOfBirth, "Must be 18 or older");
    e.Cancel = true;
    return;
  }
  // DOB 有效
  errorProvider.SetError(txtDateOfBirth, "");
}
void txtPhoneNumber_Validating(object sender, CancelEventArgs e) {
  // 需要输入电话号码,且格式必须符合澳大利亚电话号码格式
  Regex re = new Regex(@"^\(\d{2}\) \d{4} \d{4}$");
  if( !re.IsMatch(txtPhoneNumber.Text) ) {
    errorProvider.SetError(txtPhoneNumber, "Must be (xx) xxxx xxxx");
    e.Cancel = true;
    return;
  }
  // 电话号码有效
  errorProvider.SetError(txtPhoneNumber, "");
}

窗体范围的验证

Validating 事件和 ErrorProvider 组件的组合提供了一个优秀的解决方案,可以在需要时(即当用户输入数据时)动态验证每个控件。遗憾的是,对 Validating 事件的依赖使该解决方案无法自动扩展为支持窗体范围的验证(当用户单击 OK 按钮完成数据输入时,需要此验证)。单击 OK 按钮前,一个或更多个控件可能没有焦点,因此不引发其 Validating 事件。窗体范围的验证通过手动调用捆绑在每个 Validating 事件中的验证逻辑来实现,方法是枚举窗体中的所有控件,为每个控件设置焦点,然后调用该窗体的 Validate 方法,如下所示:

void btnOK_Click(object sender, System.EventArgs e) {
  foreach( Control control in Controls ) {
    // 在控件上设置焦点
    control.Focus();
    // 验证导致引发控件的验证事件,
    // 如果 CausesValidation 为 True
    if( !Validate() ) {
      DialogResult = DialogResult.None;
      return;
    }
  }
}

Cancel 按钮不需要实现窗体范围的验证,因为它只用于关闭窗体。如果用户当前所在的控件无效,则用户将无法单击 Cancel 按钮,原因是 Cancel 按钮的 CausesValidation 属性在默认情况下也设置为 True,因此焦点被保留。将 Cancel 按钮的 CausesValidation 设置为 False,可以轻松地避免该情况,从而避免在焦点已从中转移的控件中引发 Validating,如图 4 所示。


4. 阻止验证

Add New Employee 窗体中大约包含 60 行代码,它支持基本的验证功能。

返回页首返回页首


程序验证与声明性验证

从工作效率的角度来看,该解决方案存在一个根本性的问题,即大型应用程序通常包含多个窗体,每个窗体通常比本文的小程序示例包含更多的控件,因此需要更多的验证代码。在 UI 日益复杂的情况下编写越来越多的代码是一项不具伸缩性的技术,因此应尽可能地避免。解决方案之一是标识通用的验证逻辑,并将其封装到可重复使用的类型。这有助于减少用于创建和配置所需的验证对象实例的客户端代码。然而,尽管向正确的方向迈进了一步,该解决方案仍需要编写程序代码。但它提供了一种 Windows 窗体 UI 通用的模式-代表 Windows 窗体执行大多数任务的类型(大多数客户端代码用于配置这些类型的属性,这对于 Windows 窗体组件或控件来讲是理想的选择方案。以这种方式封装代码,开发人员的工作转换为将组件或控件从 Toolbox 拖放到窗体中,从设计时功能(如“属性浏览器”)中配置它,然后让 Windows 窗体设计器实施将设计时目标转换成保留在 InitializeComponent 中的代码这项工作量繁重的任务。这样,编程性工作转换为声明性工作,其中声明性是高效率的同义词。这对 Windows 窗体验证来说是一种理想的情况。

返回页首返回页首


建立设计时支持

第一步是建立设计时支持,这要求实现派生自三种类型的设计时组件之一:System.ComponentModel.ComponentSystem.Windows.Forms.ControlSystem.Windows.Forms.UserControl。如果逻辑不需要 UI,则 Component 是正确的选择;否则,ControlUserControl 是正确的选择。ControlUserControl 之间的区别在于 UI 的呈现方式。前者由您编写的自定义绘图代码呈现,后者则由其他控件和组件呈现。现有验证代码不进行自身绘制,因为它将该任务传递给了 ErrorProvider。因此,Component 成为封装验证类的最佳选择。

返回页首返回页首


模仿是最真诚的恭维

下一步是弄清所需的验证程序类型。首先,应了解 ASP.NET 实现的验证程序。为什么是这样呢?本人是一致性的忠实推崇者,并且在我的前额刺有“不要多此一举”的字样(当然是反着刺上去的,所以在刷牙的时候能在镜子里面看到它)。因此,我认为主要目标是创建在公开的类型和成员方面与 ASP.NET 的验证程序保持一致的 Windows 窗体验证程序(如果该解决方案适用于 Windows 窗体)。除了在技术方面向 ASP.NET 小组表示敬意之外,该选择还可以增进应用不同开发模式的开发人员之间的了解。ASP.NET 当前提供了下表列出的验证程序。

验证控件 说明

RequiredFieldValidator

确保字段包含值

RegularExpressionValidator

使用正则表达式验证字段的格式

CompareValidator

对目标字段和其他字段或值进行等同性测试

RangeValidator

测试字段属于某种特殊的类型并/或在指定的范围内

CustomValidator

为复杂程度超过其他验证程序处理能力的自定义验证逻辑创建一个服务器端事件处理程序

此外,我认为另一个目标是具有可扩展性,以便当提供的验证程序不能满足要求时,开发人员只需进行少量的工作便能将他们自己的自定义验证程序并入基础结构中。最后,为减少工作量,实现应利用我们先前讨论的现有 Windows 窗体验证基础结构。

返回页首返回页首


必需字段验证程序简介

明确以上目标之后,现在我们开始了解具体的操作细节。首先,我们将创建一个最简单的验证程序,即 RequiredFieldValidator

Visual Studio .NET 解决方案

我们将需要一个 Visual Studio® .NET 解决方案来实现 RequiredFieldValidator。生成组件或控件库时,我倾向于采用“两个项目”方法,即将解决方案拆分为一个组件或控件项目和一个相应的测试工具项目,方法是:

1.

创建一个解决方案 (winforms03162004_CustomValidation)

2.

向解决方案 CustomValidationSample 中添加一个 Windows Forms Application 项目

3.

向解决方案 CustomValidation 中添加一个 Windows Forms Control 项目

4.

CustomValidation 项目中删除默认的 UserControl1

5.

添加一个名为 RequiredFieldValidator 的新类,并使其从 System.ComponentModel.Component 中派生

注意 由于这些验证组件不局限于 Whidbey,因此我使用了 Visual Studio .NET 2003.NET Framework 1.1

接口

保持一致性要求实现同样适用于 Windows 窗体的接口。这意味着实现由 ASP.NET RequiredFieldValidator 公开的相同成员:

class RequiredFieldValidator : Component {
  string ControlToValidate {get; set;}
  string ErrorMessage {get; set;}
  string InitialValue {get; set;}
  bool IsValid {get; set;}
  void Validate();
}

下表详细描述了每个成员。

成员 说明

ControlToValidate

指定要验证的控件。

ErrorMessage

ControlToValidate 无效时显示的消息。默认值是 ""。

InitialValue

强制值并不一定意味着 "" 以外的值。某些情况下,默认的控件值可能用于提示,例如在 ListBox 控件中使用 "[Choose a value]"。这种情况下,所需的值必须不同于初始值 "[Choose a value]".。InitialValue 支持此项要求。默认值为 ""。

IsValid

调用 Validate 后,报告 ControlToValidate’s 数据是否有效。同 ASP.NET 实现一样,当需要覆盖 Validate 时,可以从客户端代码设置 IsValid。默认值为 True。

Validate

验证 ControlToValidate’s 值并将结果存储到 IsValid 中。

在 ASP.NET 中,ControlToValidate 是一个用于为服务器端控件实例的 ViewState 编制索引的字符串在处理 Web 应用程序的基于请求的无连接特性时需要这种间接的方法。由于在 Windows 窗体中不需要做同样的考虑,因此可以直接引用该控件。此外,由于 ErrorProvider 将在内部使用,因此我们将添加一个 Icon 属性以供开发人员进行配置。明确以上要求后,需要对接口进行如下调整:

class RequiredFieldValidator : Component {
  ...
  Control ControlToValidate {get; set;}
  Icon Icon {get; set;}
  ...
}

实现

以下是最终的实现:

class RequiredFieldValidator : Component {
  // 专用字段
  ...
  Control ControlToValidate
    get { return _controlToValidate; }
    set {
      _controlToValidate = value;
      // 运行时(即不从 VS.NET 中)关联 ControlToValidate 的
      // Validating 事件
      if( (_controlToValidate != null) && (!DesignMode) ) {
        _controlToValidate.Validating +=
        new CancelEventHandler(ControlToValidate_Validating);
      }
    }
  }
  string ErrorMessage {
    get { return _errorMessage; }
    set { _errorMessage = value; }
  }
  Icon Icon {
    get { return _icon; }
    set { _icon = value; }
  }
  string InitialValue {
    get { return _initialValue; }
    set { _initialValue = value; }
  }
  bool IsValid {
    get { return _isValid; }
    set { _isValid = value; }
  }
  void Validate() {
    // 如果与初始值(该值不一定是空字符串)不同,
    // 则有效
    string controlValue = ControlToValidate.Text.Trim();
    string initialValue;
    if( _initialValue == null ) initialValue = "";
    else initialValue = _initialValue.Trim();
    _isValid = (controlValue != initialValue);
    // 如果 ControlToValidate 无效,则显示错误消息
    string errorMessage = "";
    if( !_isValid ) {
      errorMessage = _errorMessage;
      _errorProvider.Icon = _icon;
    }
    _errorProvider.SetError(_controlToValidate, errorMessage);
  }
  private void ControlToValidate_Validating(
    object sender,
    CancelEventArgs e) {
    // 无效时不取消,因为我们不希望在无效时
    // 强制焦点保留在 ControlToValidate 上
    Validate();
  }
}

该实现的关键是如何将其关联到 ControlToValidateValidating 事件:

Control ControlToValidate {
  get {...}
  set {
    ...
    // 运行时(即不在 VS.NET 设计时)关联 ControlToValidate 的
    //  Validating 事件
    if( (_controlToValidate != null) && (!DesignMode) ) {
      _controlToValidate.Validating +=
        new CancelEventHandler(ControlToValidate_Validating);
  }
}

这使得能够在输入数据时动态执行所需的字段验证,如同标准的 Windows 窗体实现一样。此外,还获得了并不明显的额外好处。在初始实现中,焦点保留在控件中,直到该控件有效,即直到它的 Validating 事件的 CancelEventArgs.Cancel 属性设置为 False(即在该控件中捕获用户)。数据输入 UI 在任何地方都不应捕获用户,该设计思想在 ControlToValidate_Validating 中得到反映,即 ControlToValidate_Validating 作为其自身验证的引发器(而不是作为与基础验证结构集成的机制)来处理该事件。图 5 显示了已更新的进程。


5. 已更新的验证进程

设计时集成

除设计时方面以外,该实现的功能性方面是完整的。所有优秀的 Visual Studio .NET 设计时成员都使用各种功能指定它们的设计时行为。例如,RequiredFieldValidator 使用 ToolboxBitmapAttribute 指定在 Toolbox Component Tray 中显示的自定义图标,并使用 CategoryAttributeDescriptionAttribute 使 RequiredFieldValidator 的公共属性在“属性浏览器”中更具有描述性:

[ToolboxBitmap(
  typeof(RequiredFieldValidator),
  "RequiredFieldValidator.ico")]
class RequiredFieldValidator : Component {
  ...
  [Category("Behaviour")]
  [Description("Gets or sets the control to validate.")]
  Control ControlToValidate {...}
  [Category("Appearance")]
  [Description("Gets or sets the text for the error message.")]
  string ErrorMessage {...}
  [Category("Appearance")]
  [Description("Gets or sets the Icon to display ErrorMessage.")]
  Icon Icon {...}
  [Category("Behaviour")]
  [Description("Gets or sets the default value to validate against.")]
  string InitialValue  {...}
}

完整的实现使用其他几个设计时功能(这些功能超出了本文介绍的范畴),包括:

指定从“属性浏览器”中可以为 ControlToValidate 选择的控件。 该实现允许使用 TextBoxListBoxComboBoxUserControl 控件

从“属性浏览器”中隐藏 IsValid,因为它是只限运行时属性(所有公共属性在设计时自动显示在 Property Browser 中的)。

要了解有关设计时的详细介绍,请参阅以下文章:

Building Windows Forms Controls and Components with Rich Design-Time Features, Part 1

Building Windows Forms Controls and Components with Rich Design-Time Features, Part 2

使用 RequiredFieldValidator

要使用 RequiredFieldValidator,首先应将其添加到 Toolbox,然后才能将它拖动到窗体并对其进行配置。为此,应执行以下操作:

1.

重新生成解决方案。

2.

在 Visual Studio .NET 中打开测试窗体。

3.

右键单击 Toolbox 并选择“添加选项卡”。

4.

创建一个名为 Custom Validation 的选项卡并选中它。

5.

右键单击 Toolbox 并选择“添加/删除项”。

6.

浏览到新生成的组件程序集 (CustomValidation.dll) 并选中它。

通过以上步骤可以在 Windows 窗体设计器中该使用组件,如图 6 所示。


6. 包含运行时属性的 RequiredFieldValidator 演示

我对 Add New Employee 窗体中的所有字段都应用了 RequiredFieldValidator,并包含有关用户可以在每个字段中输入的值类型的提示。图 7 显示它在运行时的工作方式。


7. 运行时的乐趣

返回页首返回页首


BaseValidator:分治法

有了 RequiredFieldValidator,便可以轻松地实现其余的验证程序。但也许并不会如此轻松。RequiredFieldValidator 将特定的“必需”验证逻辑与每个验证程序需要实现的逻辑的余下主要部分(如指定要验证的控件)紧密关联在一起。这种情况下,一个适当的解决方案是将 RequiredFieldValidator 分成两个新类型,即 BaseValidator 和由其派生且比较简单的 RequiredFieldValidator。严格地说,从一致性角度考虑,我们还应创建 IValidator 接口并通过与 ASP.NET 相同的方式实现 BaseValidator。然而,该方法提供的可扩展性的程度超出了我们当前的需要。BaseValidator 实现与原始的 RequiredFieldValidator 相类似的验证逻辑的可重用部分:

abstract class BaseValidator : Component, IValidator {
  Control ControlToValidate {...}
  string ErrorMessage {...}
  Icon Icon {...}
  bool IsValid {...}
  void Validate() {...}
  private void ControlToValidate_Validating(...) {...}
}

尽管 IsValidValidate 是所有验证程序所共有的,但它们在新基础结构中只是作为一种形式存在,从而将特定的验证留给了每个派生的验证程序。因此,BaseValidator 需要一种机制,通过该机制它能够“询问”派生的验证程序,它是否有效。这是通过一个额外的抽象方法 EvaluateIsValid 实现的。EvaluateIsValidBaseValidator 都是抽象的,以确保每个派生的验证程序都知道它们能够回答这个问题。新添加部分要求一些重构,如下所示:

abstract class BaseValidator : Component {
  ...
  void Validate() {
    ...
    _isValid = EvaluateIsValid();
    ...
  }
  protected abstract bool EvaluateIsValid();
}

结果是 BaseValidator 只能由派生方法使用,因此必须实现 EvaluateIsValidValidate 方法已经过更新,可以向下调用派生的 EvaluateIsValid 以查询其是否有效。该项技术还应用于 ASP.NET 控件,从而增强了所需的一致性。

RequiredFieldValidator:Redux

完成 BaseValidator 后,我们现在需要重构 RequiredFieldValidator,以实现其自身的功能,即从 BaseValidator 派生并在 EvaluateIsValidInitialValue 中实现指定的必须字段验证位。本着以简代蘩的原则,编写了下面这个更简单的新 RequiredFieldValidator

[ToolboxBitmap(
  typeof(RequiredFieldValidator),
  "RequiredFieldValidator.ico")]
class RequiredFieldValidator : BaseValidator {
  string InitialValue  {...}
  protected override bool EvaluateIsValid() {
    string controlValue = ControlToValidate.Text.Trim();
    string initialValue;
    if( _initialValue == null ) initialValue = "";
    else initialValue = _initialValue.Trim();
    return (controlValue != initialValue);
  }
}
返回页首返回页首


一个放便士,一个放英镑

在基类与派生类之间合理地分隔普通验证功能与特定验证功能使我们能够将注意力只集中在特定的验证上。这不但适用于 RequiredFieldValidator,而且对其余几个用于与 ASP.NET 保持一致而应实现的验证程序更为适用,这些验证程序包括:

RegularExpressionValidator

CustomValidator

CompareValidator

RangeValidator

让我们了解一下每个验证程序以及用于实现它们的逻辑。

RegularExpressionValidator

正则表达式是一个基于大量令牌而构建的功能强大的文本模式匹配技术。当字段需要特定形式的值时,正则表达式是优秀的字符串操作替代方法,尤其是对于重要的文本模式。例如,确保电话号码符合澳大利亚格式((xx) xxxx xxxx)可以简化为一个正则表达式:

^\(\d{2}\) \d{4} \d{4}$

其好处是为什么 ASP.NET 实现 RegularExpressionValidator,并且我们也会实现:

using System.Text.RegularExpressions;
...
[ToolboxBitmap(
  typeof(RegularExpressionValidator),
  "RegularExpressionValidator.ico")]
class RegularExpressionValidator : BaseValidator {
  ...
  string ValidationExpression {...}
  protected override bool EvaluateIsValid() {
    // 如果为空,则不验证
    if( ControlToValidate.Text.Trim() == "" ) return true;
    // 如果与 ControlToValidate 的整个文本匹配,则成功
    string field = ControlToValidate.Text.Trim();
    return Regex.IsMatch(field, _validationExpression.Trim());
  }
}

在设计时,开发人员可以通过属性浏览器提供验证表达式,如图 8 所示。


8. 指定验证的正则表达式

CustomValidator

有时,特定验证程序无法解决特定的验证问题,尤其是当复杂的业务规则为需要数据库访问时。这种情况下,可以编写自定义代码,且 CustomValidator 使我们能够编写该自定义代码并确保它与自定义验证基础结构集成在一起,这对于以后的窗体范围的验证是很重要的。CustomValidator 包含的 Validating 事件和 ValidatingCancelEventArgs 可以满足此要求:

[ToolboxBitmap(typeof(CustomValidator), "CustomValidator.ico")]
class CustomValidator : BaseValidator {
  class ValidatingCancelEventArgs {...}
  delegate void ValidatingEventHandler(object sender, ValidatingCancelEventArgs e);
  event ValidatingEventHandler Validating;
  void OnValidating(ValidatingCancelEventArgs e) {
    if( Validating != null ) Validating(this, e);
  }

  protected override bool EvaluateIsValid() {
    // 将验证进程传递至事件处理程序并等待响应
    ValidatingCancelEventArgs args =
      new ValidatingCancelEventArgs(false,ControlToValidate);
    OnValidating(args);
    return args.Valid;
  }
}

要处理 CustomValidatorValidating 事件,在“属性浏览器”中双击该事件,如图 9 所示:


9. 为自定义的复杂验证创建验证处理程序

然后,只需添加相应的验证逻辑,以下示例中的验证逻辑确保新员工的年龄大于或等于 18 岁:

class AddNewEmployeeForm : Form
  void cstmDOB_Validating(object sender, ValidatingCancelEventArgs e) {
  try {
    // 必须大于或等于 18 岁
    DateTime dob = DateTime.Parse(((Control)e.ControlToValidate).Text);
    DateTime  legal = DateTime.Now.AddYears(-18);
    e.Valid = ( dob < legal );
  }
  catch( Exception ex ) { e.Valid = false; }
}

注意CustomValidator 根据 ASP.NET 的服务器端处理特性命名其等价事件 ServerValidate。我们之所以使用 Validating,是因为我们不需要做这种区分,且 BaseValidator 已经使用了 Validate 派生于的 Validate

BaseCompareValidator

该验证程序只能处理一个字段。然而,在某些情况下,验证可能涉及多个字段和/或值、是否确保某个字段的值在最大/最小值范围内 (RangeValidator) 或将一个字段与另一个字段进行比较 (CompareValidator)。不论在哪种情况下,都需要进行额外的工作以执行类型检查、转换和比较,其中包括验证时必须参照的类型。该功能应封装在新类型 BaseCompareValidator 中,从该类型可以派生出 RangeValidatorCompareValidator 以节省某些额外的工作。BaseCompareValidator 自身派生自 BaseValidator,以获取基本的验证支持并实现四个新成员:

abstract class BaseCompareValidator : BaseValidator {
  ...
  ValidationDataType Type {...}
  protected TypeConverter TypeConverter {...}
  protected bool CanConvert(string value) {...}
  protected string Format(string value) {...}
}

ValidationDataType 是自定义枚举,用于指定支持比较和范围验证的类型:

enum ValidationDataType {
  Currency,
  Date,
  Double,
  Integer,
  String
}

RangeValidator

在需要确定某个字段值是否在指定范围内的情况下,可以使用 RangeValidator。它允许开发人员输入最小值和最大值以及适用于所有相关值的类型。在以下示例中,RangeValidator 派生自 BaseCompareValidator

[ToolboxBitmap(typeof(RangeValidator), "RangeValidator.ico")]
class RangeValidator : BaseCompareValidator {
  ...
  string MinimumValue {...}
  string MaximumValue {...}
  protected override bool EvaluateIsValid() {
      // 如果为空,则不验证,除非要求进行验证
      // 验证并转换最大值
      // 验证并转换最大值
      // 检查最小值是否 <= 最大值
      // 检查并转换 ControlToValue
      // 是否最小值 <= 值 <= 最大值
    }
  }

“属性浏览器”用于配置范围和类型属性,如图 10 所示。


10. 配置 RangeValidator

CompareValidator

最后介绍的是 CompareValidator(将它放在最后介绍并不意味着它的重要性最低),CompareValidator 用于针对 ControlToValidate 以及其它控件或值执行等同性测试。控件或值的等同性测试分别由 ControlToCompareValueToCompare 指定:

[ToolboxBitmap(typeof(CompareValidator), "CompareValidator.ico")]
class CompareValidator : BaseCompareValidator {
  ...
  Control ControlToCompare {...}
  ValidationCompareOperator Operator {...}
  string ValueToCompare {...}
  protected override bool EvaluateIsValid() {
    // 如果为空,则不验证,除非要求进行验证
    // 检查提供的 ControlToCompare
    // 检查提供的 ValueToCompare
    // 验证并转换 CompareFrom
    // 验证并转换 CompareTo
    // 执行比较,如 ==, >, >=, <, <=, !=
  }
}

Operator 定义在 ControlToValidateControlToCompareValueToCompare 中执行的等同性测试。OperatorValidationCompareOperator 指定:

enum ValidationCompareOperator {
  DataTypeCheck,
  Equal,
  GreaterThan,
  GreaterThanEqual,
  LessThan,
  LessThanEqual,
  NotEqual
}

正如您所知,还可以使用 CompareValidator 以验证 ControlToValidate 是否是特殊的数据类型(由 DataTypeCheck 值指定)。这是唯一一个既不需要 ControlToCompare 也不需要 ValueToCompare 的比较。图 11 显示如何配置 CompareValidator


11. 配置 CompareValidator

返回页首返回页首


已完成的自定义验证基础结构

至此,所有与 ASP.NET 等价的验证程序组件均已列出并予以了说明。每个实现都直接或间接地从 BaseValidator 派生以继承基本的、可重复使用的验证功能。此类继承创建了可隐式扩展的基础结构,其他开发人员可以像我们在本文中那样轻松地使用该基础结构。图 12 显示了该自定义验证解决方案以及开发人员可以在哪些情况下使用它。必须使用该自定义验证解决方案的情况包括:

创建新的普通验证程序

创建业务规则特定的验证程序

向一个现有的验证程序中添加额外的相关逻辑

图 12 显示了该设计为这些情况提供的可扩展支持。


12. 自定义验证的可扩展支持

返回页首返回页首


结论

首先,我们介绍了内置于 .NET Framework 的 Windows 窗体中的验证框架。然后,我们将验证逻辑重新封装到多个设计时组件中,以将最初的程序验证进程转换为更具声明性、且效率更高的进程。在此过程中,我们基于 BaseValidator 创建了可扩展的验证基础结构,以实现 RequiredFieldValidatorRegularExpressionValidatorCustomValidatorCompareValidatorRangeValidator。该设计允许其他开发人员能够轻松地创建新验证程序,方法也是从 BaseValidator 派生以继承普通功能,并将实现问题的范围缩小至特定的验证。本文主要介绍了按控件验证,并且只对窗体范围的验证提供了说明。下一篇文章将在自定义验证的基础之上并入窗体范围的验证,介绍如何组合现有的验证程序并添加 ASP.NET 验证控件集合的另一个主要部分,即 ValidationSummary 控件。现在,我建议您看一看 Amazon Women on the Moon,仅仅为找些笑料。

返回页首返回页首


其他自定义验证解决方案

我最初为 Genghis 创建验证组件要追溯到 2002 年下半年。在研究所参考的 MSDN 杂志中有关设计时的文章时,我发现了 Billy Hollis 在他(以及 Rocky)的 MSDN Online 专栏中提供的验证程序片段,其网址是 Adventures in Visual Basic .NET。强烈建议您阅读一下该文章,创建一个出色的 Windows 窗体验证方案,以代替我创建的那一个,该文章还深入介绍了如何在实现设计时的扩展属性问题。

返回页首返回页首


C# 与 Visual Basic .NET

我前面写的两篇文章强调了文中编写的代码片段只适用于 C#,而该专栏的许多读者是 Visual Basic® .NET 开发人员。本人对 C# 偏爱有加,但同时也很乐于为 Visual Basic.NET 编写程序。我原本希望同时提供 Visual Basic .NET 和 C# 两种版本的代码实例,但由于时间方面的原因,未能将两者合并在一起并写入各自的专栏。但我不想排除两者中的任何一个,作为一种折衷方法,我将在撰写的每一篇文章中,在 Visual Basic .NET 和 C# 代码之间进行转换。这会使得第一个针对 Visual Basic .NET 的程序片段显示在两篇文章中(下一个验证程序片段将继续从此处开始的 C# 工作)。在此项工作的时间内,很乐意保持有关两种代码的交流。该解决方案是否满足需要?请给我发邮件并告诉我您的想法。

返回页首返回页首


Genghis

一个更完整并与该解决方案稍有不同的解决方案当前可以在 Genghis 中获得,Genghis 是一个在 Microsoft Foundation Classes (MFC) 上服务于 .NET Windows 窗体开发人员的共享源代码集合。有趣的是,我在编写本文时,有些地方需要进行改进,但这些改进将在 Genghis 的下一版中反映出来。但是,如果您已经迫不及待,则可以随时下载本文,如果您有时间,请将错误或希望改进的地方发送给我。

返回页首返回页首


致谢

本文受到了 Ron Green、Chris Sells 和 Amazon Women on the Moon 的启发。

返回页首返回页首


参考资料

Billy Hollis 所著的 Validator Controls for Windows Forms

有关如何开发面向设计时的组件和控件的深入介绍,请参阅:

Michael Weinhardt 和 Chris Sells 所著的 Building Windows Forms Controls and Components with Rich Design-Time Features, Part 1,MSDN Magazine,2003 年 4 月

Michael Weinhardt 和 Chris Sells 所著的 Building Windows Forms Controls and Components with Rich Design-Time Features, Part 2,MSDN Magazine,2003 年 5 月

如果您对正则表达式不太熟悉,请参阅以下资料:

Jeffery E. F. Friedl, O’Reilly 所著的 Mastering Regular Expressions, Second Edition,2003 年

Michael Weinhardt 和 Chris Sells 所著的 Regular Expressions in .NET,Windows Developer Magazine,2002 年 12 月

Michael Weinhardt 当前从事全职的不同 .NET 程序编写工作,包括与 Chris Sells 合作撰写 Windows Forms Programming in C#, 2nd Edition (Addison Wesley) 以及撰写该专栏。总而言之,Michael 迷恋 .NET,尤其是 Windows 窗体,并在可能的时候看 80 集电视剧。有关详细信息,请访问 http://www.mikedub.net/

转到原英文页面

© 2004 Microsoft Corporation 版权所有。保留所有权利。使用规定。

创建分布式应用程序

ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.2052/vsintro7/html/vbwlkCreatingDistributedWebApplicationWalkthrough.htm

请参见 Visual Studio  

演练:创建分布式应用程序

在本演练中,您将创建一个多层的分布式 Intranet 应用程序。此应用程序由三个逻辑层组成:数据层、业务对象层和用户界面层。数据层是 SQL Server 中的数据库。业务对象层处理如何访问数据以及如何将数据分发到客户端。用户界面层由基于 Web 的应用程序和传统的 Windows 应用程序组成。下图描述了此应用程序的结构。

您将创建的分布式应用程序的结构

将要生成的应用程序是具有查找和编辑功能的简单数据应用程序。您将生成一个既是 Windows 又基于 Web 的客户端,以显示 SQL Server Pubs 示例数据库中的 Authors 表。对于 Web 部分,您将通过 Web 窗体设计器创建一个与标准 HTML 3.2 浏览器兼容的 Web 页。在服务器上,Web 窗体代码将调用 XML Web services 以从数据库中检索包含作者信息的数据。对于 Windows 部分,您将生成一个 Windows 应用程序,它与同一个 XML Web services 通信以检索包含作者信息的数据集。使用 HTTP 和 XML 处理与此 XML Web services 的通信。

您将在同一个服务器上创建 XML Web services 和客户端应用程序,即使数据库驻留在另一个服务器上。应用程序服务器和数据库服务器必须驻留在同一个 Intranet 上。这种项目结构使您得以使用 Windows 身份验证来访问数据库和测试应用程序。同时,这种结构移除了资源访问的许多详细信息,使您能够看到 XML Web services 和客户端应用程序之间的交互。这种结构可能与您自己创建的应用程序所需的结构不完全一样。有关其他安全模型和结构的更多信息,请参见 Web 应用程序的访问权限。

系统要求

若要完成本演练,您需要:

  • 对具有 Pubs SQL Server 示例数据库、配置了集成 Windows 身份验证的服务器的访问权限。Pubs 数据库是可以与 SQL Server 一起安装的示例数据库之一。
  • 基本了解在 Visual Studio .NET 中数据是如何处理的。有关更多信息,请参见 ADO.NET 数据访问介绍。

分布式应用程序的创建过程

开发分布式应用程序的一个可能的方案是一次创建一层,多半是从数据层开始,然后移到中间层业务规则对象,最后创建用户界面层。在本演练中,已经生成了数据,它们可用于 SQL Server 中的 Pubs 数据库。因此,本演练将从创建业务对象,即 XML Web services 开始,然后生成两个用户界面:Web 窗体页和 Windows 窗体。

本演练的过程如下:

  1. 创建中间层业务对象
    1. 创建 ASP.NET Web 服务项目
    2. 创建并配置数据库连接和数据集架构
    3. 从业务对象中公开数据集
  2. 创建用户界面
    1. 创建 Windows 用户界面
    2. 创建 Web 用户界面
  3. 部署解决方案或添加更多功能

创建中间层业务对象

您创建的业务对象将在 Web 服务器上运行,提供分布式应用程序所需的性能和可伸缩性。另外,您将把业务对象实现为 XML Web services,这样客户端可以使用标准 Internet 协议从任何平台与您的业务对象进行通信。有关详细信息,请参见 使用 XML Web services 进行 Web 编程。

在本演练中,XML Web services 组件将保存数据连接和数据集定义。然后将添加 XML Web services 方法以公开此数据集,这使其他应用程序能够查看和修改此数据集。

ASP.NET Web 服务项目模板是基于 Web 的项目,旨在创建将其自身的接口公开为 XML Web services 的中间层组件。有关更多信息,请参见 Visual Studio 中的 ASP.NET Web 服务项目。

XML Web services 将公开两个方法。第一个方法 GetAuthors 将从数据库返回数据集。第二个方法 UpdateAuthors 将用用户的更改更新数据库。创建了若干私有成员来实现这些方法。它们包括数据集定义、数据适配器、数据连接和若干从数据库中检索数据和更新数据库的命令对象。下面的关系图描述了 XML Web services。

分布式应用程序的中间层

创建 ASP.NET Web 服务项目

  1. 在“文件”菜单上,指向“新建”,然后单击“项目”以显示“新建项目”对话框。
  2. 根据要使用的语言,在“项目类型”窗格中选择“Visual Basic 项目”或“Visual C# 项目”,然后在“模板”窗格中选择“ASP.NET Web 服务”。
  3. 在“位置”框中,输入 Web 服务器(在您的开发计算机上)的名称和项目名称 http://ServerName/AuthorsWebService,然后单击“确定”。
    提示   如果 Web 服务器位于您的计算机上,则可以使用服务器名称 LOCALHOST。

    提示   如果在创建 ASP.NET Web 服务项目方面有困难,请参见 “Web 访问失败”对话框。

  4. 项目 AuthorsWebService 就添加到解决方案中。Service1.asmx 的“组件设计器”出现在开发环境中。
  5. 在解决方案资源管理器中,双击 Service1.asmx 以选择它。
  6. 在“属性”窗口中,将 Service1 Name 属性设置为 AuthorsService
  7. 在解决方案资源管理器中,右击 Service1.asmx 文件,选择“重命名”,将此文件重命名为 AuthorsService.asmx,以便与服务名相匹配。

    在此组件中,您将创建与数据存储区的连接并通过数据集获取数据实例。

创建和配置数据库连接和数据集架构

您将向 XML Web services 添加两个对象:一个 SqlDataAdapter 对象和一个 SqlConnection 对象。连接对象创建到数据库的新连接,而数据适配器则查询数据库并将结果发送到 DataSet 对象中。数据适配器还用用户对数据集所做的更改来更新数据库。通过使用这个到数据库的连接,将创建一个数据集来保存在数据库中存在的信息的实例。此数据集稍后将在本演练中用来显示 Windows 窗体和 Web 窗体页中的数据。有关概述,请参见 分布式应用程序和数据集成介绍。

创建数据库连接和数据适配器

  1. 单击服务器资源管理器。
    注意   默认情况下,服务器资源管理器在开发环境的左侧显示为一个垂直选项卡。如果看不到此选项卡,请单击“视图”菜单,然后选择“服务器资源管理器”。

  2. 在服务器资源管理器中,右击“数据连接”节点,然后从快捷菜单中选择“添加连接”。
  3. 在“数据链接属性”对话框的“连接”选项卡中,输入安装有 pubs 数据库的 SQL Server 的名称。如果在本地计算机上有 SQL Server,请输入 (local)
  4. 为登录信息选择“使用 Windows NT 集成安全设置”。
    注意   如果在您的系统上没有设置集成安全性,请与网络管理员联系。

  5. 从列表中选择 pubs 数据库。
  6. 单击“测试连接”验证您提供的信息,然后单击“确定”建立连接。

    一个新节点出现在服务器资源管理器的“数据连接”节点中。

    注意   如果数据库连接失败,请与数据库管理员联系。

  7. 在服务器资源管理器中,展开新连接节点的节点,然后展开“表”节点。
  8. 找到 authors 节点并展开它以显示 authors 表中的字段。
  9. 使用 CTRL+鼠标单击,选择 au_idau_lnameau_fnamecity 字段。
  10. 将这些字段从服务器资源管理器拖放到设计图面上。SqlConnection 对象与 SqlDataAdapter 对象在设计器中成对出现。现在已建立了到数据库的连接,信息传输将由 SqlDataAdapter 对象处理。这些组件被配置为将包含 authors 表的数据集移入和移出数据库。
    注意   本演练使用 SqlDataAdapter 对象,此对象已针对使用 SQL Server 7.0 或更高版本进行了优化。也可以使用 OleDbDataAdapter 对象,这是因为它更通用,并提供对任何与 OLE DB 兼容数据源的 ADO.NET 访问权。如果要连接的数据库不是 SQL Server 数据库,则项目系统会创建一个 OleDbDataAdapter 对象和一个 OleDbConnection 对象。

    注意   在本演练中,已使用 authors 表中的一些列和所有的行填充了数据集。在成品应用程序中,通常通过创建只返回所需列和行的查询来优化数据访问。有关示例,请参见 演练:使用参数化查询在 Windows 窗体中显示数据。

需要配置项目的安全设置以便使用集成安全性。通过关闭匿名访问并打开模拟来执行此操作。有关更多信息,请参见 安全模型。

配置集成 Windows 身份验证

若要为项目配置集成 Windows 身份验证,需要使用“Internet 信息服务”工具更改项目文件并配置项目。

  1. 启动“Internet 信息服务”工具。可以从“控制面板”的“管理工具”中运行它。(有关启动此工具的更多信息,请参见 Windows 帮助文档。)
  2. 展开服务器的节点。
  3. 展开“默认 Web 站点”节点。
  4. 右击 AuthorsWebService 的节点并从快捷菜单中选择“属性”。
  5. 单击“目录安全性”选项卡。
  6. 单击“匿名访问和身份验证控制”节中的“编辑”按钮。
  7. 清除“匿名访问”复选框。
  8. 选中“集成 Windows 身份验证”复选框。现在已配置了 XML Web services 目录。
  9. 返回 Visual Studio 中的项目,在解决方案资源管理器中双击 Web.config 文件。
  10. <system.web> 标记后面的行中添加下面的标记,以便为 XML Web services 配置集成安全性。
    <identity impersonate="true"/>

创建数据集类定义

  1. 在解决方案资源管理器中,双击 AuthorsService 文件以在设计器中打开它。
  2. 从“数据”菜单中选择“生成数据集”。在“生成数据集”对话框中,选择“新建”并将数据集命名为“authors1”。不要选中标记为“将此数据集添加到设计器”的框。
    注意   将创建数据集架构文件 authors1.xsd 并将其添加到项目中。此架构文件包含 authors1 的类定义。该类(从 DataSet 类继承)包含 authors 表的类型化数据集定义。

  3. 从“文件”菜单中,选择“全部保存”。

从业务对象中公开 authors1 数据集

本演练的下一步是从业务对象公开刚创建的数据集对象。此操作使数据集可供 Windows 或 Web 应用程序使用。

将方法添加到 XML Web services

  1. 在解决方案资源管理器中,如果还没有在设计器中打开 AuthorsService,请双击它。
  2. 在“视图”菜单上单击“代码”。
  3. 添加一个名为 GetAuthors 的方法以向客户端传递一个数据集。

    如下所示,此方法创建一个新的 authors1 数据集,并根据 authors 表使用 SqlDataAdapter 对象填充该数据集。然后此方法返回数据集。

    ' Visual Basic
    <WebMethod> Public Function GetAuthors() As authors1
       Dim authors As New authors1()
       SqlDataAdapter1.Fill(authors)
       Return authors
    End Function
    
    // C#
    [WebMethod]
    public authors1 GetAuthors()
    {
       authors1 authors = new authors1();
       sqlDataAdapter1.Fill(authors);
       return authors;
    }
  4. 添加名为 UpdateAuthors 的方法,以便将更改从客户端传递回数据库。

    如下所示,此方法有一个 authors1 数据集参数 (authorChanges),该参数包含已更改的数据并通过 SqlDataAdapter.Update 方法更新数据库。Update 方法接受数据集中的更改。数据集返回给客户端。客户端然后用这个返回的数据集更新它自己的 authors1 数据集实例。有关 Update 方法和如何接受数据集更改的信息,请参见 数据适配器介绍。

    ' Visual Basic
    <WebMethod> Public Function UpdateAuthors(ByVal authorChanges _
    As authors1) As authors1
       If Not (authorChanges Is Nothing) Then
          SqlDataAdapter1.Update(authorChanges)
          Return authorChanges
       Else
          Return Nothing
       End If
    End Function
    
    // C#
    [WebMethod]
    public authors1 UpdateAuthors(authors1 authorChanges)
    {
       if (authorChanges != null)
       {
          sqlDataAdapter1.Update(authorChanges);
          return authorChanges;
       }
       else
       {
          return null;
       }
    }

    注意   在成品应用程序中,将向这些方法添加错误检查和异常处理功能。

  5. 从“文件”菜单中,选择“全部保存”。
  6. 从“生成”菜单中,选择“生成解决方案”。

在上一节中,您创建了一个中间层业务对象,该对象包含绑定到 SQL Server 数据库的数据集。您向中间层 AuthorsWebService XML Web services 中添加了代码,以便从数据源获取数据并用更改更新数据源。客户端通过 GetAuthorsUpdateAuthors XML Web services 方法访问这些函数。

创建用户界面

创建了用于数据访问的中间层业务对象并将其公开为 XML Web services 后,下一步是创建客户端界面。本演练中有两个方案:传统的 Windows 窗体和 Web 窗体页。两个方案在此示例中被创建为同一解决方案中的不同项目。

Windows 用户界面选项

Windows 界面使用客户机的功能来处理部分应用程序处理工作。通常,Windows 界面比基于 Web 的界面提供更完善的功能和更丰富的用户操作。服务器上的负载比 Web 前端的负载少,这是因为服务器不必执行所有的应用程序逻辑。另外,Windows 界面可以利用通过操作系统可用的资源,包括对文件系统和注册表的调用。

下列关系图突出显示要实现的客户端。

分布式应用程序中的 Windows 客户端

Windows 应用程序(由一个窗体 Windows 窗体组成)将包含一个对 AuthorsWebService 的 Web 引用。单击窗体上的“Load”(加载)按钮时,数据库中的数据将显示在 DataGrid 控件中。这一加载行为是通过调用 XML Web services 的 GetAuthors 方法实现的。DataGrid 控件允许直接编辑,并将数据更改直接传递到基础数据集。此窗体还会有一个“Save”(保存)按钮。此按钮的代码将调用 XML Web services 的 UpdateAuthors 方法,以便将更改保存回数据库。

创建 Windows 应用程序

  1. 在“文件”菜单上,指向“添加项目”,然后单击“新建项目”以打开“添加新项目”对话框。
  2. 根据要使用的语言,在“项目类型”窗格中选择“Visual C# 项目”或“Visual Basic 项目”,然后在“模板”窗格中选择“Windows 应用程序”。
  3. 将项目命名为 AuthorsWinClient,并为该项目选择一个位置。

    AuthorsWinClient 项目随即添加到解决方案中。Form1 自动添加到项目中并且出现在 Windows 窗体设计器中。

  4. 为前面创建的 ASP.NET Web 服务项目添加 Web 引用:
    1. 在解决方案资源管理器中,右击 AuthorsWinClient 项目,然后单击快捷菜单上的“添加 Web 引用”。
    2. 在“添加 Web 引用”对话框顶部的“URL”框中,输入 XML Web services 项目的位置 (http://ServerName/AuthorsWebService/AuthorsService.asmx),然后按 ENTER 键。如果在添加 Web 引用方面有困难,请参见“添加 Web 引用”对话框添加和移除 Web 引用
    3. 单击“添加引用”。
    4. 现在可以在应用程序中创建 authors1 数据集的实例。

将控件添加到窗体

  1. 从“工具箱”的“Windows 窗体”选项卡中将 DataGrid 控件拖到窗体上。
  2. 从“工具箱”的“Windows 窗体”选项卡中将 Button 控件拖到窗体上。将此按钮的 Name 属性设置为 LoadData,将它的 Text 属性设置为 Load
  3. 从“工具箱”的“Windows 窗体”选项卡中将另一个 Button 控件拖到窗体上。将此按钮的 Name 属性设置为 SaveData,将它的 Text 属性设置为 Save
  4. 从工具箱的“数据”选项卡中将 DataSet 对象拖到窗体上。出现“添加数据集”对话框。选择“类型化数据集”并从“名称”列表中选择“AuthorsWinClient.ServerName.authors1”。此操作将在组件栏中创建一个基于 authors1 数据集类定义的 DataSet 对象。
  5. 选择 DataSet 控件并将 Name 属性设置为 AuthorData
  6. 选择 DataGrid 控件并从 DataSource 属性列表中选择 AuthorData。从 DataMember 属性列表中选择 authorsDataGrid 的列标题被设置为 authors 表的列名称。

为 LoadData 和 SaveData 按钮添加代码

  1. 在“视图”菜单上单击“设计器”。双击 LoadData 按钮为 Click 事件创建一个空事件处理程序。调用 XML Web services 方法的方法是先创建服务类的一个实例,然后调用服务方法。在这种情况下,调用 GetAuthors 方法。返回的数据集会与 AuthorData 数据集合并,XML Web services 的 Credentials 属性用于将您的身份传递给 XML Web services,该服务接着将它传递给数据库服务器。将如下所示的代码添加到方法中。
    注意   如果 XML Web services 不是在本地计算机上运行的,则需要用运行 XML Web services 的服务器名称替换代码示例中的 localhost

    ' Visual Basic
    Private Sub LoadData_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles LoadData.Click
       Dim ws As New AuthorsWinClient.localhost.AuthorsService()
       ws.Credentials = System.Net.CredentialCache.DefaultCredentials
       AuthorData.Merge(ws.GetAuthors())
    End Sub
    
    // C#
    private void LoadData_Click(object sender, System.EventArgs e)
    {
       AuthorsWinClient.localhost.AuthorsService ws =
          new AuthorsWinClient.localhost.AuthorsService();
       ws.Credentials = System.Net.CredentialCache.DefaultCredentials;
       AuthorData.Merge(ws.GetAuthors());
    }
  2. 在“视图”菜单上单击“设计器”。双击 SaveData 按钮为 Click 事件创建一个空事件处理程序。

    如果数据集有改动,则将创建一个 authors1 类型的新数据集来仅保存更改的数据。然后,此数据集被传递到 XML Web services 的 UpdateAuthors 方法。此数据集与接受的更改一起返回,并且更新 AuthorData 数据集以反映这些新的更改。有关接受数据集更改的更多信息,请参见 数据适配器介绍。

    注意   在成品应用程序中,将需要在这一步中考虑数据并发问题。有关更多信息,请参见 介绍 ADO.NET 中的数据并发。

    将以下代码添加到方法中:

    ' Visual Basic
    Private Sub SaveData_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles SaveData.Click
       If AuthorData.HasChanges() Then
          Dim ws As New AuthorsWinClient.localhost.AuthorsService()
          ws.Credentials = System.Net.CredentialCache.DefaultCredentials
          Dim diffAuthors As New AuthorsWinClient.localhost.authors1()
          diffAuthors.Merge(AuthorData.GetChanges())
          diffAuthors = ws.UpdateAuthors(diffAuthors)
          AuthorData.Merge(diffAuthors)
       End If
    End Sub
    
    // C#
    private void SaveData_Click(object sender, System.EventArgs e)
    {
       if (AuthorData.HasChanges())
       {
          AuthorsWinClient.localhost.AuthorsService ws =
             new AuthorsWinClient.localhost.AuthorsService();
          ws.Credentials = System.Net.CredentialCache.DefaultCredentials;
          AuthorsWinClient.localhost.authors1 diffAuthors
             = new AuthorsWinClient.localhost.authors1();
          diffAuthors.Merge(AuthorData.GetChanges());
          diffAuthors = ws.UpdateAuthors(diffAuthors);
          AuthorData.Merge(diffAuthors);
       }
    }

运行应用程序

  1. 从“文件”菜单中,选择“全部保存”。
  2. 在解决方案资源管理器中选择“AuthorsWinClient”,右击并选择“设为启动项目”。
  3. 按 CTRL+F5 以运行应用程序。

    显示出一个窗口,其中包含一个空表,表的标头来自 pubs 数据库中的 authors 表。

  4. 单击“加载”填充此表,做一些更改,然后单击 Save 保存所做的更改。

在上一节中,您在解决方案中添加了一个 Windows 窗体项目作为 Windows 界面。将 Windows 窗体连接到了在第一节中创建的 XML Web services,并使用 DataGrid Button 控件创建了用于加载和更新数据的用户界面。在下一节中,您将为解决方案创建基于 Web 的用户界面。

Web 用户界面选项

Web 界面有助于应用程序访问各种不同的客户机和浏览器。所有用户界面处理都在 Web 服务器上而不是在客户端完成。您将使用 Web 窗体页来创建一个 Web 界面,与上一节中创建的 Windows 界面一样,此界面访问同一个中间层业务对象。下列关系图突出显示要实现的客户端。

分布式应用程序中的 Web 客户端

创建 Web 窗体应用程序

  1. 在“文件”菜单上指向“添加项目”,然后单击“新建项目”。
  2. 在“添加新项目”对话框中,在“项目类型”窗格中选择“Visual C# 项目”或“Visual Basic 项目”,然后在“模板”窗格中单击“ASP.NET Web 应用程序”。
  3. 在“位置”框中,输入 Web 服务器(在您的开发计算机上)的名称和项目名称 http://ServerName/AuthorsWebClient,然后单击“确定”。

    新项目添加到解决方案资源管理器。一个名为 WebForm1.aspx 的 Web 窗体页添加到项目中并加载到设计器中。

  4. 为前面创建的 ASP.NET Web 服务项目添加 Web 引用:
    1. 在解决方案资源管理器中,右击 AuthorsWebClient 项目,然后单击快捷菜单上的“添加 Web 引用”。
    2. 在“添加 Web 引用”对话框顶部的“URL”框中,输入 XML Web services 项目的位置 (http://ServerName/AuthorsWebService/AuthorsService.asmx),然后按 ENTER 键。
    3. 单击“添加引用”。
    4. 现在可以在应用程序中创建 authors1 数据集的实例。

将控件添加到 Web 页

  1. 从工具箱的“数据”选项卡中将 DataSet 对象拖到窗体上。出现“选择数据集”对话框。选择“TypedDataSet”并从“名称”列表中选择“AuthorsWebClient.ServerName.authors1”。一个 DataSet 对象被添加到组件栏中。
  2. 选择 DataSet 对象并将 Name 属性设置为 AuthorData
  3. 从“工具箱”的“Web 窗体”选项卡中将 DataGrid 控件拖到窗体上。
  4. DataGrid 控件的“属性”窗口中,将 DataSource 属性设置为 AuthorData,将 DataMember 属性设置为 authors。这些设置出现在这些控件的下拉列表中。DataGrid 的列标题被设置为 authors 表的列名称。
  5. 若要支持在 DataGrid 控件中就地编辑,需要添加一个“编辑、更新、取消”列,该列将包含一个“编辑”按钮。当用户单击此“编辑”按钮时,该行的内容将显示在文本框中,并且“编辑”按钮将被“更新”和“取消”按钮替换。添加此列的方法是:
    1. 单击“属性”窗口底部的“属性生成器”链接,然后选择对话框中的“列”选项卡。
    2. 在“可用列”窗格中展开“按钮列”项。
    3. 选择“编辑、更新、取消”,然后单击 () 按钮。
    4. 单击“确定”。

为“编辑”、“更新”和“取消”按钮添加代码

  1. 右击窗体并选择“查看代码”。在 Page_Load 事件中添加填充 DataGrid 控件的代码,如下所示。这些代码创建 XML Web services 的实例,填充 AuthorData 数据集,并将此数据集绑定到 DataGrid 控件。每当页在往返过程后返回用户时,网格都将包含来自数据库中数据的全新副本。XML Web services 的 Credentials 属性用于将您的身份传递给 XML Web services,该服务接着将它传递给数据库服务器。
    注意   在本演练中,每当 Web 窗体页下载到客户端浏览器时,都会从数据库中检索 authors 表的新副本。这可能不能提供最佳性能。有关优化性能的其他方法的讨论,请参见 Web 数据访问策略建议。

    ' Visual Basic
    Private Sub Page_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
       Dim ws As New AuthorsWebClient.localhost.AuthorsService()
       ws.Credentials = System.Net.CredentialCache.DefaultCredentials
       AuthorData.Merge(ws.GetAuthors())
       If Not Page.IsPostBack Then
          Me.DataGrid1.DataBind()
       End If
    End Sub
    
    // C#
    private void Page_Load(object sender, System.EventArgs e)
    {
       AuthorsWebClient.localhost.AuthorsService ws =
          new AuthorsWebClient.localhost.AuthorsService();
       ws.Credentials = System.Net.CredentialCache.DefaultCredentials;
       AuthorData.Merge(ws.GetAuthors());
       if (! Page.IsPostBack)
       {
          DataGrid1.DataBind();
       }
    }
  2. 当用户单击“编辑”按钮时,将引发 DataGrid 控件的 EditCommand 事件。使用此事件更改 DataGrid 控件的 EditItemIndex 属性。EditItemIndex 属性指定的行将与所有数据一起显示在文本框中。
    1. EditCommand 事件创建事件处理程序。

      在 Visual Basic 中,右击页并选择“查看代码”。在代码编辑器的左下拉框中选择 DataGrid1。在右下拉框中选择“EditCommand”。

      - 或 –

      在 Visual C# 中,单击 DataGrid1 控件。在“属性”窗口中,单击“事件”按钮以显示 DataGrid 事件的列表。双击 EditCommand 事件。

    2. 如下所示添加代码。
      ' Visual Basic
      Private Sub DataGrid1_EditCommand(ByVal source As Object, _
      ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) _
      Handles DataGrid1.EditCommand
         DataGrid1.EditItemIndex = e.Item.ItemIndex
         DataGrid1.DataBind()
      End Sub
      
      // C#
      private void DataGrid1_EditCommand(object source,
      System.Web.UI.WebControls.DataGridCommandEventArgs e)
      {
         DataGrid1.EditItemIndex = e.Item.ItemIndex;
         DataGrid1.DataBind();
      }
  3. CancelCommand 事件创建事件处理程序。(有关添加事件处理程序的信息,请参见第 2 步。)

    当用户单击“取消”按钮时,将引发 DataGrid 控件的 CancelCommand 事件。使用此事件将 EditItemIndex 属性设置为 -1,以便将当前行再次显示为文本。如下所示添加代码。

    ' Visual Basic
    Private Sub DataGrid1_CancelCommand(ByVal source As Object, _
    ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) _
    Handles DataGrid1.CancelCommand
       DataGrid1.EditItemIndex = -1
       DataGrid1.DataBind()
    End Sub
    
    // C#
    private void DataGrid1_CancelCommand(object source,
    System.Web.UI.WebControls.DataGridCommandEventArgs e)
    {
       DataGrid1.EditItemIndex = -1;
       DataGrid1.DataBind();
    }
  4. 当用户单击“更新”按钮时,将引发 DataGrid 控件的 UpdateCommand 事件。该方法负责用 DataGrid 控件中的更改更新 AuthorData 数据集,并通过 XML Web services 将这些更改传递回数据库。为 UpdateCommand 事件创建事件处理程序。(有关添加事件处理程序的信息,请参见第 2 步。)如下所示添加代码。
    ' Visual Basic
    Private Sub DataGrid1_UpdateCommand(ByVal source As Object, _
    ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) _
    Handles DataGrid1.UpdateCommand
       ' update each of the fields in the dataset row
       Dim i As Integer
       For i = 1 To AuthorData.authors.Columns.Count
          ' Controls(0) returns a Control object, so it must be cast
          ' to TextBox.
          Dim t As TextBox = CType(e.Item.Cells(i).Controls(0), TextBox)
          Dim row As DataRow = AuthorData.authors(e.Item.DataSetIndex)
          row(AuthorData.authors.Columns(i - 1).Caption) = t.Text
       Next
       ' Update the database.
       If Me.AuthorData.HasChanges() Then
          Dim ws As New AuthorsWebClient.localhost.AuthorsService()
          ws.Credentials = System.Net.CredentialCache.DefaultCredentials
          Dim diffAuthors As New AuthorsWebClient.localhost.authors1()
          diffAuthors.Merge(Me.AuthorData.GetChanges())
          ws.UpdateAuthors(diffAuthors)
          AuthorData.Merge(diffAuthors)
       End If
       ' Take the row out of edit mode and display the new data.
       DataGrid1.EditItemIndex = -1
       DataGrid1.DataBind()
    End Sub
    
    // C#
    private void DataGrid1_UpdateCommand(object source,
    System.Web.UI.WebControls.DataGridCommandEventArgs e)
    {
       // Change the data in the dataset.
       for (int i=1; i <= AuthorData.authors.Columns.Count; i++)
       {
          TextBox t = (TextBox)(e.Item.Cells[i].Controls[0]);
          DataRow row = AuthorData.authors[e.Item.DataSetIndex];
          row[AuthorData.authors.Columns[i-1].Caption] = t.Text;
       }
    
       // Update the database.
       if (AuthorData.HasChanges())
       {
          AuthorsWebClient.localhost.AuthorsService ws =
             new AuthorsWebClient.localhost.AuthorsService();
          ws.Credentials = System.Net.CredentialCache.DefaultCredentials;
          AuthorsWebClient.localhost.authors1 diffAuthors =
             new AuthorsWebClient.localhost.authors1();
          diffAuthors.Merge(AuthorData.GetChanges());
          ws.UpdateAuthors(diffAuthors);
          AuthorData.Merge(diffAuthors);
       }
       DataGrid1.EditItemIndex = -1;
       DataGrid1.DataBind();
    }

需要配置项目的安全设置以便使用集成安全性。通过关闭匿名访问并打开模拟来执行此操作。有关更多信息,请参见 安全模型。

配置集成 Windows 身份验证

若要为项目配置集成 Windows 身份验证,需要使用“Internet 信息服务”工具更改项目文件并配置项目。

  1. 启动“Internet 信息服务”工具。可以从“控制面板”的“管理工具”中运行它。(有关启动此工具的更多信息,请参见 Windows 帮助文档。)展开服务器的节点。
  2. 展开“默认 Web 站点”节点。
  3. 右击 AuthorsWebClient 的节点,并从快捷菜单中选择“属性”。
  4. 单击“目录安全性”选项卡。
  5. 单击“匿名访问和身份验证控制”节中的“编辑”按钮。
  6. 清除“匿名访问”复选框。
  7. 选中“集成 Windows 身份验证”框。现在已配置了 XML Web services 目录。
  8. 返回 Visual Studio 中的项目,在解决方案资源管理器中双击 Web.config 文件。
  9. <system.web> 标记后面的行中添加下面的标记,以便为 XML Web services 配置集成安全性。
    <identity impersonate="true"/>

运行应用程序

  1. 在解决方案资源管理器中选择“AuthorsWebClient”,右击然后选择“设为启动项目”。
  2. 按 CTRL+F5 以运行应用程序。

在本节中,您在解决方案中添加了一个 ASP.NET Web 应用程序项目作为数据的浏览器界面。将 Web 窗体页连接到了在第一节中创建的 XML Web services,并使用了 DataGrid 控件创建用于显示和编辑数据的用户界面。

部署解决方案或添加更多功能

这时,您可以部署应用程序;如果需要,还可以添加程序的其他功能。下面给出几条建议。

  • 部署解决方案

    您可以向服务器部署 Web 应用程序,也可以创建 Windows 应用程序的安装项目。有关部署 Web 应用程序的信息,请参见演练:部署 Web 解决方案。有关部署 Windows 应用程序的信息,请参见演练:部署 Windows 应用程序

  • 支持 Internet Web 访问

    本演练处理 Intranet Web 应用程序的身份验证问题。您的应用程序可能被 Internet 上的用户访问,因此需要另一种身份验证解决方案。有关更多信息,请参见 IIS 身份验证和 Web 应用程序的访问权限。

  • 支持添加或删除记录

    本演练仅支持显示和更新数据库记录,而您可能还需要支持添加和删除记录。添加和删除不是 DataGrid Web 服务器控件支持的固有功能,但它们在 Windows 窗体 DataGrid 控件中受支持。有关更多信息,请参见 允许用户删除 DataGrid Web 服务器控件中的项和 在数据集中更新、插入和删除记录。

  • 考虑安全性

    本演练使用集成 Windows 身份验证来控制数据库访问。数据库安全性只是 Web 应用程序需要注意的一个方面。有关 Web 应用程序安全性的讨论,请参见 Web 应用程序的基本安全实施策略。

请参见

使用 XML Web services 进行 Web 编程 | Web 窗体页 | Windows 窗体 | 托管代码中的 XML Web services | 分布式应用程序和数据集成介绍 | 在分布式应用程序中使用企业级模板

本地化 Windows 窗体

ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.2052/vbcon/html/vbwlkWalkthroughLocalizingWindowsForms.htm

Visual Studio 项目系统为本地化 Windows 窗体应用程序提供相当大的支持。使用 Visual Studio 开发环境生成资源文件有两种方法:一种是使项目系统生成可本地化 UI 元素(如窗体上的文本和图像)的资源文件。然后将该资源文件生成到附属程序集中。第二种方法是添加资源文件模板,然后用 XML 设计器编辑该模板。采取后一种方法的原因之一是为了生成在对话框和错误信息中出现的可本地化字符串。然后,必须编写代码以访问这些资源。

本演练主题在一个 Windows 应用程序项目中演示这两种过程。

还可以将文本文件转换为资源文件;有关更多信息,请参见 文本文件格式中的资源和 资源文件生成器 (Resgen.exe)。

使 Visual Studio 生成资源文件

  1. 创建名为“WindowsApplication1”的新 Windows 应用程序。有关详细信息,请参见创建 Windows 应用程序项目
  2. 在“属性”窗口中,将窗体的 Localizable 属性设置为 true

    Language 属性已经设置为 (Default)

  3. Button 控件从“工具箱”的“Windows 窗体”选项卡拖到窗体上,然后将其 Text 属性设置为“Hello World”。
  4. 将该窗体的 Language 属性设置为“德语(德国)”。
  5. 将该按钮的 Text 属性设置为“Hallo Welt”。
  6. 将窗体的 Language 属性设置为“法语(法国)”。
  7. 将该按钮的 Text 属性设置为“Bonjour le Monde”。如果需要的话,可调整该按钮的大小以容纳较长的字符串。
  8. 保存和生成解决方案。
  9. 单击解决方案资源管理器中的“显示所有文件”按钮。

    资源文件出现在 Form1.vb 或 Form1.cs 的下面。Form1.resx 是后备区域性的资源文件,它将生成到主程序集中。Form1.de-DE.resx 是在德国讲的德语的资源文件。Form1.fr-FR.resx 是在法国讲的法语的资源文件。

    此外,您将看到名为 Form1.de.resx 和 Form1.fr.resx 的文件。Visual Studio 自动创建这些文件,以便解决 Visual SourceSafe 中存在的一个限制,即 Visual SourceSafe 不得不在保存操作期间处理将新文件添加到项目中。.resx 文件为空并且不包含任何资源。

  10. 按 F5 键或从“调试”菜单中选择“启动”。

    现在将看到对话框中出现英语、法语或德语的问候,具体看到哪一种取决于操作系统的 UI 语言。

    注意   Windows 中使用的 UI 语言是 CurrentUICulture 设置的一种功能。如果您的 Windows 副本装有多语言用户界面包 (MUI),则可以在控制面板中更改 UI 语言。有关更多信息,请参见 Microsoft 全球化站点。如果未安装 MUI,则可以用编程方式更改当前的 UI 区域性,如下所述。

下面的过程说明如何设置 UI 区域性,以便该应用程序显示法语资源。在实际应用程序中,不用这种方式硬编码 UI 区域性。UI 区域性的设置将取决于用户设置或应用程序设置。

设置 UI 区域性以查看特定资源

  1. 在代码编辑器中,在 Form1 声明前的模块开始处添加下列代码:
    ' Visual Basic
    Imports System.Globalization
    Imports System.Threading
    
    // C#
    using System.Globalization;
    using System.Threading;
  2. 添加下列代码。在 Visual Basic 中,该代码应在调用 InitializeComponent 函数前进入 New 函数中。在 Visual C# 中,也应在调用 InitializeComponent 函数前进入 Form1() 中。
    ' Visual Basic
    ' Sets the UI culture to French (France)
    Thread.CurrentThread.CurrentUICulture = New CultureInfo("fr-FR")
    
    // C#
    // Sets the UI culture to French (France)
    Thread.CurrentThread.CurrentUICulture = new CultureInfo("fr-FR");
  3. 保存和生成解决方案。
  4. 按 F5 键或从“调试”菜单中选择“启动”。

    现在该窗体将始终以法语显示。如果早已更改了该按钮的大小以容纳较长的法语字符串,则请注意,在法语资源文件中也已经保持了该按钮的大小。

手动向项目中添加资源文件并编辑它们

  1. 在“项目”菜单上单击“添加新项”。
  2. 在“模板”框中选择“程序集资源文件”模板。在“名称”框中键入文件名“WinFormStrings.resx”。WinFormStrings.resx 文件将包含英语的后备资源。每当应用程序找不到更适合于 UI 区域性的资源时,就将访问这些资源。

    该文件被添加到解决方案资源管理器中的项目中,并自动在 XML 设计器中以“数据”视图形式打开。

  3. 在“数据表”窗格中选择“data”。
  4. 在“数据”窗格中,单击空行,并在“name”列中输入“strMessage”,在“value”列中输入“Hello World”。

    不需要为字符串指定 typemimetype;它们用于对象。类型说明符保持所保存对象的数据类型。如果对象由二进制数据组成,则 MIME 类型说明符保持所存储的二进制信息的基类型 (base64)。

  5. 在“文件”菜单上,单击“保存 WinFormStrings.resx”。
  6. 再执行两次步骤 1-5 以创建两个分别名为“WinFormStrings.de-DE.resx”和“WinFormStrings.fr-FR.resx”的资源文件,并且这两个文件具有在下表中指定的字符串资源。WinFormStrings.de-DE.resx 文件将包含特定于在德国讲的德语的资源。WinFormStrings.fr-FR.resx 文件将包含特定于在法国讲的法语的资源。
    资源文件名 name value
    WinFormStrings.de-DE.resx strMessage Hallo Welt
    WinFormStrings.fr-FR.resx strMessage Bonjour le Monde

访问手动添加的资源

  1. 在代码编辑器中,在代码模块的开始处导入 System.Resources 命名空间。
    ' Visual Basic
    Imports System.Resources
    
    // C#
    using System.Resources;
  2. 在“设计”视图中,双击该按钮以显示其 Click 事件处理程序的代码并添加下面的代码。ResourceManager 构造函数采用两个参数。第一个参数是资源的根名称,即不带区域性和 .resx 后缀的资源文件名称。第二个参数是主程序集。

    在本演练中,不声明任何命名空间,因此 ResourceManager 构造函数的第一个参数可以采用“ProjectName.ResourceFileRootName”的格式。但是,在实际的应用程序中,您应设置 DefaultNamespace 属性。在该情况下,您将需要通过资源文件的完全限定的根名称(包括其命名空间)声明资源管理器。例如,如果默认命名空间是“MyCompany.MyApplication.MyComponent”,则 ResourceManager 构造函数的第一个参数将是“MyCompany.MyApplication.MyComponent.WinFormStrings”。

    ' Visual Basic
    ' Declare a Resource Manager instance
    Dim LocRM As New ResourceManager("WindowsApplication1.WinFormStrings", GetType(Form1).Assembly)
    ' Assign the string for the "strMessage" key to a messagebox
    MessageBox.Show(LocRM.GetString("strMessage"))
    
    // C#
    // Declare a Resource Manager instance
    ResourceManager LocRM = new ResourceManager("WindowsApplication1.WinFormStrings",typeof(Form1).Assembly);
    // Assign the string for the "strMessage" key to a messagebox
    MessageBox.Show(LocRM.GetString("strMessage"));

    注意   默认情况下,ResourceManager 对象区分大小写。如果您希望执行不区分大小写的查找(例如使“TXTWELCOME”与“txtWelcome”检索相同的资源),则可以将资源管理器的 IgnoreCase 属性设置为 true。但是,为了提高性能,最好始终为资源名称指定正确的大小写。如果执行不区分大小写的资源查找,则可能会导致性能问题。

  3. 生成并运行窗体。单击按钮。

    消息框将显示适合于 UI 区域性设置的字符串;或者如果它不能找到该 UI 区域性的资源,它将显示来自后备资源的字符串。

请参见

为 Windows 窗体全球化设置区域性和 UI 区域性 | 介绍 Visual Basic 和 Visual C# 中的国际应用程序 | 演练:本地化 Web 窗体页 | 对应用程序进行全球化和本地化 | 安全性和已本地化的附属程序集

2005年07月26日

http://www0.ccidnet.com/tech/guide/2000/03/16/58_481.html

对于Windows环境下的应用软件,一个漂亮醒目而且富有立体感变化的封面总会使你的软件更加充满魅力,从而提高软件的商品化程度,增强对用户的吸引力。本文将介绍在Windows环境下使用Visual Basic制作应用软件封面的技术。

  一、封面设计技术 其

  1.封面的制作

  开发者可以选择一些已有的图像文件,或从其它图像处理过程中截取比较满意的图案,也可以制作具有个人风格的封面。在VB中制作封面时,是在一个窗体上使用VB的绘图工具Line和Shape绘制出图形,也可以在窗体内载入一个Picture Box或Image Control,利用Picture Box或Image Control载入自己喜欢的图片来制作封面,同时在图形之上,可以使用Text Box或Label来显示版本信息。

  为了使设计的窗体更加象一个封面,可以去除窗体的控制框、标题栏和最大最小化按钮,窗体的大小不允许用户改变。

  在封面窗体的属性中,属性Control Box应设为False,Caption应设为空,MaxButton和MinButton应设为False,BorderStyler属性设为1,即固定的单线边界,这样就设计出了一个标准的封面。

  2. 浮动窗体

  主窗体载入后,激发封面事件(event),使封面窗体成为当前活动窗体,必须保证封面总是浮在主窗体的上面,在VB中可以通过调用Windows的API函数SetWindowpos来实现。SetWindowpos函数的声明如下: 

  Declare Sub SetWindowPos Lib ″User″ (ByVal hWnd As Integer, ByVal hWndInsertAfter As Integer, ByVal X As Integer, ByVal Y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal wFlags As Integer)

  其中:hWnd是作为封面窗体的句柄;

  hWndInsertAfter 指示为封面窗体前载入的窗体;

  X为封面窗体左上角的X坐标;

  Y为封面窗体左上角的Y坐标;

  cx为窗体新宽度;

  cy为窗体新高度;

  wFlags影响窗体大小和位置的16位值。

  其中,hWndInsertAfter设为HWND—TOPMOST,wFlags设为SWP—NOMOVE (不移动窗体,即忽略cx和cy)或SWP—NOSIZE(不改变窗体大小,即忽略X和Y),就可以实现将HWND标识的窗体置于主窗体之上。

  3. 封面卸载

  保证主窗体载入完毕后,封面窗体自动卸出。在主窗体中可使用定时器控件,在激活主窗体的Activate事件时激活定时器,设置定时器活动时间属性,在定时器控件的事件Timer中,关闭定时器,卸出封面,释放存储空间,提高软件运行速度。

  二、封面设计程序

  假设封面窗体名为frmface,主窗体名为frmfirst,其中封面窗体可以用绘图指令绘出。程序中给出了绘制三维窗体的方法,在封面窗体载入过程中,窗体上可根据用户需要绘制三维面板或图片框等。

  Sub Form—Load ()

  Dim fWidth As Integer

  Dim i As Integer, t As Integer, l As Integer

  Dim w As Integer, h As Integer

  frmface.BackColor=&HC0C0C0

  frmface.Left=(screen.Width-Width) / 2

  frmface.Top=(screen.Height-Height) / 2

  frmface.ScaleMode=3: fWidth=4

  frmface.AutoRedraw=-1

  For i=1 To fWidth

  t=i

  l=i

  h=scaleheight

  w=scalewidth

  Line (l, t)-(l, h-i), RGB(255, 255, 255)

  Line (l, t)-(w-i, t), RGB(255, 255, 255)

  Line (w-l, t)-(w-l, h-t), RGB(64, 64, 64)

  Line (l, h-l)-(w-l, h-l), RGB(64, 64, 64)

  Next i

  End Sub  

  在主窗体的激活过程中激活定时器,并设置定时器的Interval属性,程序如下:

  Sub Form—Activate ()

  Timer1.Interval=3000

  ′封面存在时间为3秒

  Timer1.Enabled=True

  ′打开定时器

  End Sub

  载入封面的代码放在主窗体的Load事件中,可以改变鼠标形状以示用户等待。首先在通用过程中声明常量:

  Const HWND—TOPMOST=-1

  Const SWP—NOMOVE=&H10

  Const SWP—NOSIZE=&H40

  Const flags=SWP—NOMOVE Or SWP—NOSIZE

  使用SetWindowPos函数实现浮动效果:

  Sub Form—Load ()

  screen.MousePointer=11

  ′鼠标呈沙漏状

  frmface.Show

  ′显示封面

  frmface.Refresh

  SetWindowPos frmface.hWnd, HWND—TOPMOST, 0, 0, 0, 0, flags

  End Sub

  定时器时间到后关闭定时器,同时卸载封面窗体,使用户可以继续操作主窗体。程序如下:

  Sub Timer1—Timer ()

  frmfirst.Timer1.Enabled=False

  ′关闭定时器

  Unload frmface

  ′卸载封面

  screen.MousePointer=0

  ′鼠标恢复原样

  End Sub