精华内容
下载资源
问答
  • winform, 要先登录, 登录进去之后点击目录获得以下的文件,有些文件是word文档,要直接在上面查看, 不下载, 用什么控件显示里面的内容? 怎么显示?
  • 这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、...

    欢迎使用Markdown编辑器

    你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

    新的改变

    我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

    1. 全新的界面设计 ,将会带来全新的写作体验;
    2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
    3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
    4. 全新的 KaTeX数学公式 语法;
    5. 增加了支持甘特图的mermaid语法1 功能;
    6. 增加了 多屏幕编辑 Markdown文章功能;
    7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
    8. 增加了 检查列表 功能。

    功能快捷键

    撤销:Ctrl/Command + Z
    重做:Ctrl/Command + Y
    加粗:Ctrl/Command + B
    斜体:Ctrl/Command + I
    标题:Ctrl/Command + Shift + H
    无序列表:Ctrl/Command + Shift + U
    有序列表:Ctrl/Command + Shift + O
    检查列表:Ctrl/Command + Shift + C
    插入代码:Ctrl/Command + Shift + K
    插入链接:Ctrl/Command + Shift + L
    插入图片:Ctrl/Command + Shift + G
    查找:Ctrl/Command + F
    替换:Ctrl/Command + G

    合理的创建标题,有助于目录的生成

    直接输入1次#,并按下space后,将生成1级标题。
    输入2次#,并按下space后,将生成2级标题。
    以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

    如何改变文本的样式

    强调文本 强调文本

    加粗文本 加粗文本

    标记文本

    删除文本

    引用文本

    H2O is是液体。

    210 运算结果是 1024.

    插入链接与图片

    链接: link.

    图片: Alt

    带尺寸的图片: Alt

    居中的图片: Alt

    居中并且带尺寸的图片: Alt

    当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

    如何插入一段漂亮的代码片

    博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

    // An highlighted block
    var foo = 'bar';
    

    生成一个适合你的列表

    • 项目
      • 项目
        • 项目
    1. 项目1
    2. 项目2
    3. 项目3
    • 计划任务
    • 完成任务

    创建一个表格

    一个简单的表格是这么创建的:

    项目 Value
    电脑 $1600
    手机 $12
    导管 $1

    设定内容居中、居左、居右

    使用:---------:居中
    使用:----------居左
    使用----------:居右

    第一列 第二列 第三列
    第一列文本居中 第二列文本居右 第三列文本居左

    SmartyPants

    SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

    TYPE ASCII HTML
    Single backticks 'Isn't this fun?' ‘Isn’t this fun?’
    Quotes "Isn't this fun?" “Isn’t this fun?”
    Dashes -- is en-dash, --- is em-dash – is en-dash, — is em-dash

    创建一个自定义列表

    Markdown
    Text-to-HTML conversion tool
    Authors
    John
    Luke

    如何创建一个注脚

    一个具有注脚的文本。2

    注释也是必不可少的

    Markdown将文本转换为 HTML

    KaTeX数学公式

    您可以使用渲染LaTeX数学表达式 KaTeX:

    Gamma公式展示 Γ(n)=(n1)!nN\Gamma(n) = (n-1)!\quad\forall n\in\mathbb N 是通过欧拉积分

    Γ(z)=0tz1etdt. \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,.

    你可以找到更多关于的信息 LaTeX 数学表达式here.

    新的甘特图功能,丰富你的文章

    Mon 06Mon 13Mon 20已完成 进行中 计划一 计划二 现有任务Adding GANTT diagram functionality to mermaid
    • 关于 甘特图 语法,参考 这儿,

    UML 图表

    可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:

    张三李四王五你好!李四, 最近怎么样?你最近怎么样,王五?我很好,谢谢!我很好,谢谢!李四想了很长时间,文字太长了不适合放在一行.打量着王五...很好... 王五, 你怎么样?张三李四王五

    这将产生一个流程图。:

    链接
    长方形
    圆角长方形
    菱形
    • 关于 Mermaid 语法,参考 这儿,

    FLowchart流程图

    我们依旧会支持flowchart的流程图:

    Created with Raphaël 2.2.0开始我的操作确认?结束yesno
    • 关于 Flowchart流程图 语法,参考 这儿.

    导出与导入

    导出

    如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

    导入

    如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
    继续你的创作。


    1. mermaid语法说明 ↩︎

    2. 注脚的解释 ↩︎

    展开全文
  • 目录1.题目1.1基本要求1.2 本人新增额外功能2.窗体运行截图3.使用说明4.控件/组件5.总体设计6.详细设计6.1界面设计(MainWindow.xaml)6.2 逻辑设计(MainWindow.xaml.cs)7.源码 1.题目 1.1基本要求 操作WORD文件:...

    1.题目

    1.1基本要求

    操作WORD文件:使用MSWORD.OLB组件将RichTextBox中的文本保存为WORD格式文件。
    在这里插入图片描述

    1.2 本人新增额外功能

    1、RichTextBox的内容实现撤消、恢复、清空、添加图片的操作;
    2、关闭、打开WORD格式文件;
    3、显示正在打开/编辑的文件名;
    4、自定义背景图;
    5、快捷键点击按钮;
    6、控件背景颜色透明化;
    7、鼠标移动到/离开按钮上方,字体颜色变化;
    8、分别利用WPF应用和Windows窗体应用实现这些功能(由于两者的设计思想基本一致,所以这里只介绍利用WPF应用实现的,但两者的源码都会和报告一起提交)。

    2.窗体运行截图

    在这里插入图片描述

    3.使用说明

    利用Visual Stdio打开源码内的项目文件夹中的后缀为.lsn的文件即可开始使用。
    打开窗体,RichTextBox富文本框会提示从此处开始输入信息,点击富文本框提示信息可以清除。输入信息过程中输入错误可以使用窗体上方的撤销、恢复、清空按钮进行操作,同时点击添加图片按钮弹出对话框选择任意图片文件即可导入富文本框。输入完成点击保存,弹出保存文件对话框,输入文件名点击确定即可保存文件,此时窗体会显示该文件名称。
    点击关闭可以关闭该文件、清空富文本框的内容,正在编辑文件空。点击打开按钮会显示打开文件对话框,可以选择任意word文件打开并加载到富文本框,窗体显示该文件名。也可以编辑该文件再保存,也可以直接点击删除按钮删除该文件,同时富文本框清空、正在编辑文件空。

    4.控件/组件

    1、标签(System.Windows.Controls.Lable)
    2、按钮(System.Windows.Controls.Button)
    3、富文本框(System.Windows.Controls…RichTextBox)
    4、网格(System.Windows.Controls…Grid)
    5、定时器(System.Windows.Threading.DispatcherTimer)
    6、打开文件对话框(System.Windows.Forms.OpenFileDialog)
    7、保存文件对话框(System.Windows.Forms.SaveFileDialog)
    8、消息框(System.Windows.Forms.MessageBox)
    9、剪贴板(System.Windows.Forms.Clipboard)
    10、文本范围(System.Windows.Documents.TextRange)
    11、图像(System.Drawing.Image)
    12、MSWORD.OLB(Microsoft.Office.Interop.Word)

    5.总体设计

    窗体利用WPF应用实现,界面设计利用XAML(可扩展应用程序标记语言,Extensible Application Markup Language)实现,程序运行逻辑使用C#语言实现。首先利用Grid网格将窗体分割,接着定义按钮等其他控件并放在网格所规划的位置,并设置它们的各个属性,然后为窗体设计背景图片使得更美观。同时为按钮设置MouseEnter和MouseLeave事件通过改变字体颜色解决鼠标在按钮上方按钮上的文字看不清的问题。
    界面设计完成后设计各个按钮所对应的操作,并将其订阅对应的Click事件,其中保存/打开/添加图片主要利用了savefiledialog/openfiledialog组件打开窗体保存/打开对应的文件,保存文件成功/失败会通过MessageBox显示提示信息。同时还为按钮设计了快捷键,richtetbox显示提示信息,用户点击后会自动清除该提示文本。未来方便用户还在左上角设置Lable标签用于显示正在编辑的文件名,利用定时器定时通过获取类所定义的属性edictingfilename(正在编辑的文件名)刷新Lable的值。

    6.详细设计

    6.1界面设计(MainWindow.xaml)

    首先设置窗体的标题Title、高度Height、宽度Width以及用来监视键盘输入字符的事件PreviewKeyDown,用于设置按钮快捷键。在对象元素Window.Background里面添加对象元素ImageBrush并设置其附加属性ImageSource设置窗体背景图。接着通过Margin定义一个左上右下边距为(10,0,10,10)、Grid.RowDefinitions和Grid.ColumnDefinitions定义2行17列的Grid网格控件,其中第4行的Height高度设置随着窗口缩小/放大而改变大小,且第1列的宽度为自动使得其根据Lable控件里面的内容设置宽度,并设置最小宽度MinWidth为控件初始长度。
    通过按照如下格式设置控件的各个属性:

    <Button Name="RedoButton" Content="恢复(R)" Grid.Row="0" Grid.Column="4" Height="25" Click="RedoButton_Click" Margin="0,8,0,7" Grid.RowSpan="3" Background="Transparent" BorderBrush="Transparent" Foreground="#FFFDF
    DFD" MouseEnter="RedoButton_MouseEnter" MouseLeave="RedoButton_MouseLeave"/>
    

    里面的属性分别表示设置一个Button控件,名为RedoButton、内容为恢复®、处于网格0行4列的位置、高度25、鼠标点击触发的事件为RedoButton_Click、左上右下边距为(0,8,0,7)、占3行、图标背景颜色为透明、边框颜色为透明、字体颜色为白色、鼠标进入该控件触发的事件为RedoButton_MouseEnter、鼠标移出该控件的范围触发事件RedoButton_MouseLeave,其他的Lable、RichTextBox控件基本也是按照此格式进行定义。

    6.2 逻辑设计(MainWindow.xaml.cs)

    首先定义一个用于保存正在编辑的文件名的变量editingFilename,接着就是用于定时刷新Lable里面的值的DispatcherTimer型的变量timer1,以及用于打开/保存文件的OpenFileDialog 型的openFileDialog、SaveFileDialog型的saveFileDialog。构造函数里面设置保存文件的默认类型为.docx、利用TextRange全选RichTextBox富文本框里面的内容并设置初始的提示信息、设置变量editingFilename为空字符串、将计时器触发的事件的订阅者添加timer1_Tick(用于定时刷新Lable里面的文件名)、timer1.Start()启动该计时器,同时在析构函数里面利用Stop()方法使其停止。

    保存按钮SaveButton对应的函数SaveButton_Click:首先判断editingFilename若不为空,则直接调用SaveFile函数保存该文件;若为空,则首先利用TextRange判断RichTextBox里面或者调用保存文件的窗口返回是否是确定,如果不符合条件,则什么不返回;否则设置saveFileDialog返回的文件名设置为editingFilename,若不为空,则调用SaveFile函数保存文件,否则什么不返回。而保存文件函数SaveFile形参为保存的文件名,定义一个Word应用对象以及Word文档对象并将该文档添加到该应用,利用SelectAll以及Copy方法将richTextBox1里面的数据(文本和图片)复制到粘贴板,利用myDoc的Paste方法将其粘贴。由于richTextBox1的文字为白色,保存到word文档后会看不见,所以全选文档里面的文字利用myApp.Selection.Font.Color = WdColor.wdColorBlack将其字体颜色设置成黑色,接着再执行保存、关闭、退出的操作。同时利用MessageBox.Show的方法提示保存成功或者失败,若失败则输出失败的原因。

    打开按钮OpenButton对应的函数OpenButton_Click:首先还是利用TextRange将richTextBox1的内容设置为空,还是调用打开文件对话框并且返回确定时接着接下来的操作,否则什么不返回。接着设置editingFilename为savefiledialog返回的内容,利用定义的Word应用对象以及Word文档对象的Open方法打开文件并全选里面的数据,同时由于文档里面的字体为黑色在richTextBox1中显示不清,所以利用上面相同的方法将其设置为白色的字体,并复制到剪贴板上,然后粘贴、关闭、退出,若出现异常则调用MessageBox显示出异常的原因。

    添加图片按钮AddPhotoButton对应的函数AddPhotoButton_Click:利用openPhotoFileDialog.Filter设置对话框的过滤条件,同样调用打开文件对话框获取选择的图片,并利用Clipboard.SetDataObject(Image.FromFile(openPhotoFileDialog.FileName), false)将图片复制到剪贴板,参数false表示退出程序后不再将图片保留在剪贴板中。

    恢复按钮RedoButton对应的函数RedoButton_Click就是调用富文本框的Redo方法;而撤销按钮UndoButton对应的函数UndoButton_Click则是调用Undo方法;清空按钮ClearAllButto对应的函数ClearAllButton_Click则调用richTextBox1.Document.Blocks.Clear()清空里面的内容;关闭文档按钮对应的函数CloseButton_Click将正在编辑的文件名设置为空字符串,并利用TextRange将富文本框里面的内容也设置为空字符串。

    定时刷新lable1的内容,用于显示正在编辑/打开的文件名的函数timer1_Tick则是将字符串“正在编辑文件:”以及editingFilename显示在Lable控件上;删除按钮对应的函数DeleteButton_Click,若正在编辑的文件名为空,则什么都不返回,否则利用File.Delete(editingFilename);删除文件,并将正在编辑的文件设置为空,并将richTextBox1的内容清空;而清除richBoxText提示的输入信息的函数PromptInputInfoOfRichTextBox,也就是富文本框PreviewMouseDown事件对应的函数,是将富文本框的内容清空,并利用richTextBox1.PreviewMouseDown -= PromptInputInfoOfRichTextBox取消订阅该事件,使得该方法只执一次,不至于后面一点击富文本框就将所有的内容清除。

    而后面的鼠标进入按钮控件,字体变黑;鼠标出来,字体恢复,其实就是各个按钮对应的MouseEnter和MouseLeave事件分别设置成Button.Foreground = System.Windows.Media.Brushes.Black、Button.Foreground = System.Windows.Media.Brushes.White;而后面的快捷键操作是监视键盘输入,将对应的按键绑定到操作对应的按钮上,具体做法if ((e.KeyboardDevice.IsKeyDown(Key.LeftCtrl) || e.KeyboardDevice.IsKeyDown(Key.RightCtrl)) && e.KeyboardDevice.IsKeyDown(Key.U)) {UndoButton_Click(null, null); },也就是表示若键盘按Ctrl+U就执行撤销按钮对应的函数。

    7.源码

    https://github.com/zhz000/wpf-cs-manipulate-word-file
    https://gitee.com/zhz000/wpf-cs-manipulate-word-file

    展开全文
  • WHC第三方控件

    2018-07-16 12:51:42
    1. 跨线程的控件安全访问方式(CallCtrlWithThreadSafety.cs) 2. CheckBoxList(CheckBoxListUtil.cs) 3. 窗口管理类(ChildWinManagement.cs) 4. 由马丁•米勒http://msdn.microsoft.com/en-us/library/ms996492.aspx...
  • 本章内容4.1 控件属性的作用4.2 简单属性4.3 属性的设计时特性4.4 复杂属性4.5 深入研究——定制自己的属性编辑器4.6 类型转换器4.7 实现自定义属性[点击下载本书word格式完整目录介绍] 4.1 控件属性的作用 ...
      
    

     

     

    4  庖丁解牛系列服务器控件属性

     

     

    本章内容

    4.1  控件属性的作用

    4.2  简单属性

    4.3  属性的设计时特性

    4.4  复杂属性

    4.5  深入研究—定制自己的属性编辑器

    4.6  类型转换器

    4.7  实现自定义属性

    [点击下载本书word格式完整目录介绍]

    4.1  控件属性的作用

     

    属性、方法和事件是控件使用者与控件交互的接口。本节主要介绍控件属性。属性分为系统属性和自定义的属性。

     

    4-1 Webcontrol类属性窗口

    4.1.1  系统属性

    当开发控件时如果选择基类,比如选择继承WebControl基类,一旦继承于此类,一些默认的系统属性就会成为当前控件的属性集的一部分,图4-1所示的是WebControl类的系统属性。

    可以看到一个通用Web控件所应具备的基本属性都已经有了,在实际开发控件时选择某个基类。

    4.1.2  自定义属性

    4.1.1节所讲的是系统已有的属性,在开发控件时一般都要为自己的控件增加一些自定义属性。自定义属性与系统属性完全一样。只是由于不具有系统属性的通用性而需要开发者自己去实现。下面看一下属性的语法格式:

    string strText = "默认值";

    public string Text

    {

        get

        {

            return strText;

        }

     

        set

        {

            strText = value;

        }

    }

    以上是一个最简单的属性,由一个setget语段组成。注意,setget段不是必需的,比如可以去掉set段表示此属性只允许读取而不允许接收值。

    事实上属性的特性范畴还比较多,如简单属性、复杂属性,以及属性在设计时的特性和标记形式的格式等,下面将对这些特性一一进行介绍。

    4.2  简单属性

    简单属性是类型为字符串的或容易转换为字符串的属性。简单属性在控件的开始标记上自行保留为属性。.NET Framework 类库中的基元值类型,如StringBooleanInt16Int32DateTimeByteCharDoubleEnum均为简单属性。可以通过添加代码将简单属性存储在ViewState字典中,以便在回发间进行状态管理。请看例子:

    /// <summary>

    /// 获得本书更多内容,请看:

    /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

    /// </summary>

    public string Value

    {

        get

        {

            String s = (String)ViewState["Value"];

            return ((s == null) ? String.Empty : s);

        }

     

        set

        {

            ViewState["Value"] = value;

        }

    }

    上面声明的简单属性中,属性可接收及其返回值的类型是String,表示本属性为简单属性。另外,简单属性可以直接使用ViewState存储其值,因为简单属性可以直接映射为字符串,而ViewState中可以直接接收的格式也是字符串。

    ViewState是页面视图状态机制中的存储机制,是为解决在Web浏览器两次访问之间无状态保持而提供的一种机制,视图信息存储在网页中专用HiddenField区域,而且每次页面提交都会往返于客户端和服务器,因此一般视图主要用于存储少量的文本数据信息,而不适合存储数据量比较大的业务数据。另外,复杂属性的存储也要自己实现视图机制功能,这一点在后面讨论视图机制的章节会详细介绍,这里仅作了解即可。

    只要控件中定义了上面的代码段,对应在页面设计器属性窗口中就会包含此项,如图4-2所示。

     

     

    4-2  属性窗口中的属性

     

    在属性窗口中输入一些文本,打开设计器中的源代码会看到如下标记的ASP.NET代码:

    <cc1:controlproperty id="ControlProperty1" runat="server" Value="简单属性">

    </cc1:controlproperty>

    同样,BooleanInt16Int32DateTimeByteCharDoubleEnum等类型的属性与上面的String类型属性代码标记完全一样。简单属性比较简单,就讲解到这里。

    4.3  属性的设计时特性

    .NET Framework为控件设计时属性提供了很多丰富的类,这些属性的功能非常灵活,控制范围广泛,比如可以控制该属性在属性窗口中的显示模式,如:是否在属性窗口中显示该属性,也可以指定此属性必须接收值类型描述,按组分类等,也可以控制文本的标记呈现格式等,甚至可以自己定义一个属性类,实现自己想实现的功能。下面讲一下常用的.NET Framework的属性类对控件的支持功能。

    Ø  Bindable

    指定属性是否可以绑定一个有效数据源,通常使用布尔值进行设置。例如:Bindable(true)。如果使用值true标记属性,表示该属性可以绑定一个有效数据源

    Ø  Browsable

    指定属性是否应该在属性窗口中显示,使用布尔值设置。一般情况下,对于常用的和比较重要的属性设置Browsabletrue,否则设置Browsablefalse

    Ø  EditorBrowsable

    设置属性在编辑器中的可见性,比如设置在智能提示列表不显示或高级用户才可以看到该属性。

    Ø  Category

    指定属性在属性浏览器中进行分组显示的类别。该设计时特性帮助可视化编辑器将属性进行逻辑分组。通常分为:外观(Appearance)、行为(Behavior)、布局(Layout)、数据(Data)、操作(Action)、键盘(Key)和鼠标(Mouse)等。如果您安装的是中文版的IDE则默认情况下中文分类和英文分类是通用的即设置成“数据”或“Data”类别是等价的

    Ø  Description

    设置显示在属性窗口最下面的描述属性功能的文字说明。

    Ø  DesignOnly

    如果此属性设置为true,表示该属性只能在设计期间使用,不能在页面代码中设置其值。

    Ø  ReadOnly

    设置该属性是否为只读状态。如果此特性设置为true,则在属性窗口能看到属性,但不能设置其值。另外,通过在属性语句体中把 set 语句段去掉也可以起到相同的效果。

    Ø  Themeable

    设置该属性是否支持主题特性,默认情况下属性都支持主题。当该属性与界面无关时可以设置其值为false,禁用该属性的主题功能。

    Ø  DesignerSerializationVisibility

    指定属性是否以及如何在代码中序列化,其值为DesignerSerializationVisibility的枚举值,存在3种设置方式:

    —  DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)指定序列化程序不应该序列化属性值;

    —  DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)指定应该允许序列化程序序列化属性的值;

    —  DesignerSerializationVisibility(DesignerSerializationVisibility.Content)指定序列化程序应该序列化属性的内容,而不是属性本身。此字段为只读。Visible为其默认值。

    这里说的序列化是指在IDE中的设计器界面切换到代码视图时,看到的代码标记,或反向切换时把代码标记转化到设计器界面。后面讲复杂属性时会通过示例介绍此属性功能。

    Ø  NotifyParentProperty

    指示当此设计特性应用到的属性的值被修改时将通知其父属性。换言之,如果属性的父属性应该在该属性值被修改时接到通知,则向该属性应用NotifyParentProperty特性。通常使用布尔值进行设置。一般常用于复杂属性通知转换器更新到父级标记

    Ø  ParseChildren

    使用该设计特性指示当在页面上以声明方式使用控件时,嵌套在服务器控件标记内的XML元素是应该视为属性还是应视为子控件。通常情况下,包含两种声明方式:

    —  ParseChildren(true)表示将子XML元素作为服务器控件的属性分析;

    —  ParseChildren(bool childrenasProperty, string defaultProperty),其中childrenasProperty和上面的方式中的布尔值参数意义相同,defaultProperty定义默认情况下将子控件分析为服务器控件的集合属性。

    Ø  PersistChildren

    该设计特性指示设计时是否应将服务器控件的子控件作为内部嵌套控件保持。如果该特性为PersistChildren(true),则将服务器控件的子控件作为嵌套服务器控件标记保持。如果为PersistChildren(false),则将该控件的属性作为嵌套元素保持。

    Ø  PersistenceMode

    指定如何将服务器控件属性或事件保持到ASP.NET页面的元数据属性,共存在4种枚举设置方式:

    —  PersistenceModePersistenceMode.Attribute)指定属性或事件保持为属性;

    —  PersistenceModePersistenceMode.EncodedInnerDefaultProperty)指定属性作为服务器控件的唯一内部文本,如果属性值是HTML编码的,只能对字符串作这种指定;

    —  PersistenceModePersistenceMode.InnerDefaultProperty)指定属性在服务器控件中保持为内部文本,还指示将该属性定义为元素的默认属性,只能指定一个属性为默认属性;

    —  PersistenceModePersistenceMode.InnerProperty)指定属性在服务器控件中保持为嵌套标记,通常用于复杂对象,它们具有自己的持久性属性。

    关于以上4种标记的具体用法下一节会详细介绍

    Ø  DefaultValue

    指定属性的默认值此特性的设置需要特别谨慎假如设置的值不为空则开发人员在使用时如果自己输入的值与默认值相同则控件不会装载开发人员输入的值也就是说此默认值不能指定为具有有效意义或业务意义的实际值一般设置为空即可。

    Ø  DisplayName

    指定在属性窗口中显示的别名。此别名仅在属性窗口中看到,当转换器转换到代码视图,以及在页面后面的代码中编码还是以实际的属性名称为准,而不是以该别名为准。

    Ø  ParenthesizedPropertyName

    指定属性在属性窗口中显示时是否带有括号相当于在Category分组特性基础上的对属性窗口属性集的排序功能如果不带括号该属性会自动排在该组的前面

    Ø  PasswordPropertyText

    指定是否设置成密码文本。如果设置为true,则在属性窗口中输入的文本会用特定的密码符号显示,而不是显示原文本;另外,在代码视图中看到的仍为原文本。

    Ø  TypeConverter

    指定用作此特性所绑定到的对象的转换器的类型。用于转换的类必须从TypeConverter继承。使用ConverterTypeName属性来获取为该特性所绑定到的对象提供数据转换的类名。后面会通过代码示例讲解如何自定义一个自己的类型转换器。

    Ø  Editor

    指定该属性的编辑器,如系统的文件编辑器、文本编辑器、颜色编辑器,还有集合编辑器等,也可以自己实现编辑器,具体用法后面会讲到。

    Ø  ToolBoxItem

    此属性为类特性。属于工具箱属性,可以设置当前控件是否在工具箱中显示,以及所在工具箱项的类型名称等信息。默认生成的控件都显示在工具箱中。

    Ø  ToolBoxData

    此特性为类特性,即不是属性的特性,而是类的特性,设置位置也是在类的上面。ToolBoxData表示从工具箱中拖一个控件到设计界面上时默认显示标记格式,如:

    [ToolboxData("<{0}:ControlProperty runat=server></{0}:ControlProperty>")]

    可以修改参数字符串,定制为自己想要的格式,但要保证所添加的属性为有意义的属性。

    Ø  DefaultProperty

    此特性为类特性。它指定服务器控件的默认属性,例如:[DefaultProperty("Text")]

    指定用黑色粗体显示默认属性特性的属性名称。一般设置比较重要或常用的属性为默认的属性。如TextBox控件的Text属性。

    Ø  DefaultEvent

    此特性为类特性指定服务器控件的默认事件,例如:[DefaultEvent("OnClicked")]

    指定用黑色粗体显示默认事件特性的事件名称。一般设置比较重要或常用的属性为默认的事件,如Button控件的OnClick事件。

    Ø  ValidationProperty

    此特性为类特性,指定该控件的哪个属性作为验证属性。当该控件与验证控件组合使用时,验证控件会自动验证该特性指定的属性。

    Ø  AspNetHostingPermission

    此属性为JIT编译时代码访问安全属性。需要使用此属性确保链接到控件的代码具有适当的安全权限。Control类带有两个JIT编译时代码访问安全属性标记:

    AspNetHostingPermission(SecurityAction.Demand,Level=AspNetHostingPermissionLevel.Minimal)AspNetHostingPermission(SecurityAction.InheritanceDemand,Level=AspNetHosting PermissionLevel.Minimal).在使用时应把第一个属性应用于当前开发的控件,第二个属性是可选的,因为继承请求是可传递的,在派生类中仍有效。

    Ø  ControlBuilder

    分析时特性,将自定义控件生成器与控件关联。只有在您希望使用自定义控件生成器,对页分析器用分析控件的声明性语法的默认逻辑进行修改时,才需要应用此特性。如果仅希望指定控件标记中的内容是否与属性或子控件对应,请使用ParseChildrenAttribute,而不要使用自定义控件生成器。

    Ø  Designer

    设计时特性,指定与控件关联的设计器类。控件设计器类用于控制关联的控件在可视化设计器的设计图面上的外观和行为。

    还有一些更复杂的,包括在设计模式下的元数据属性类在这里没有列出,因为在后面有专门的章节详细介绍,通过代码示例更容易理解。在这里只要理解上面这些属性类功能,开发一般的控件是没有问题了。

    4.4  复杂属性

    4.4.1  概述

    复杂属性是属性的类型不是简单值类型,或者是一个包含其他属性的类。例如.NET Framework中的StyleFontPoint等都是复杂属性。另外还有集合属性,这里也将它作为复杂属性归类,对于集合属性在本章后面会单独拿出来一节进行详细讲解。

    4.4.2  复杂属性的几种标记形式

    先看看一个典型的代码段:

    <asp:GridView ID="GridView1" runat="server">

        <FooterStyle BackColor="#507CD1" Font-Bold="true" ForeColor="White" />

        <RowStyle BackColor="#EFF3FB" />

        <PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign= "Center" />

        <SelectedRowStyle BackColor="#D1DDF1" Font-Bold="true" ForeColor= "#333333" />

        <HeaderStyle BackColor="#507CD1" Font-Bold="true" ForeColor="White" />

        <EditRowStyle BackColor="#2461BF" />

        <AlternatingRowStyle BackColor="White" />

    </asp:GridView>       

    <asp:ListBox ID="ListBox1" runat="server">

        <asp:ListItem Value="1"></asp:ListItem>

        <asp:ListItem Value="0"></asp:ListItem>

    </asp:ListBox>

    代码非常简单,一段是GridView控件的一些属性,另一段是ListBox控件的一些属性。仔细观察一下这些控件的属性标记,我们能很容易给它们归类,比如:GridViewID/Runat属性标记类型相似,FootStyle/RowStyle这样的标记类似,还有Font-Bold这样的属性标记,ListBox的集合项ListItem标记也比较特殊等这么多标记类型。我们在开发控件时当然也希望能够生成这么多灵活的标记类型,那么本节就详细介绍一下服务端控件的这些标记类型是怎样生成的。

    开始之前,有必要说明一下,下面所有代码示例在调试时都是在设计模式下进行的。关于在设计模式下如何调试代码在第2章已经详细讲解过了,如果读者还有疑问请再回顾一下第2章的内容。

    通常情况下,复杂属性表现为几种形式:连字符形式属性、内部嵌套形式属性和内部嵌套形式默认属性。下面将介绍以上几种形式复杂属性的具体实现方法。

    4.4.2.1  连字符形式的复杂属性标记

    连字符复杂属性标记是指属性通过“复杂属性名称-复杂属性的子属性名称”的格式追加到主控件的标记形式。下面用一个例子来讲解这种标记。

    首先,定义一个复合类Person,结构如下:

    /// <summary>

    /// 获得本书更多内容,请看:

    /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

    /// </summary>

    public class Person

    {

        private string strName;

        /// <summary>

        /// 姓名

        /// </summary>

        public string Name

        {

            get { return strName; }

            set { strName = value; }

        }

       

        private int intAge;

        /// <summary>

        /// 年龄

        /// </summary>

        public int Age

        {

            get { return intAge; }

            set { intAge = value; }

        }

    }

     

    u  再在控件中增加一个类型为Person的属性,将以下代码增加到控件中:

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. private Person pPerson;
    6. [Description("复杂属性")]
    7. [Category("复杂属性")]
    8. public Person Person
    9. {
    10.     get
    11.     {
    12.         if (pPerson == null)
    13.         {
    14.             pPerson = new Person();
    15.         }
    16.         return pPerson; 
    17.     }
    18. }

    此属性与简单属性的区别有两点:第一,属性接收和返回的类型不是简单类型(intstring 等),而是用我们自己定义的Person类;第二,复杂属性一般没有set语句,因为一般是对复杂属性的子属性(或子对象)赋值,只要保证它的子属性(子对象)中具有get/set语句即可。编译此控件,在IDE中打开页面,并打开控件的属性窗口,会看到如图4-3所示的界面。

     

    图4-3 复杂属性

     

     

    另外,在属性窗口中比较这两个属性,您会发现上节讲的简单属性的Value属性可以设置其值,复杂属性Person是只读的,上面我们没有设置ReadOnly特性。这是因为复杂属性中类型比较复杂,甚至还有嵌套。如果把所有复杂属性包含其子属性的值都放到这一个框中,显然不太方便。这就要求我们自己根据复杂属性类型增加一些序列化的特性。

    解决办法是,为主控件属性Person增加PersistenceModeDesignerSerializationVisibility两个设计特性片段代码如下所示:

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. … …
    6. [PersistenceMode(PersistenceMode.Attribute)]
    7. [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    8. public Person Person
    9. {
    10.     … …
    11. }

    1PersistenceMode特性

    PersistenceMode特性指定在页面*.aspx*.ascx文件中如何保持复杂属性,理解此特性用法非常重要,这里详细介绍一下它的用法。PersistenceMode有四种枚举状态:

    Ø PersistenceMode.Attribute

    表示复杂属性的标记作为主控件的属性,如果复杂属性包含子属性,则子属性持久化成破折号连接的样式,比如:

    <asp:GridView ID="GridView1" runat="server">           

        <HeaderStyle BackColor="#507CD1" Font-Bold="true" ForeColor="White" />

    </asp:GridView>

    上面代码中的Font-Bold对于<HeaderStyle>来说就是使用了PersistenceMode下的Attribute枚举标记类型。本节例子中就是实现此标记形式。

    Ø PersistenceMode.InnerProperty

    表示用属性名称作为嵌套标签表示复杂属性,比如GridViewHeaderStyle属性,就是使用了PersistenceMode下的InnerProperty标记形式。代码如下:

    <asp:GridView ID="GridView1" runat="server">           

        <HeaderStyle BackColor="#507CD1" Font-Bold="true" ForeColor="White" />

    </asp:GridView>

    Ø PersistenceMode.InnerDefaultProperty

    该特性值与InnerProperty类似都是在主控件外标记复杂属性不同的是InnerDefaultProperty不需要像InnerProperty那样把属性名作为最外标签一般用于常用的或重要复杂属性或集合如:

    <asp:ListBox ID="ListBox1" runat="server">

        <asp:ListItem Value="1"></asp:ListItem>

        <asp:ListItem Value="0"></asp:ListItem>

    </asp:ListBox>

    以上代码中的ListItem它的特点是直接把ListItem单项放到ListBox的标记内部而没有增加一个类似<Items>的标记在ListItem的外面另外InnerDefaultProperty在一个控件类中只能设置一个复杂属性InnerProperty可以设置任意多个复杂属性

    一般情况下会把最重要的一个集合属性设置为InnerDefaultProperty枚举标记类型

    Ø PersistenceMode.EncodedInnerDefaultProperty

    上面的代码中ListItem.Text属性(值为“男”或“女”)除了标记方式与InnerDefaultProperty有点区别外其内容会进行HTML编码比如把HTML标记<div>编码为&lt;div&gt;,即要保证其内容不能再存储HTML标记和子标签。

    2DesignerSerializationVisibility特性

    此特性表示指定在设计时序列化复杂对象的方式它有三个枚举类型

    Ø DesignerSerializationVisibility.Visible

    表示代码生成器要对属性本身生成代码。

    Ø DesignerSerializationVisibility.Hidden

    表示代码生成器不对属性生成代码即在属性窗口设置的值不会被代码生成器生成到*.aspx*.ascx文件中

    Ø DesignerSerializationVisibility.Content

    表示代码生成器生成复杂属性内容的代码而不是其本身比如在上面的People类中我们实际要操作的数据是People类下面的 Name/Sex/Age属性即我们在属性窗口中修改了Name/Sex/Age的值后会仅把这些值通过代码生成器映射到*.aspx*.axcx页面中

    果没有设置DesignerSerializationVisibility特性则其值默认为DesignerSerialization Visibility. Visible;一般复杂属性都要设置为DesignerSerializationVisibility.Content

    理解了PersistenceMode DesignerSerializationVisibility两个特性的用法我们再继续完成上面进行中的代码部分为属性Person增加了这两个特性后,再打开Person类定义代码,为该类增加一个类特性TypeConverter,如下所示:

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. [TypeConverter(typeof(ExpandableObjectConverter))]  
    6. public class Person
    7. {
    8.     … …
    9. }

    TypeConverter特性指定转换器的类型,ExpandableObjectConverter表示可扩展对象与其他类型的转换器类,该类为系统提供。另外,也可以自己定义转换器规则类,本章后面会有专门介绍。

    增加以上属性之后,编译控件再查看属性窗口,就可以在属性窗口中进行设置Person属性的值了,如图4-5所示。

     

     

    4-5  设置Person属性值

     

    在属性浏览器中为Person设置值,后切换到代码视图,会看到如下标记:

    <cc1:controlproperty id="ControlProperty1" runat="server" Person-Age="26" Person-Name="King Zheng"></cc1:controlproperty>

    到此我们就实现以上功能:连字符复杂属性的标记形式。

    4.4.2.2  内部嵌套复杂属性标记

    连字符复杂属性标记虽然能够实现复杂属性,且代码生成器能够进行正/反向转换,但它把所有复杂属性都挤到主控件的属性上,显示比较臃肿,设想一下,如果GridView把它的<HeadStyle><Rowstyle>等属性都挤到GridView主标记内部,会是什么样子,为了解决这个问题,下面我们就实现一个类似以下代码中的RowStyle标记形式的复杂属性。

    <asp:GridView ID="GridView1" runat="server">

        <RowStyle BackColor="#EFF3FB" />

    </asp:GridView>

    u  在控件所在项目中增加一个类文件 RowStyle.cs,定义其内容如下:

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. [TypeConverter(typeof(ExpandableObjectConverter))]  
    6. public class RowStyle    //TableItemStyle: Table的Row和Cell样式基类,也可以直
    7.                              //接继承此类
    8. {
    9.     private Color bcBackColor;        
    10.     [NotifyParentProperty(true)]
    11.     public Color BackColor
    12.     {
    13.         get { return bcBackColor; }
    14.         set { bcBackColor = value; }
    15.     }
    16. }

    注意不要漏掉TypeConverterNotifyParentProperty,其用途在前面中已经讲过了。

    再在主控件中增加一个RowStyle类型的属性,属性名为RowStyle,增加后的属性代码片段如下所示:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. [PersistenceMode(PersistenceMode.InnerProperty)]
    6. [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    7. [NotifyParentProperty(true)]
    8. [Category("复杂属性")]
    9. [Description("复杂属性——内部嵌套形式")]
    10. public RowStyle RowStyle
    11. {
    12.     get
    13.     {
    14.         if (rsRowStyle == null)
    15.         {
    16.             rsRowStyle = new RowStyle();
    17.         }
    18.         return rsRowStyle; 
    19.     }
    20. }

    选择PersistenceMode特性的InnerProperty枚举项,表示生成嵌套标记;至于DesignerSerializationVisibility特性,依然选择Content枚举值,这里也是对复杂属性RowStyle的类对象子属性进行序列化。如还不清楚这两个属性的使用,请到前面的4.1.1节回顾一下。

    然后,在主控件加两个类特性,如下所示:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. [ParseChildren(true), PersistChildren(false)] //继承WebControl时可以省略此行
    6. public class ControlProperty : WebControl
    7. {
    8.     … …
    9. }

    PerseChildren特性指定页面分析器把控件标记中的内容解析为属性还是子控件,该属性值设置为true,则表示解析为属性。PersistChildren指定设计器把控件标记中的内容保存为属性还是子控件,该属性值设置为false,表示保存为属性。

    设置了如上几个重要特性后,编译控件,在设计器属性窗口中设置RowStyle属性值,并切换到代码视图,会看到RowStyle的标记形式如下所示:

    <cc1:controlproperty id="ControlProperty1" runat="server">

        <RowStyle BackColor="CornflowerBlue" />

    </cc1:controlproperty>

    只要实现RowStyle复杂类型,那么类似GridView的其他嵌套属性如:<HeaderStyle><FooterStyle><SelectedRowStyle><EditRowStyle>等实现方法用同样方式也可以实现。

    在嵌套标记属性比较多的情况下,这些属性看起来效果比上节讲过的连字符复杂属性标记要清晰许多。

    另外,还可以按上面所说的步骤对集合类型生成类似的内部嵌套默认属性,如:

    <asp:DropDownList id="DropDownList1" runat="server"  >

        <Items>

            <asp:ListItem Value="red">红色</asp:ListItem>

            <asp:ListItem Value="green">绿色</asp:ListItem>

        <Items>

    </asp:DropDownList>

    基于实现原理与RowStyle类似且本章后面有专门章节详细探讨集合属性,这里不作代码示范集合属性也是非常重要和常用的复杂属性类型

    4.4.2.3  内部嵌套默认复杂属性标记

    内部嵌套默认属性与内部嵌套属性非常类似,一般用于设置某个控件的集合属性。比如标准服务器控件中的DropDownList控件中的属性均为内部嵌套默认属性,代码如下:

    <asp:DropDownList id="DropDownList1" runat="server" >

        <asp:ListItem Value="red">红色</asp:ListItem>

        <asp:ListItem Value="green">绿色</asp:ListItem>

    </asp:DropDownList>

    内部嵌套默认属性的ListItem标记外部没有像内部集合属性一样嵌套在<Items></Items>中,然后把<Items>嵌套在主控件标记中,而是直接把<asp:listItem></asp:listItem>嵌套在主控件标记内部,一般当该控件只有一个集合复杂属性的情况时使用;而当一个集合中有多个集合或复杂属性时一般设置为内部嵌套复杂属性标记形式。

    为主控件增加集合属性之前,先要建立两个类:

    Ø ListItem类:集合中的单项定义类。

    Ø Items类:集合类,提供ListItem的容器以及一些常用的添加/删除等子项方法。

    1ListItem类完整代码

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. [ToolboxItem(false)]
    6. [TypeConverter(typeof(ExpandableObjectConverter))]    
    7. public class ListItem : Control
    8. {        
    9.     private string _Text;
    10.     private string _Value;        
    11.     public ListItem()
    12.     { }
    13.     public ListItem(string strText,string strValue)
    14.     {            
    15.         this._Text = strText;
    16.         this._Value = strValue;            
    17.     }        
    18.     /// <summary>
    19.     /// 文本属性
    20.     /// </summary>     
    21.     [NotifyParentProperty(true)]           
    22.     public string Text
    23.     {
    24.         get { return _Text; }
    25.         set { _Text = value; }
    26.     }
    27.     /// <summary>
    28.     /// 值属性
    29.     /// </summary>     
    30.     [NotifyParentProperty(true)]
    31.     public string Value
    32.     {
    33.         get { return _Value; }
    34.         set { _Value = value; }
    35.     }
    36. }

    此子项类的代码比较简单唯一要说明的是上面的[ToolBoxItem(false)]表示不在IDE工具箱的控件集合中显示很显然这不是一个控件不能在工具箱集合列表中显示一般除了主控件之外的其余类都要把ToolBoxItem类元数据特性置为false否则当使用者拖一个不完整的控件标记到页面上时可能出现控件不能使用的情况

    2Items类的完整代码

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. /// <summary>
    6. /// 菜单实现类[实用泛型集合]    
    7. /// </summary>
    8. [
    9. ToolboxItem(false),
    10. ParseChildren(true)
    11. ]
    12. public class Items : List<ListItem>
    13. {
    14.     #region 定义构造函数
    15.     public Items()
    16.         : base()
    17.     {
    18.     }
    19.     #endregion
    20.     /// <summary>
    21.     /// 得到集合元素的个数
    22.     /// </summary>
    23.     public new int Count
    24.     {
    25.         get
    26.         {
    27.             return base.Count;
    28.         }
    29.     }
    30.     /// <summary>
    31.     /// 表示集合是否为只读
    32.     /// </summary>
    33.     public bool IsReadOnly
    34.     {
    35.         get
    36.         {
    37.             return false;
    38.         }
    39.     }
    40.     /// <summary>
    41.     /// 添加对象到集合
    42.     /// </summary>
    43.     /// <param name="item"></param>
    44.     public new void Add(ListItem item)
    45.     {
    46.         base.Add(item);
    47.     }
    48.     /// <summary>
    49.     /// 清空集合
    50.     /// </summary>
    51.     public new void Clear()
    52.     {
    53.         base.Clear();
    54.     }
    55.     /// <summary>
    56.     /// 判断集合中是否包含元素
    57.     /// </summary>
    58.     /// <param name="item"></param>
    59.     /// <returns></returns>
    60.     public new bool Contains(ListItem item)
    61.     {
    62.         return base.Contains(item);
    63.     }
    64.     /// <summary>
    65.     /// 移除一个对象
    66.     /// </summary>
    67.     /// <param name="item"></param>
    68.     /// <returns></returns>
    69.     public new bool Remove(ListItem item)
    70.     {
    71.         return base.Remove(item);
    72.     }
    73.     /// <summary>
    74.     /// 设置或取得集合索引项
    75.     /// </summary>
    76.     /// <param name="index"></param>
    77.     /// <returns></returns>
    78.     public new ListItem this[int index]
    79.     {
    80.         get
    81.         {
    82.             return base[index];
    83.         }
    84.         set
    85.         {
    86.             base[index] = value;
    87.         }
    88.     }          
    89. }

    这里的Items采用泛型集合,继承list<T>强类型集合作为基类,此外在System.Collections. Generic命名空间中还有其他一些强类型集合。

    增加完上面两个类后,实现内部默认集合属性,还需要设置两个类设计特性:一是在控件类前设置ParseChildren(true,“默认属性名称”),指定主控件中的属性名称表示是属性,而不是子控件,ParseChildren4.3节已经做了讲解;二是设置[PersistChildren(false)]类特性表示要把集合标记作为属性方式保持和进行序列化

    在主控件的集合属性前要设置如下三个特性:

    [PersistenceMode(PersistenceMode.InnerDefaultProperty)]

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]

    [NotifyParentProperty(true)]

    第一个特性,指定集合属性为内部默认属性;第二个特性,指定要序列化的是集合属性的内容,而不是集合属性本身;第三个特性,指定集合属性的子属性修改时会通知父属性。

    新建一个Web自定义控件文件,并按以上所述进行设置,控件主类核心代码如下:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. /// <summary>
    6. /// 控件主类[复杂属性-内部默认属性]
    7. /// </summary>
    8. [DefaultProperty("Text")]
    9. [ToolboxData("<{0}:CollectionControlProperty runat=server></{0}:Collection ControlProperty>")]
    10. [PersistChildren(false)]
    11. [ParseChildren(true"Items")] 
    12. public class CollectionControlProperty : WebControl
    13. {
    14.     private Items items;
    15.     [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
    16.     [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    17.     [NotifyParentProperty(true)]
    18.     [TypeConverter(typeof(CollectionConverter))]
    19.     [Category("复杂属性")]
    20.     [Description("复杂属性——内部默认嵌套形式")]
    21.     public Items Items
    22.     {
    23.         get
    24.         {
    25.             if (this.items == null)
    26.             {
    27.                 this.items = new Items();
    28.             }
    29.             return this.items;
    30.         }
    31.     }
    32.     … …
    33. }

    除了设置上面所提到的属性外,本集合类还多了一个特性[TypeConverter(typeof(collection Converter)]。此特性指定本集合属性转换到代码视图时采用系统默认的集合转换器。针对常用的类型,系统提供了一组默认的转换器,后面章节会介绍怎样创建自定义复杂类型的类型转换器。

    经过以上设置后,在页面上拖动一个控件,并在属性窗口中增加填加几个子项,如图4-6所示。

     

    4-6  集合编辑器

     

    设置完后,回到源代码视图,会看到刚才设置好的几个子项:

    <cc1:CollectionControlProperty ID="CollectionControlProperty1" runat="server">

        <cc1:ListItem ID="ListItem1" runat="server" Text="红色" Value="red">

        </cc1:ListItem>

        <cc1:ListItem ID="ListItem2" runat="server" Text="蓝色" Value="blue">

        </cc1:ListItem>

        <cc1:ListItem ID="ListItem3" runat="server" Text="绿色" Value="green">

        </cc1:ListItem>

    </cc1:CollectionControlProperty>

    本节主要是完成一个复杂集合属性,并把集合属性设置为默认属性。本节示例控件的所有源代码请参阅随书光盘中的内容。

    4.4.2.4  内部嵌套编码默认属性

    请看下面这段我们经常使用的代码:

    <asp:DropDownList id="DropDownList1" runat="server" >

        <asp:ListItem Value="red">红色</asp:ListItem>

        <asp:ListItem Value="green">绿色</asp:ListItem>

    </asp:DropDownList>

    细心的读者可能看到,表示Text(“红色”位置的属性)的属性不像Value属性是附属于ListItem标记,而是在两个<asp:ListItem></asp:ListItem>标记之间呈现。这样的标记主要用于显示非HTML标记或非子控件的纯文本,本节主要完成这种格式属性的实现。

    为了保留前面控件已有的功能,重新定义两个类Item2ListItem2

    1Items集合类

    代码如下:

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. /// <summary>
    6. /// 菜单实现类[实用泛型集合]
    7. /// </summary>
    8. [
    9. ToolboxItem(false),
    10. ParseChildren(true)
    11. ]
    12. public class Items2 : List<ListItem2>
    13. {
    14.     //省略,此集合类内部代码与Items完全相同
    15. }
    2
    ListItem2子项类

    代码如下:

     

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. /// <summary>
    6. /// 子项类
    7. /// </summary>
    8. [ToolboxItem(false)]
    9. [TypeConverter(typeof(ExpandableObjectConverter))]    
    10. [ParseChildren(true"Text")]  
    11. [PersistChildren(false)]       
    12. public class ListItem2 : Control  
    13. {        
    14.     private string _Text;
    15.     private string _Value;        
    16.     public ListItem2()
    17.     { }
    18.     public ListItem2(string strText, string strValue)
    19.     {            
    20.         this._Text = strText;
    21.         this._Value = strValue;            
    22.     }        
    23.     /// <summary>
    24.     /// 文本属性
    25.     /// </summary>     
    26.     [NotifyParentProperty(true)]
    27.     [PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)]
    28.     [Description("复杂属性——内部默认嵌套形式")]
    29.     public string Text
    30.     {
    31.         get { return _Text; }
    32.         set { _Text = value; }
    33.     }
    34.     /// <summary>
    35.     /// 值属性
    36.     /// </summary>     
    37.     [NotifyParentProperty(true)]
    38.     public string Value
    39.     {
    40.         get { return _Value; }
    41.         set { _Value = value; }
    42.     }
    43. }

    此子项类要做一些特性设置,类元特性需要增加两个特殊的特性:

    Ø [ParseChildren(true, "Text")] 

    Ø [PersistChildren(false)]       

    第一个特性表示将子Text元素作为服务器控件的属性分析;第二个特性表示将该控件的属性作为嵌套元素保持。

    另外,还要注意要对作为编码内部属性的属性进行设置,比如这里为Text属性加上:

    [PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)] 

    u  进行如上设置后,增加一个主控件文件,并进行如下所示设置:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. [DefaultProperty("Text")]
    6. [ToolboxData("<{0}:EncodedInnerDefaultPropertyControl runat=server></{0}: EncodedInnerDefaultPropertyControl>")]
    7. [PersistChildren(false)]
    8. [ParseChildren(true"Items")]
    9. public class EncodedInnerDefaultPropertyControl : WebControl
    10. {
    11.     public EncodedInnerDefaultPropertyControl()
    12.     { 
    13.     }
    14.     private Items2 items;
    15.     [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
    16.     [DesignerSerializationVisibility(DesignerSerializationVisibility. Content)]
    17.     [NotifyParentProperty(true)]
    18.     [TypeConverter(typeof(CollectionConverter))]
    19.     [Category("复杂属性")]
    20.     [Description("复杂属性——内部默认嵌套形式")]
    21.     public Items2 Items
    22.     {
    23.         get
    24.         {
    25.             if (this.items == null)
    26.             {
    27.                 this.items = new Items2();
    28.             }
    29.             return this.items;
    30.         }
    31.     }
    32.     … …
    33. }

    上面主控件类与 4.4.2.3中主控件类设置完全相同,这里就不再作说明。

    设置完成后编译控件库,拖动此控件到页面中,可以看到在属性窗口增加了几个集合项,如图4-7所示。

     

    4-7  属性窗口

    集合设置界面与4.4.2.3节中的完全相同,但切换到代码视图界面,会发现序列化后的代码变化了,如下所示:

    <cc1:EncodedInnerDefaultPropertyControl ID="EncodedInnerDefaultProperty Control1" runat="server" Items-Capacity="4">    

        <cc2:ListItem2 ID="ListItem22" runat="server" Value="red">红色</cc2:ListItem2>

        <cc2:ListItem2 ID="ListItem23" runat="server" Value="blue">蓝色</cc2:ListItem2>

    </cc1:EncodedInnerDefaultPropertyControl>

    以看到Text属性已经不再作为ListItem的直接属性,而是嵌套在<ListItem2> </ListItem2>之间。

    本节主要说明控件内部嵌套编码默认属性格式的实现。在实现时需要注意的是,对ListItem2子项类进行一些元数据特性设置,因为Text是属于ListItem2类的属性。

    4.4.3  深入研究—复杂属性分析器

    4.4.3.1  使用AddParsedSubObject控制复杂内容(子控件)

    4.4.2节已经把各种各样的复杂属性类型都实现了,这些都是在实际开发中常用的属性格式,能够满足绝大多数开发需要。

    这一节讲解稍微复杂一点的属性格式。一般在一个控件中只能设置单个的属性为内部默认属性,比如4.4.2.3节中实现的属性:

    <cc1:CollectionControlProperty ID="CollectionControlProperty1" runat="server">

        <cc1:ListItem ID="ListItem1" runat="server" Text="红色" Value="red">

        </cc1:ListItem>

        <cc1:ListItem ID="ListItem2" runat="server" Text="蓝色" Value="blue">

        </cc1:ListItem>

        <cc1:ListItem ID="ListItem3" runat="server" Text="绿色" Value="green">

        </cc1:ListItem>      

    </cc1:CollectionControlProperty>  

    其中Items属性设置成了内部默认属性,如果控件中需要多个内部默认属性的格式,默认分析器对此是不支持的。如果强行设置了两个默认属性格式的属性,控件可以编译通过,但在页面的属性窗口设置多个复杂属性后,进行代码与设计器视图切换时系统会报以下错误:

     

     

    这说明不能利用默认的解析器分析多个设置了默认属性格式的子标记。为了解决这个问题,其中一种方法可以重写AddParsedSubObject来定制自己的页面解析子控件方法。主控件核心源代码如下:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. /// <summary>
    6. /// 本控件包含三个集合复杂属性: 两个内部默认嵌套形式; 一个内部嵌套形式.
    7. /// </summary>
    8. [ToolboxData("<{0}:MultiCollectionControlProperty runat=server></{0}:Multi CollectionControlProperty>")]
    9. [ParseChildren(false)]     
    10. public class MultiCollectionControlProperty : WebControl
    11. {
    12.     private Items items;
    13.     [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
    14.     [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    15.     [NotifyParentProperty(true)]
    16.     [TypeConverter(typeof(CollectionConverter))]
    17.     [Category("复杂属性")]
    18.     [Description("复杂属性——内部默认嵌套形式")]
    19.     public Items Items
    20.     {
    21.         get
    22.         {
    23.             if (this.items == null)
    24.             {
    25.                 this.items = new Items();
    26.             }
    27.             return this.items;
    28.         }
    29.     }
    30.     private Items2 items2;
    31.     [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
    32.     [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    33.     [NotifyParentProperty(true)]
    34.     [TypeConverter(typeof(CollectionConverter))]
    35.     [Category("复杂属性")]
    36.     [Description("复杂属性——内部默认嵌套形式")]
    37.     public Items2 Items2
    38.     {
    39.         get
    40.         {
    41.             if (this.items2 == null)
    42.             {
    43.                 this.items2 = new Items2();
    44.             }
    45.             return this.items2;
    46.         }
    47.     }
    48.     private Items3 items3;
    49.     [PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)]
    50.     [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    51.     [NotifyParentProperty(true)]
    52.     [TypeConverter(typeof(CollectionConverter))]
    53.     [Category("复杂属性")]
    54.     [Description("复杂属性——内部编码嵌套形式")]
    55.     public Items3 Items3
    56.     {
    57.         get
    58.         {
    59.             if (this.items3 == null)
    60.             {
    61.                 this.items3 = new Items3();
    62.             }
    63.             return this.items3;
    64.         }
    65.     }
    66.     protected override void AddParsedSubObject(object obj)
    67.     {            
    68.         if (obj is ListItem)
    69.         {
    70.             if (this.items == null)
    71.             {
    72.                 this.items = new Items();
    73.             }
    74.             this.items.Add((ListItem)obj);
    75.         }
    76.         if (obj is ListItem2)
    77.         {
    78.             if (this.items2 == null)
    79.             {
    80.                 this.items2 = new Items2();
    81.             }
    82.             this.items2.Add((ListItem2)obj);
    83.         }
    84.         if (obj is ListItem3)
    85.         {
    86.             if (this.items3 == null)
    87.             {
    88.                 this.items3 = new Items3();
    89.             }
    90.             this.items3.Add((ListItem3)obj);
    91.         }
    92.     }
    93. }

    本主控件类包含三个集合复杂属性两个内部默认嵌套形式属性(ItemsItems2);一个内部编码嵌套形式属性(Items3)。这三个集合类的子项也是使用前面示例中的子项类,其中ListItem3代码省略它与前面的ListItem1类的内部代码完全一样只是类名不同

    主类MultiCollectionControlProperty前面要加个很重要的元特性 [ParseChildren(false)],指定页解析器把嵌套的内容作为子控件解析。

    另外,还可以重写AddParseSubObject,定制自定义的页面解析实现,页面在设计模式下解析(从代码视图切换到设计视图)时,每检测到一个内部子控件都会触发此方法,此方法的参数object就是当前内部子控件标记生成的对象。方法体中的三个if语句分别判断当前对象是什么类型,如果是ListItem类型就把它添加到相关的类集合中,如上面代码把ListItem类型的对象增加到了Items集合中。只有这样,我们在设计视图中查看属性窗口中值时,当前集合才有值显示在属性集合编辑器中(弹出窗口编辑器)。在增加一个子项到集合中时,还要注意第一次往集合中增加子项时,集合值为null,要先为当前集合生成对象实例。

    事实上控件中的嵌套标记,不仅可以内置集合类型子标记,还可以增加任意类型的标记,只要子标记具有前缀标志和runat属性即可;如果没有前缀和runat属性,系统也不会报错,只是页面解析器会把不具有前缀和runat属性的整个块标记都用LiteralControl包装后返回LiteralControl的对象(返回给AddParseSubObject的参数obj),而不管此块有多大。

    编译此控件后拖一个控件到页面中会在属性窗口中看到三个并列的集合属性如图4-8所示。

     

    4-8  属性窗口

     

    为三个集合属性分别设置几个子项切换到源代码视图会看到如下源代码:

    <cc1:MultiCollectionControlProperty ID="MultiCollectionControlProperty1" runat="server">

        <cc2:ListItem runat="server" Text="红色" Value="red" ID="ListItem1"> </cc2:ListItem>

        <cc2:ListItem runat="server" Text="绿色" Value="green" ID="ListItem2"> </cc2:ListItem>

        <cc2:ListItem2 runat="server" Value="blue" ID="ListItem21">蓝色</cc2:ListItem2>

        <cc2:ListItem2 runat="server" Value="gray" ID="ListItem22">灰色</cc2:ListItem2>

        <cc2:ListItem3 runat="server" Text="黄色" Value="yellow" ID="ListItem31"> </cc2:ListItem3>

        <cc2:ListItem3 runat="server" Text="淡蓝" Value="lightblue" ID="ListItem32"> </cc2:ListItem3>

    </cc1:MultiCollectionControlProperty>

    可以看到ListItemListItem2ListItem3非常有序地嵌套在主控件内部从而实现了主控件内部多个复杂默认属性嵌套功能

    AddParseSubObject方法固然能够帮助我们实现控件内部多个复杂默认属性的嵌套功能但它也有局限性就是前面提到过的子标记必须是子控件形式标记子标记要具有前缀标志和runat属性,否则整个非子控件类型块标记都用LiteralControl包装后返回LiteralControl的对象(返回给AddParseSubObject的参数obj而不管此块有多大

    以上是通过重写AddParseSubObject方法实现页面解析功能;另外,.NET Framework为控件设计模式支持专门提供了一个控件构造类:System.Web.UI.ControlBuilder,通过继承此类也可以实现定制页面解析,而且更灵活,后面会专门对比进行介绍。

    4.4.3.2  使用ControlBuilder解析复杂内容

    通过System.Web.UI.ControlBuilder类定制页面解析逻辑,可以定制任意类型的标记,而不像重写AddParseSubObject方法那样限定子标记必须是子控件,且必须有前缀和runat属性,下面直接通过一个例子来说明一下此类的用法。

    首先建立两个文件ScriptItem.csScriptItemCollection.cs,分别定义ScriptItem类和ScriptItemCollection类。其中,ScriptItem类主要存储用户自定义的客户端脚本命令(JavaScript块),ScriptItemCollection可以定义一个集合容器,每个项都是一个 ScriptItem项。与前面讲的集合实现非常类似。这两个类的完整代码如下:

    1ScriptItem

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. private string _Text;        
    6. [DefaultValue("")]
    7. [Editor("System.ComponentModel.Design.MultilineStringEditor,System.Design"typeof(UITypeEditor))]
    8. [PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)]       
    9. [NotifyParentProperty(true)]
    10. /// <summary>
    11. /// JavaScript脚本块
    12. /// </summary>        
    13. public string Text
    14. {
    15.     get
    16.     {
    17.         return _Text;
    18.     }
    19.     set
    20.     {
    21.         _Text = value;
    22.     }
    23. }

    该类中的Text就是用于存储用户定义的脚本块;Editor元数据特性指定在属性窗口中Text属性的编辑器是一个下拉块输入编辑器,关于属性编辑器下一节会详细讲解,这里仅知道它的功能即可。

    需要注意的是,在上一节使用AddParsedSubObject实现页面解析子控件时,要嵌套的三个集合的子标记:ListItemListItem2ListItem3都继承了Control基类,目的是把这些子标记作为子控件(也就具有了前缀和runat属性),而这里的ScriptItem没有继承任何基类,这样就避免了继承一些基类中的冗余属性和方法。

    2ScriptItemCollecton

    /// <summary>

    /// 获得本书更多内容,请看:

    /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

    /// </summary>

     

    [ToolboxItem(false)]   

    public class ScriptItemCollection : List<ScriptItem>

    {

        public ScriptItemCollection() : base() { }

        public ScriptItemCollection(int capacity) : base(capacity) { }

        public ScriptItemCollection(IEnumerable<ScriptItem> collection):base (collection) { }

    }

     

    u  定义这两个类之后,实现我们自己的ControlBuilder类,可以直接继承该类并实现自己的方法,已经预先实现好了的构造器类代码如下所示:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. public class ScriptItemBuilder : ControlBuilder
    6. {       
    7.     public override Type GetChildControlType(string tagName, IDictionary attributes)
    8.     {
    9.         if (string.Compare(tagName.ToLower(), "scriptitem"false, CultureInfo. InvariantCulture) == 0)
    10.         {
    11.             return typeof(ScriptItem);
    12.         }
    13.         return null;
    14.     }
    15.     public override bool AllowWhitespaceLiterals()
    16.     {
    17.         return false;            
    18.     }        
    19. }

    在该类中要做的最重要的事情是重写方法GetChildControlType,在页面解析器分析主控件的每个子标记时,都会调用一次此方法。

    该方法的第一个参数表示当前正在解析的控件标记字符串,第二个参数表示标记上所有特性的字典集合。方法体中的if语句的功能是,假如当前解析的标记是“scriptitem”(就是后面定义到主控件的集合属性名称),则返回ScriptItem类的类型,且通过ToLower()方法实现不区分大小写。需要注意的是,这里我们做的工作非常简单,只是匹配相应的字符串标记并返回一个类型。而AddParsedSubObject则要自己处理当前对象的值。还有个重写方法AllowWhitespaceLiterals用于指定控件的开始标记和结束标记之间是否允许存在空白。

    定义完自己的构造器后,通过为主控件增加如下元数据特性,指定主控件的解析器:

    [ControlBuilder(typeof(ScriptItemBuilder))]

    u  设置完后,完整的主控件类源代码如下:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. [ToolboxData("<{0}:ControlBuilderControl runat=server></{0}:ControlBuilder Control>")]
    6. [ParseChildren(true"ScriptItems")]
    7. [ControlBuilder(typeof(ScriptItemBuilder))]
    8. public class ControlBuilderControl : WebControl
    9. {
    10.     private ScriptItemCollection _ScriptItems = new ScriptItemCollection();
    11.     /// <summary>
    12.     /// 脚本命令集合属性
    13.     /// </summary>
    14.     [PersistenceMode(PersistenceMode.InnerProperty)]
    15.     [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    16.     [Description("工具按钮集设置")]
    17.     [Category("工具按钮——属性设置")]
    18.     public ScriptItemCollection ScriptItems
    19.     {
    20.         get
    21.         {
    22.             if (_ScriptItems == null)
    23.             {
    24.                 _ScriptItems = new ScriptItemCollection();
    25.             }
    26.             return _ScriptItems;
    27.         }
    28.     }
    29.     //… …  
    30. }

    整个主控件只包括一个集合属性。需要注意的是我们把这个属性定义成内部嵌套标记形式,即我们在<ScriptItem>标记外面又嵌套了一个<ScriptItems>,把ScriptItems作为主控件的直接嵌套标记。

    编译此控件后往页面中添加一个控件,并在属性窗口中增加命令项,然后切换到代码视图会看到如下格式的标记代码:

    <cc1:controlbuildercontrol id="ControlBuilderControl1" runat="server">

       <ScriptItems>

           <cc1:ScriptItem>alert('Hello King');</cc1:ScriptItem>

           <cc1:ScriptItem>alert('Hello Rose');</cc1:ScriptItem>         

           <cc1:ScriptItem>alert('Hello James');</cc1:ScriptItem>               

       </ScriptItems>          

    </cc1:controlbuildercontrol>

    上面生成了一个具有ScriptItem集合属性的标记集合。与上节我们使用的AddParsedSub Object相比,嵌套标记中多了一个<ScriptItems>内部嵌套标记,且ScriptItem没有前缀和runat属性,如果使用AddParsedSubObject,会把整个<ScriptItems>块(包括其中的ScriptItem一块作为文本LiteralControl返回,这显然不符合我们的要求;另外,这里的<ScriptItem>虽然具有前缀,但它也不具有runat的属性,但也能够正确被页面解析器进行正反向解析

    到目前为止,已经分别使用重载基类AddParsedSubObject方法和继承ControlBuilder的构造实现类实现了自定义的页面解析功能。那么使用它们两个的场景是怎样呢?其实在讲解它们的过程中笔者已经作了不少比较,再总结一下:

    1)在绝大多数情况下,如果页面中只要设置一个内部嵌套标记属性或不需要设置内部嵌套标记属性,则不需要重写AddParsedSubObject和实现ControlBuilder的继承类。这两种方式主要是在实现页面中有多个默认嵌套属性时使用。

    2AddParsedSubObject实现比较简单,仅实现一个方法,一般用于复杂属性单一且比较少的情况。

    3)实ControlBuilder的定制构造器类比重载AddParsedSubObject要麻烦些,但功能更强,能处理更灵活的嵌套标记。AddParsedSubObject最大的限制是它的内部必须是子控件类型。

    4)两种方式都是ASP.NET提供的两种解决方案,都有它们使用的场景,可以根据自己喜好选择,当习惯使用构造器后,会发现构造器功能更强大、更灵活,用起来更顺手,它可以完全替代重载AddParsedSubObject方式。

    到现在为止基本上已经把我们见过的所有的属性标记格式都实现了一遍,4.4.3.2节也把平时很少用到的定制页面解析器功能详细地讲解了一下,其中有些标记在平常开发中比较少用到。本章可以作为查找手册使用,什么时候用到这些内容,什么时候过来查即可。下一节会有更精彩的内容。

    4.5  深入研究—定制自己的属性编辑器

    对于控件的所有属性,如果都提供非常友好的属性编辑器,使用者使用起来会更加方便。本节主旨就是讲解一下控件属性编辑器,4.5.1节提供一些系统通用编辑器;在复杂属性中,集合属性是最重要的最常用的属性,4.5.2节将主要讲解怎样定制复杂集合类型编辑器以及一些特殊比较酷的编辑器类型。

    这些设计器的执行主要是在设计模式下,直接与IDE交互,在编程时可以直接使用System.Windows命名空间开头的一些命名空间下的类。这里首先加入几个本节需要使用到的引用。右击控件库工程,选择“添加引用”命令,如图4-9所示。

    选择“添加引用”命令后会打开“添加引用”对话框,如图4-10所示。

         

     

    4-9  添加引用                                          

     

    4-10 “添加引用”对话框

     

    在对话框中找到以下三个引用程序集:

    1System.Designer

    2System.Drawing.Design

    3System.Windows.Forms

    单击“确定”按钮,这样在需要使用的地方打开程序集中的命名空间,就可以使用程序集中的系统类了。

    4.5.1  系统属性编辑器

    很有必要用一小节讲解一下系统提供的一些编辑器。读者对这些编辑器可能都比较熟悉,但它们是怎么使用的呢?其实使用都很简单,仅在每个需要配置的属性前面指定一个标志某种属性编辑器的元数据特性即可。下面就分别介绍一下它们。

    4.5.1.1  多行下拉文本属性编辑器

    1.配置方式

    /// <summary>

    /// 获得本书更多内容,请看:

    /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

    /// </summary>

    [Editor("System.ComponentModel.Design.MultilineStringEditor,System.Design", typeof(UITypeEditor))]

    public string TxtEditor

    {

        //... ...

    }

    Editor特性就是指定属性编辑器类型,后面的几种系统属性编辑器类型也是如此。

    2.属性浏览器中的效果(如图4-11所示)

     

    4-11  多行下拉文本属性编辑器

     

    4.5.1.2  色值选择属性编辑器

    1.配置方式

    [Editor("System.ComponentModel.Design.ColorEditor,System.Design", typeof(UITypeEditor))]

    public Color ColorEditor

    {

        //... ...

    }

    2.属性浏览器中的效果(如图4-12所示)

     

    4-12  色值选择属性编辑器

     

    4.5.1.3  文件选择属性编辑器

    1.配置方式

    [Editor(typeof(FileNameEditor), typeof(UITypeEditor))]

    public string FileName

    {

        //... ...

    }

    2.属性浏览器中的效果(如图4-13所示)

     

    4-13  文件选择属性编辑器

    上图即为单击属性窗口中“文件名属性”按钮弹出的“文件选择属性编辑器”对话框,其实也是调用的Windows系统的“打开文件”对话框。

    4.5.1.4  目录选择属性编辑器

    1.配置方式

    [Editor(typeof(FolderNameEditor), typeof(UITypeEditor))]

    public string FolderNameEditor

    {

        //... ...

    }

    2.属性浏览器中的效果(如图4-14所示)

     

    4-14  目录选择属性编辑器

     

    4.5.1.5  连接字符串属性编辑器

    1.配置方式

    [Editor(typeof(System.Web.UI.Design.ConnectionStringEditor), typeof(UITypeEditor))]

    public string ConnectionStringEditor

    {

        //... ...

    }

    2.属性浏览器中的效果(如图4-15所示)

     

    4-15  连接字符串属性编辑器

     

    4.5.1.6  表达式绑定集合属性编辑器

    1.配置方式

    [Editor(typeof(System.Web.UI.Design.ExpressionsCollectionEditor), typeof(UITypeEditor))]

    public string ExpressionsCollectionEditor

    {

        //... ...

    }

    2.属性浏览器中的效果(如图4-16所示)

     

    4-16  表达式绑定集合属性编辑器

     

    在此窗口可以为属性指定要绑定到应用程序的配置文件、连接字符串或者资源文件。

    4.5.1.7  用户控件对话框编辑器

    1.配置方式

    [Editor(typeof(System.Web.UI.Design.UserControlFileEditor), typeof(UITypeEditor))]

    public string UserControlFileEditor

    {

        //... ...

    }

    2.属性浏览器中的效果(如图4-17所示)

     

    4-17  用户控件对话框编辑器

     

    此窗口用于选择当前站点下的用户控件文件(*.ascx),且默认的可选择路径不像文件和目录选择是本计算机硬盘,而是当前站点。

    主控件的完整源代码如下:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. [ToolboxData("<{0}:EditorControl runat=server></{0}:EditorControl>")]
    6. public class EditorControl : WebControl
    7. {
    8.     string strTxtEditor;
    9.     [Category("编辑器")]       
    10.     [Description("下拉多行文本编辑器")] 
    11.     [Editor("System.ComponentModel.Design.MultilineStringEditor, System.Design"typeof(UITypeEditor))]
    12.     public string TxtEditor
    13.     {
    14.         get
    15.         {
    16.             return strTxtEditor;
    17.         }
    18.         set
    19.         {
    20.             strTxtEditor = value;
    21.         }
    22.     }
    23.     Color cColorEditor;
    24.     [Category("编辑器")]
    25.     [Description("颜色编辑器")]
    26.     [Editor("System.ComponentModel.Design.ColorEditor,System.Design"typeof(UITypeEditor))]
    27.     public Color ColorEditor
    28.     {
    29.         get
    30.         {              
    31.             return cColorEditor;
    32.         }
    33.         set
    34.         {
    35.             cColorEditor = value;
    36.         }
    37.     }
    38.     string strFileName;
    39.     [Category("编辑器")]
    40.     [Description("文件选择编辑器")]
    41.     [Editor(typeof(FileNameEditor), typeof(UITypeEditor))]
    42.     public string FileName
    43.     {
    44.         get
    45.         {
    46.              return strFileName;
    47.         }
    48.         set
    49.         {
    50.             strFileName = value;
    51.         }
    52.     }
    53.     string strFolderNameEditor;
    54.     [Category("编辑器")]
    55.     [Description("目录选择编辑器")]
    56.     [Editor(typeof(FolderNameEditor), typeof(UITypeEditor))]
    57.     public string FolderNameEditor
    58.     {
    59.         get
    60.         {
    61.             return strFolderNameEditor;
    62.         }
    63.         set
    64.         {
    65.             strFolderNameEditor = value;
    66.         }
    67.     }
    68.     string strConnectionStringEditor;
    69.     [Category("编辑器")]
    70.     [Description("连接字符串编辑器")]
    71.     [Editor(typeof(System.Web.UI.Design.ConnectionStringEditor), typeof(UITypeEditor))]
    72.     public string ConnectionStringEditor
    73.     {
    74.         get
    75.         {
    76.             return strConnectionStringEditor;
    77.         }
    78.         set
    79.         {
    80.             strConnectionStringEditor = value;
    81.         }
    82.     }
    83.     string strExpressionsCollectionEditor;
    84.     [Category("编辑器")]
    85.     [Description("编辑表达式绑定集合的编辑器")]
    86.     [Editor(typeof(System.Web.UI.Design.ExpressionsCollectionEditor), typeof(UITypeEditor))]
    87.     public string ExpressionsCollectionEditor
    88.     {
    89.         get
    90.         {
    91.             return strExpressionsCollectionEditor;
    92.         }
    93.         set
    94.         {
    95.             strExpressionsCollectionEditor = value;
    96.         }
    97.     }
    98.     string strUserControlFileEditor;
    99.     [Category("编辑器")]
    100.     [Description("用户控件(ascx)对话框编辑器")]
    101.     [Editor(typeof(System.Web.UI.Design.UserControlFileEditor), typeof(UITypeEditor))]
    102.     public string UserControlFileEditor
    103.     {
    104.         get
    105.         {
    106.             return strUserControlFileEditor;
    107.         }
    108.         set
    109.         {
    110.             strUserControlFileEditor = value;
    111.         }
    112.     }
    113. //... ...
    114. }

    这些代码比较简单,就不作解释了。以上仅列出一些可能会经常用到的属性编辑器,系统提供的属性编辑器不止这些,像前面讲的集合也是使用系统默认的集合属性编辑器。其他的属性编辑器可以在使用的过程中慢慢研究。下一节通过几个例子详细讲一下怎样定制自己的属性编辑器。

    4.5.2  定制属性编辑器

    提供了很多属性编辑器,能够满足绝大多数复杂度不是很高的控件的需要。这一节主要通过四个小例子讲解怎样定制个性化的属性编辑器,其实只要能够想到,我们就能够做到。

    4.5.2.1  定制多类型集合属性编辑器

    前面我们在为控件增加集合属性时,默认情况下会使用系统默认的集合编辑器,而我们这里自定义的集合属性编辑器功能更强,先看一下它实现后的效果图,如图4-18所示。

    可以看到,该编辑器除了能够实现基本的集合属性编辑功能外,通过单击“添加”按钮右边的下拉按钮还可以选择添加项的类型,即我们可以定义任意个不同类型的集合项作为属性集合内容,这里我们定义两个集合子项类别:CommonItem(子项)和CommandSeperator(分隔符),以它们作为示例讲解。下面就来看一下它的实现过程。

     

    4-18  定制多类型集合属性编辑器

     

    由于涉及集合,首先还是定义几个与集合实现相关的类。定义一个抽象类ItemBase,表示每个集合子项类的子类,任何集合子类都要继承该类。其类代码如下所示:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. /// <summary>
    6. /// 命令项基类
    7. /// </summary>
    8. public abstract class ItemBase
    9. {
    10.     private bool _EnableViewState = true;
    11.     public bool EnableViewState
    12.     {
    13.         get
    14.         {
    15.             return _EnableViewState;
    16.         }
    17.         set
    18.         {
    19.             _EnableViewState = value;
    20.         }
    21.     }
    22. }

    这里为了简化代码,只定义了一个EnableViewState的属性,表示是否启用视图状态,在使用时还可以继承Control等基类,使用Control等类的类成员。这里要注意的是此类定义成了抽象类,此类不会单独生成实例添加到集合中,也不会被作为一种集合子类型显示到属性编辑窗口中供用户选择,因为ItemBase在这里没有表示具体的集合子项,也不具有集合子项意义。所有集合子类型最终是以它们的基类型ItemBase添加到集合中统一管理的。

    下面定义一个具体的集合子项类CommonItem,表示一个按钮类型,类结构代码如下:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. /// <summary>
    6. ///  命令按钮类
    7. /// </summary>    
    8. [ToolboxItem(false)]    
    9. public class CommandItem : ItemBase
    10. {
    11.     private CommandActionType _CommandActionType;
    12.     //命令按钮文本
    13.     private string _Text = null;
    14.     //快捷键
    15.     private string _AccessKey = null;
    16.     //提示
    17.     private string _ToolTip = null;
    18.     //是否可用
    19.     private bool _Enable = true;        
    20.     /// <summary>
    21.     /// 默认构造方法
    22.     /// </summary>        
    23.     public CommandItem()
    24.     {
    25.     }
    26.     /// <summary>
    27.     /// 构造方法[ButtonCommand]
    28.     /// </summary>
    29.     /// <param name="bitButtonItemType"></param>        
    30.     /// <param name="strCommandText"></param>
    31.     /// <param name="strAccessKey"></param>
    32.     /// <param name="strToolTip"></param>        
    33.     public CommandItem(CommandActionType commandActionType, string strText, string strAccessKey, string strToolTip)
    34.     {
    35.         this._CommandActionType = commandActionType;
    36.         this._Text = strText;
    37.         this._AccessKey = strAccessKey;
    38.         this._ToolTip = strToolTip;            
    39.     }
    40.     /// <summary>
    41.     /// 命令按钮类型
    42.     /// </summary>
    43.     [NotifyParentProperty(true)]
    44.     public CommandActionType CommandActionType
    45.     {
    46.         get
    47.         {
    48.             return _CommandActionType;
    49.         }
    50.         set
    51.         {
    52.             _CommandActionType = value;
    53.         }
    54.     }
    55.     /// <summary>
    56.     /// 命令按钮文本
    57.     /// </summary>
    58.     [NotifyParentProperty(true)]
    59.     [Browsable(false)]
    60.     public string Text
    61.     {
    62.         get
    63.         {
    64.             return _Text;
    65.         }
    66.         set
    67.         {
    68.             _Text = value;
    69.         }
    70.     }
    71.     /// <summary>
    72.     /// 快捷键
    73.     /// </summary>
    74.     [NotifyParentProperty(true)]
    75.     [Browsable(false)]
    76.     public string AccessKey
    77.     {
    78.         get
    79.         {
    80.             return _AccessKey;
    81.         }
    82.         set
    83.         {
    84.             _AccessKey = value;
    85.         }
    86.     }
    87.     /// <summary>
    88.     /// 帮助提示文本
    89.     /// </summary>
    90.     [NotifyParentProperty(true)]
    91.     [Browsable(false)]
    92.     public string ToolTip
    93.     {
    94.         get
    95.         {
    96.             return _ToolTip;
    97.         }
    98.         set
    99.         {
    100.             _ToolTip = value;
    101.         }
    102.     }
    103.     /// <summary>
    104.     /// 是否可用
    105.     /// </summary>
    106.     [NotifyParentProperty(true)]
    107.     [Browsable(false)]
    108.     public bool Enable
    109.     {
    110.         get
    111.         {
    112.             return _Enable;
    113.         }
    114.         set
    115.         {
    116.             _Enable = value;
    117.         }
    118.     }
    119. }

    类代码很简单,主要包括描述按钮的一些基本信息:文本、快捷键、提示、可用性以及按钮类型(新增/保存/删除等)。这里仅需要注意CommandItem类继承了上面我们定义的抽象集合子项基类ItemBase,所有类型的子项都要继承于该类。

    CommandItem的第一个属性是枚举类型,表示此按钮的功能类型(新增/删除/上一页/下一页等),此属性CommandActionType对应的枚举代码结构如下所示:

    /// <summary>

    /// 获得本书更多内容,请看:

    /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

    /// </summary>

     

    /// <summary>

    /// 命令项枚举

    /// </summary>

    public enum CommandActionType

    {

        //保存

        Save,

     

        //新增

        Add,

     

        //编辑

        Edit,

     

        //删除

        Delete,

     

        //关闭

        Close

     

        //...

    }

     

    u  接下来再定义一个子项类型:CommandSeperator类型,表示分隔符类型,即一组按钮与另一组按钮之间的分隔符。比如:“首页”、“上一页”、“下一页”、“末页”,这是一组具有类似功能的一级按钮。另一组:“新增”、“修改”、“删除”、“查看”属于一组功能类似按钮,这两组按钮之间需要用某个分隔符分开,这样可以使使用者更容易区分各个按钮的功能,外观布局也不会显示零乱。类代码如下所示:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. /// <summary>
    6. /// 分隔符类
    7. /// </summary>
    8. [ToolboxItem(false)]
    9. public class CommandSeperator : ItemBase
    10. {
    11.     private Unit width;
    12.     private Unit Width
    13.     {
    14.         get
    15.         {
    16.             return width;
    17.         }
    18.         set
    19.         {
    20.             width = value;
    21.         }
    22.     }
    23.     private Unit height;
    24.     private Unit Height
    25.     {
    26.         get
    27.         {
    28.             return height;
    29.         }
    30.         set
    31.         {
    32.             height = value;
    33.         }
    34.     }
    35. }

    此分隔符类仅包括两个属性:宽度和高度。另外,它也继承了ItemBase抽象类。

    到现在为止,已经定义完四个类:抽象基类(ItemBase),按钮类(CommandItem),分隔按钮类(CommandSeperator),以及一个功能枚举类(CommandActionType)。然后就可以定义一个存储以上各个按钮类型的集合类了,类名为CommandCollection,此集合为强类型集合类,类代码如下:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. /// <summary>
    6. /// 工具按钮集合类
    7. /// </summary>
    8. [ToolboxItem(false)]
    9. [ParseChildren(true)]
    10. [Editor(typeof(CommandCollectionEditor), typeof(UITypeEditor))]
    11. public class CommandCollection : Collection<ItemBase>
    12. {
    13.     #region 定义构造函数
    14.     public CommandCollection()
    15.         : base()
    16.     {
    17.     }
    18.     #endregion
    19.     /// <summary>
    20.     /// 得到集合元素的个数
    21.     /// </summary>
    22.     public new int Count
    23.     {
    24.         get
    25.         {
    26.             return base.Count;
    27.         }
    28.     }
    29.     /// <summary>
    30.     /// 表示集合是否为只读
    31.     /// </summary>
    32.     public bool IsReadOnly
    33.     {
    34.         get
    35.         {
    36.             return false;
    37.         }
    38.     }
    39.     /// <summary>
    40.     /// 添加对象到集合
    41.     /// </summary>
    42.     /// <param name="item"></param>
    43.     public new void Add(ItemBase item)
    44.     {
    45.         base.Add(item);
    46.     }
    47.     /// <summary>
    48.     /// 清空集合
    49.     /// </summary>
    50.     public new void Clear()
    51.     {
    52.         base.Clear();
    53.     }
    54.     /// <summary>
    55.     /// 判断集合中是否包含元素
    56.     /// </summary>
    57.     /// <param name="item"></param>
    58.     /// <returns></returns>
    59.     public new bool Contains(ItemBase item)
    60.     {
    61.         return base.Contains(item);
    62.     }
    63.     /// <summary>
    64.     /// 移除一个对象
    65.     /// </summary>
    66.     /// <param name="item"></param>
    67.     /// <returns></returns>
    68.     public new bool Remove(ItemBase item)
    69.     {
    70.         return base.Remove(item);
    71.     }
    72.     /// <summary>
    73.     /// 设置或取得索引项
    74.     /// </summary>
    75.     /// <param name="index"></param>
    76.     /// <returns></returns>
    77.     public new ItemBase this[int index]
    78.     {
    79.         get
    80.         {
    81.             return base[index];
    82.         }
    83.         set
    84.         {
    85.             base[index] = value;
    86.         }
    87.     }        
    88. }

    该集合类继承Collection<ItemBase>类,表示强类型集合,且每个子项的类型为ItemBase,从这里可以想到我们上面定义的两个子项类CommandItemCommandSeperator都要继承于ItemBase的原因了。[ParseChilderen(true)]表示把当前属性作为主控件的属性(而非子控件)进行解析。

    本节的重点,也是最重要的一个属性[Editor(typeof(CommandCollectionEditor)typeof (UITypeEditor))]表示指定此集合类的集合编辑器为CommandCollectionEditor,即在主控件中凡是定义为CommandCollection类的属性都会把CommandCollectionEditor作为它的编辑器。下面详细介绍一下编辑器类是怎么使用的。还是先看一下 CommandCollectionEditor编辑器类的源代码:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. /// <summary>
    6. /// 集合属性编辑器
    7. /// </summary>
    8. public class CommandCollectionEditor : CollectionEditor
    9. {
    10.     public CommandCollectionEditor(Type type)
    11.         : base(type)
    12.     { }
    13.     protected override bool CanSelectMultipleInstances()
    14.     {
    15.         return true;
    16.     }
    17.     protected override Type[] CreateNewItemTypes()
    18.     {            
    19.         return new Type[] { typeof(CommandItem), typeof(CommandSeperator) };
    20.     }
    21.     protected override object CreateInstance(Type itemType)
    22.     {
    23.         if (itemType == typeof(CommandItem))
    24.         {
    25.             return new CommandItem();
    26.         }
    27.         if (itemType == typeof(CommandSeperator))
    28.         {
    29.             return new CommandSeperator();
    30.         }
    31.         return null;
    32.     }
    33. }

    实现一个集合编辑器一般要继承System.ComponentModel.Design.CollectionEditor,并重写该类的一些方法。下面分别说一下各个方法的作用。

    Ø 集合编辑器中的构造方法 CommandCollectionEditor ,主要完成自定义的初始化功能。该方法中的参数返回该编辑器作用的对象实例(在这里是CommandCollection的一个对象实例),可以取到当前CommandCollection对象的所有数据。

    Ø 方法CanSelectMultipleInstances的返回值表示是否能够在编辑窗口选择多个实例,这里设置返回true

    Ø 重写方法CreateNewItemTypes,返回我们定义的两个集合类型:

    return new Type[] { typeof(CommandItem), typeof(CommandSeperator) };

    CommandItemCommandSeperator是我们定义的两个集合类型。在单击主控件属性窗口中集合属性编辑器“”形状按钮时,此方法执行,把所有定义的类型加载到系统集合中缓存起来,然后根据此集合值在编辑器界面中呈现可能选择类型的列表。

    Ø CreateInstance方法主要是负责建立一个集合子项类型实例。

    至此所有功能类都建立完成了,最后建立主控件类,并应用CommandCollection集合类作为控件的一个属性,代码如下所示:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. [DefaultProperty("ToolBarItems")]
    6. [ToolboxData("<{0}:MultiTypeCollectionEditorControl runat=server></{0}: MultiTypeCollectionEditorControl>")]
    7. [ParseChildren(true"ToolBarItems")]
    8. public class MultiTypeCollectionEditorControl : WebControl
    9. {
    10.     private CommandCollection _ToolBarItems = new CommandCollection();
    11.     [PersistenceMode(PersistenceMode.InnerProperty)]
    12.     [DesignerSerializationVisibility(DesignerSerializationVisibility. Content)]
    13.     [Description("工具按钮集设置")]
    14.     [Category("集合设置")]
    15.     public CommandCollection ToolBarItems
    16.     {
    17.         get
    18.         {
    19.             if (_ToolBarItems == null)
    20.             {
    21.                 _ToolBarItems = new CommandCollection();
    22.             }         
    23.             return _ToolBarItems;
    24.         }
    25.     }
    26. //....
    27. }

    主控件定义了一个重要的类元数据特性[ParseChildren(true"ToolBarItems")],表示把ToolBarItems作为控件的属性进行解析。其他属性在前面的章节已经讲解多次了,这里就不再赘述。

    编译控件源代码库,在页面设计器中置一个控件,然后单击属性窗口中对应的集合属性,会打开我们刚刚定义的集合属性编辑器,如图4-19所示。

     

    4-19  集合编辑器

     

    在这里就可以选择命令按钮或分隔符按钮填充集合了。另外,在成员列表中如果不想看到带命名空间的类名项,比如只让它显示CommandItem而不是KingControls.CommandItem,只要为其增加一个类型转换器即可。后面4.6节会详细讲解类型转换器的实现。这个功能比较简单,如果需要,读者可以自己实现它。

    好了,本节内容已经讲解完,在讲解过程中使用到了很多类,这些类都是在实际开发中常用的一些类,只是限于篇幅笔者把它们都精简化了,读者也可以体会一下它们的用途。

    4.5.2.2  定制模态属性编辑器

    这一节我们学习定制另一种属性编辑器:模态编辑器,在此编辑器中单击一个按钮将弹出一个窗体,从窗体选择数据后会把值返回到属性窗口中。最重要的一点是我们可以自定义此选择数据的模态窗口内容,比上面的集合编辑器更灵活。还是先看一下效果图,如图4-20所示。

     

    4-20  模态属性编辑器

     

    上图是以一个表示选择食品(水果/肉类/蔬菜等)的属性为例而定制的一个模态选择窗口,单击属性旁边的“”按钮就会弹出图中左侧的模态数据选择窗口。

    下面就来说一下它是怎么实现的。首先要说明的是由于在设计模式下且模态是直接供IDE接口调用的,因此这里弹出的窗口就是一个非常普通的WinForm窗口。在我们控件中新增一个WinForm文件CategoryWindow.cs,如图4-21所示。

     

    4-21 “添加新项”对话框

     

    增加完后,放置一个ComboBox(提供绑定食品类别数据的选择列表)和两个Button控件(“确定”和“取消”)到窗体中,再在“确定”按钮的事件中增加数据返回功能。

    系统会把窗体类分成两个部分类文件:CategoryWindow.csategoryWindow.Designer.csCategoryWindow.Designer.cs主要存储窗体和内部控件内容信息;CategoryWindow.cs主要供开发人员完成交互逻辑使用。下面分别来看一下它们的源代码。

    1CategoryWindow.Designer.cs文件中窗体部分的类代码

    /// <summary>

    /// 获得本书更多内容,请看:

    /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

    /// </summary>

     

    partial class CategoryWindow

    {

        /// <summary>

        /// 必需的设计器变量。

        /// </summary>

        private System.ComponentModel.IContainer components = null;

     

        /// <summary>

        /// 清理所有正在使用的资源。

        /// </summary>

        /// <param name="disposing">如果应释放托管资源,为 true;否则为 false</param>

        protected override void Dispose(bool disposing)

        {

            if (disposing && (components != null))

            {

                components.Dispose();

            }

            base.Dispose(disposing);

        }

     

        #region Windows 窗体设计器生成的代码

     

        /// <summary>

        /// 设计器支持所需的方法 - 不要

        /// 使用代码编辑器修改此方法的内容。

        /// </summary>

        private void InitializeComponent()

        {

            this.comboBox1 = new System.Windows.Forms.ComboBox();

            this.button1 = new System.Windows.Forms.Button();

            this.button2 = new System.Windows.Forms.Button();

            this.SuspendLayout();

            //

            // comboBox1

            //

            this.comboBox1.FormattingEnabled = true;

            this.comboBox1.Location = new System.Drawing.Point(23, 12);

            this.comboBox1.Name = "comboBox1";

            this.comboBox1.Size = new System.Drawing.Size(217, 20);

            this.comboBox1.TabIndex = 0;

            //

            // button1

            //

            this.button1.DialogResult = System.Windows.Forms.DialogResult.OK;

            this.button1.Location = new System.Drawing.Point(84, 53);

            this.button1.Name = "button1";

            this.button1.Size = new System.Drawing.Size(75, 23);

            this.button1.TabIndex = 1;

            this.button1.Text = "确定";

            this.button1.UseVisualStyleBackColor = true;            

            //

            // button2

            //

            this.button2.DialogResult = System.Windows.Forms.DialogResult.Cancel;

            this.button2.Location = new System.Drawing.Point(165, 53);

            this.button2.Name = "button2";

            this.button2.Size = new System.Drawing.Size(75, 23);

            this.button2.TabIndex = 2;

            this.button2.Text = "取消";

            this.button2.UseVisualStyleBackColor = true;

            //

            // CategoryWindow

            //

            this.AcceptButton = this.button1;

            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);

            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;

            this.ClientSize = new System.Drawing.Size(252, 96);

            this.Controls.Add(this.button2);

            this.Controls.Add(this.button1);

            this.Controls.Add(this.comboBox1);

            this.Cursor = System.Windows.Forms.Cursors.Default;

            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D;

            this.MaximizeBox = false;

            this.MinimizeBox = false;

            this.Name = "CategoryWindow";

            this.StartPosition=System.Windows.Forms.FormStartPosition.CenterScreen;

            this.Text = "CategoryWindow";

            this.TopMost = true;

            this.ResumeLayout(false);

     

        }

     

        #endregion      

     

        public System.Windows.Forms.ComboBox comboBox1;

        private System.Windows.Forms.Button button1;

        private System.Windows.Forms.Button button2;

    }

    u  需要说明的一点是上面代码中把两个Button设置为窗体返回结果的枚举值,如下:

    this.button1.DialogResult = System.Windows.Forms.DialogResult.OK;

    this.button2.DialogResult = System.Windows.Forms.DialogResult.Cancel;

    以上语句表示:单击“确定”按钮则返回选中的数据给父窗体;单击“取消”按钮则不返回数据。

    2CategoryWindow.cs 文件中窗体部分的类代码

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. public partial class CategoryWindow : Form
    6. {
    7.     public CategoryWindow()
    8.     {
    9.         InitializeComponent();
    10.         SetSelectData();
    11.     }
    12.     public void SetSelectData()
    13.     {
    14.         try
    15.         {
    16.             this.comboBox1.Items.Add("水果");
    17.             this.comboBox1.Items.Add("蔬菜");
    18.             this.comboBox1.Items.Add("肉类");
    19.             this.comboBox1.Items.Add("蛋类");
    20.             this.comboBox1.Items.Add("面食");
    21.         }
    22.         catch (Exception eee)
    23.         {
    24.             throw eee;
    25.         }
    26.         finally
    27.         {
    28.         }
    29.     }  
    30. }

    该页面没有复杂的交互逻辑,仅在类构造方法中调用SetSelectData方法为窗体中的ComboBox控件绑定食品数据列表。这里限于篇幅仅做了一个尽量简单的窗体,在实际开发中还可以定制任意复杂的窗体,在本节最后还提供了一个比较复杂的可以实现计算器功能的模态窗体。

    数据选择窗体已经建立好之后,再创建控件的属性编辑器文件,该编辑器文件中的类主要用于调用上面创建的数据选择窗体,包括打开窗体,选择完数据后,接收值并赋给属性窗口的对应属性。在讲解源代码之前,要先打开几个命名空间:

    using System.Drawing;

    using System.Drawing.Design;

    using System.Windows.Forms;

    using System.Windows.Forms.Design;

    u  这些命名空间主要是提供控件对WinForm的设计模式支持。下面还是先看一下此编辑器类的代码:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. public class CategoryModalEditor : System.Drawing.Design.UITypeEditor
    6. {
    7.     public CategoryModalEditor()
    8.     {
    9.     }
    10.     public override System.Drawing.Design.UITypeEditorEditStyle GetEditStyle (System.ComponentModel.ITypeDescriptorContext context)
    11.     {            
    12.         return UITypeEditorEditStyle.Modal;
    13.     }
    14.     public override object EditValue(System.ComponentModel.ItypeDescriptor Context context, System.IServiceProvider provider, object value)
    15.     {
    16.         IWindowsFormsEditorService service = (IWindowsFormsEditorService) provider.GetService(typeof(IWindowsFormsEditorService));
    17.         if (service == null)
    18.         {
    19.             return null;
    20.         }
    21.         
    22.         CategoryWindow form = new CategoryWindow();            
    23.         if (service.ShowDialog(form) == DialogResult.OK)
    24.         {
    25.             return form.comboBox1.SelectedItem; 
    26.         }
    27.         
    28.         return value;
    29.     }
    30. }

    这里使用的编辑器基类与前面我们定义的集合编辑器不一样,前面集合编辑器是使用System.ComponentModel.Design下的集合基类,这里使用的是System.Drawing.Design下的UItypeEdit基类。在实际开发中可以任意选择系统提供的编辑器基类,在4.5.1节已经列出了很多基类,也可以直接继承这些类定制自己的编辑器。

    方法GetEditStyleSystem.ComponentModel.ITypeDescriptorContext类型参数,表示要转换的对象的上下文;方法GetEditStyleUITypeEditorEditStyle.ModalUITypeEditorEditStyle枚举表示以什么样的形式打开编辑窗体,它有三个枚举值:ModalDropDownNone。其中Modal表示以模态形式弹出编辑属性界面;DropDown表示以下拉形式显示属性编辑界面;None表示不提供任何形式的UI界面。这里我们选择的是Modal枚举值,表示以模态形式弹出上面我们建立好的食品类别选择窗体。

    EditValue方法是主要的属性编辑方法,当单击属性窗口中的属性按钮时会执行此方法。它有三个参数:第一个参数表示当前上下文对象,可以从此对象获得当前父窗口和属性的设计时元数据信息等;第二个参数是服务提供者对象,可以根据此对象获取当前我们需要的服务;第三个参数为当前属性的默认值,即编辑当前属性之前的值。比如:

    IWindowsFormsEditorService service = (IWindowsFormsEditorService)provider. GetService(typeof(IWindowsFormsEditorService));

    u  以上语句表示获取IwindowsFormsEditorService类型的服务对象(专门为WinForm窗体编辑器提供一些功能),获取对象后就可以使用服务的方法:

    if (service.ShowDialog(form) == DialogResult.OK)

    {

        return form.comboBox1.SelectedItem;

    }

    ShowDialog就是IWindowsformsEditorService类型对象的一个方法,表示打开form对象窗体。另外,ShowDialog还有一个DialogResult枚举的返回值,这里表示如果返回值为OK枚举项,才真正把窗体中当前ComboBoxSelectedItem项返回。看到这里,我们可能会想起前面把数据选择窗体中的“确定”和“取消”两个按钮的DialogResult属性值分别设置为DialogResult.OKDialogResult.Cancel的用途了。

    本方法中第三个参数表示当前属性窗口中对应属性的当前值。有时您可以根据此值写一些相关的交互逻辑,比如根据此值设置弹出窗口的默认选中项(该功能比较简单,您可以扩展该控件功能,自己去实现它)。

    整个EditValue方法返回一个object类型的值,系统会根据此返回值对属性窗口进行填充。

    整个数据选择就是这样的一个过程,最后我们在主控件代码中对上面定义的数据选择窗体和编辑器进行应用。主控件源代码如下:

    /// <summary>

    /// 获得本书更多内容,请看:

    /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

    /// </summary>

     

    [DefaultProperty("SelectFood")]

    [ToolboxData("<{0}:CustomeModalEditorControl runat=server></{0}: CustomeModalEditorControl>")]

    public class CustomeModalEditorControl : WebControl

    {

        [Bindable(true)]

        [Category("类别")]

        [DefaultValue("")]

        [Localizable(true)]

        [Editor(typeof(CategoryModalEditor), typeof(System.Drawing.Design.UIType Editor))]

        [Description("选择食品类别")]

        public string SelectFood

        {

            get

            {

                String s = (String)ViewState["SelectFood"];

                return ((s == null) ? String.Empty : s);

            }

     

            set

            {

                ViewState["SelectFood"] = value;

            }

        }

        //… …

    }

     

    u  主控件中有个选择食品类别的属性SelectFood,该属性上面有一句:

    [Editor(typeof(CategoryModalEditor),typeof(System.Drawing.Design.UITypeEditor))]

    该句代码指定属性的编辑器为CategoryModalEditor。最后,编译控件,在属性窗口中即可看到我们定义的属性,如图4-22所示。

     

    4-22  模态属性编辑器

     

    4.5.2.3  定制下拉控件属性编辑器

    这一节我们再学习定制另一种形式的属性编辑器:下拉编辑器,单击按钮会下拉一个控件,当使用者从控件选择数据后该数据值被返回到属性窗口中。并且此选择数据的模态窗口内容也是可以自定义的。还是先看一下效果图,如图4-23所示。

    上图也是以一个表示选择食品(水果/肉类/蔬菜等)属性为例而定制的编辑器示例,单击属性旁边的下拉按钮会下拉一个数据选择的界面,并且此界面也是可以任意定制的。

    下面就来说一下此编辑器是怎么实现的,在控件中新增一个用户控件(这次不是Windows窗体),如图4-24所示。

      

    4-23  下拉控件属性编辑器                                    

     

    4-24  添加新项对话框

     

    然后放置一个ComboBox(提供绑定食品类别数据的选择列表)和两个Button控件(“确定”和“取消”)到窗体中,再在“确定”按钮的事件中增加数据返回功能。

    增加用户控件文件后,系统会把窗体类分成两个部分类文件:CategoryDropDown.csCategoryDropDown.Designer.csCategoryDropDown.cs主要供开发人员完成交互逻辑;CategoryDropDown.Designer.cs主要存储窗体和内部控件内容信息,下面分别来看一下它们的源代码。

    1CategoryDropDown.Designer.cs文件中用户控件部分类的代码

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. partial class CategoryDropDown
    6. {
    7.     /// <summary> 
    8.     /// 必需的设计器变量。
    9.     /// </summary>
    10.     private System.ComponentModel.IContainer components = null;
    11.     /// <summary> 
    12.     /// 清理所有正在使用的资源。
    13.     /// </summary>
    14.     /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
    15.     protected override void Dispose(bool disposing)
    16.     {
    17.         if (disposing && (components != null))
    18.         {
    19.             components.Dispose();
    20.         }
    21.         base.Dispose(disposing);
    22.     }
    23.     #region 组件设计器生成的代码
    24.     /// <summary> 
    25.     /// 设计器支持所需的方法 - 不要
    26.     /// 使用代码编辑器修改此方法的内容。
    27.     /// </summary>
    28.     private void InitializeComponent()
    29.     {
    30.         this.btnCancel = new System.Windows.Forms.Button();
    31.         this.btnOK = new System.Windows.Forms.Button();
    32.         this.comboBox1 = new System.Windows.Forms.ComboBox();
    33.         this.SuspendLayout();
    34.         // 
    35.         // btnCancel
    36.         // 
    37.         this.btnCancel.DialogResult=System.Windows.Forms.DialogResult.Cancel;
    38.         this.btnCancel.Location = new System.Drawing.Point(161, 56);
    39.         this.btnCancel.Name = "btnCancel";
    40.         this.btnCancel.Size = new System.Drawing.Size(75, 23);
    41.         this.btnCancel.TabIndex = 5;
    42.         this.btnCancel.Text = "取消";
    43.         this.btnCancel.UseVisualStyleBackColor = true;
    44.         this.btnCancel.Click+=new System.EventHandler(this.btnCancel_Click);
    45.         // 
    46.         // btnOK
    47.         // 
    48.         this.btnOK.DialogResult = System.Windows.Forms.DialogResult.OK;
    49.         this.btnOK.Location = new System.Drawing.Point(80, 56);
    50.         this.btnOK.Name = "btnOK";
    51.         this.btnOK.Size = new System.Drawing.Size(75, 23);
    52.         this.btnOK.TabIndex = 4;
    53.         this.btnOK.Text = "确定";
    54.         this.btnOK.UseVisualStyleBackColor = true;
    55.         this.btnOK.Click += new System.EventHandler(this.btnOK_Click);
    56.         // 
    57.         // comboBox1
    58.         // 
    59.         this.comboBox1.FormattingEnabled = true;
    60.         this.comboBox1.Location = new System.Drawing.Point(19, 15);
    61.         this.comboBox1.Name = "comboBox1";
    62.         this.comboBox1.Size = new System.Drawing.Size(217, 20);
    63.         this.comboBox1.TabIndex = 3;
    64.         // 
    65.         // CategoryDropDown
    66.         // 
    67.         this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
    68.         this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
    69.         this.Controls.Add(this.btnCancel);
    70.         this.Controls.Add(this.btnOK);
    71.         this.Controls.Add(this.comboBox1);
    72.         this.Name = "CategoryDropDown";
    73.         this.Size = new System.Drawing.Size(254, 95);
    74.         this.ResumeLayout(false);
    75.     }
    76.     #endregion
    77.     private System.Windows.Forms.Button btnCancel;
    78.     private System.Windows.Forms.Button btnOK;
    79.     public System.Windows.Forms.ComboBox comboBox1;
    80. }

    这里没有像模态编辑器示例一样设置控件的DialogResult属性,而是换了一种方式,分别为“确定”和“取消”两按钮定义事件,在事件中进行数据返回逻辑处理,关于事件将在接下来要讲解的另一个部分类中介绍。

    2CategoryDropDown.cs文件中用户控件部分类的代码

    /// <summary>

    /// 获得本书更多内容,请看:

    /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

    /// </summary>

     

    public partial class CategoryDropDown : UserControl

    {

        public string strReturnValue = "";

     

        private IWindowsFormsEditorService service=null;

        public CategoryDropDown(IWindowsFormsEditorService service)

        {

            InitializeComponent();

            SetSelectData();

            this.service = service;

        }

     

        public void SetSelectData()

        {

            try

            {

                this.comboBox1.Items.Add("水果");

                this.comboBox1.Items.Add("蔬菜");

                this.comboBox1.Items.Add("肉类");

                this.comboBox1.Items.Add("蛋类");

                this.comboBox1.Items.Add("面食");

            }

            catch (Exception eee)

            {

                throw eee;

            }

            finally

            {

     

            }

        }

     

        private void btnOK_Click(object sender, EventArgs e)

        {

            strReturnValue = this.comboBox1.SelectedItem.ToString();

            service.CloseDropDown();

        }

     

        private void btnCancel_Click(object sender, EventArgs e)

        {

            strReturnValue = "";

            service.CloseDropDown();

        }

    }

     

    u  4.5.2.2节讲的模态编辑器一样构造函数中的SetSelectData方法是提供控件ComboBox下拉界面中的食品类别数据列表另外构造函数中多了一个IWindowsFormsEditorService类型的参数(在4.5.2.2节对该类型进行了说明)把窗体编辑器服务对象传递过来因为这里我们要在“确定”和“取消”事件中调用关闭当前界面的方法:

    service.CloseDropDown();

    编辑器的EditValue方法负责打开下拉界面在下拉界面中的两个按钮中要分别调用关闭自己的代码

    单击“确定”按钮时会把当前选择的值赋给类内部变量strReturnValue在下面讲的编辑器中会获取该变更的值并赋值到属性

    至此下拉界面已经定义完成接下来定义编辑器类代码如下:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. public class CategoryDropDownEditor : System.Drawing.Design.UITypeEditor
    6. {
    7.     public CategoryDropDownEditor()
    8.     {
    9.     }
    10.     public override System.Drawing.Design.UITypeEditorEditStyle GetEditStyle (System.ComponentModel.ITypeDescriptorContext context)
    11.     {
    12.         //指定编辑样式为下拉形状, 且基于Control类型
    13.         return UITypeEditorEditStyle.DropDown;
    14.     }
    15.     public override object EditValue(System.ComponentModel.ItypeDescriptor Context context, System.IServiceProvider provider, object value)
    16.     {
    17.         //取得编辑器服务对象
    18.         IWindowsFormsEditorService service = (IWindowsFormsEditorService) provider.GetService(typeof(IWindowsFormsEditorService));
    19.         if (service == null)
    20.         {
    21.             return null;
    22.         }
    23.         //定义一个用户控件对象
    24.         CategoryDropDown form = new CategoryDropDown(service);
    25.         service.DropDownControl(form);        
    26.     
    27.         string strReturn = form.strReturnValue;
    28.         if (strReturn + String.Empty != String.Empty)
    29.         {
    30.             return strReturn;
    31.         }
    32.         return (string)value;      
    33.     }
    34. }

    下拉编辑器与前面小节讲的弹出式模态编辑器都通过继承System.Drawing.Design. UITypeEditor类实现定制编辑器。

    GetEditStyle方法中的代码:

    return UITypeEditorEditStyle.DropDown;

    指定编辑器类型为下拉模式。

    EditValue中主要创建数据选择界面并以下拉模式打开。下面说一下它内部代码实现逻辑。

    IWindowsFormsEditorService service = (IWindowsFormsEditorService)provider. GetService(typeof(IWindowsFormsEditorService));

    u  以上代码主要获取窗体编辑对象的服务对象,在本例中用到了它的下拉和关闭数据选择界面的方法。

    CategoryDropDown form = new CategoryDropDown(service);

    service.DropDownControl(form);

    u  以上代码主要是创建一个数据选择界面(用户控件类型),并使用service对象的DropDownControl 方法把参数指定的用户控件以下拉形式显示出来,展现形式为模态形式(暂停执行,直到单击界面中的按钮关闭下拉窗体程序才继续执行)。而EditValue方法是在单击属性窗口中属性旁边的下拉按钮时触发。

    string strReturn = form.strReturnValue;

    return (string)value;

    以上代码是获取下拉窗体中当前选择的数据(单击“确定”按钮时把值暂存到form.str ReturnValue变量中)并返回,系统会自动把返回值赋给当前属性。

    最后定义主控件代码类,代码如下;

    /// <summary>

    /// 获得本书更多内容,请看:

    /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

    /// </summary>

     

    [DefaultProperty("SelectFood")]

    [ToolboxData("<{0}:CustomeDropDownEditorControl runat=server></{0}: CustomeDropDownEditorControl>")]

    public class CustomeDropDownEditorControl : WebControl

    {

        [Bindable(true)]

        [Category("类别")]

        [DefaultValue("")]

        [Localizable(true)]

        [Editor(typeof(CategoryDropDownEditor), typeof(System.Drawing.Design. UITypeEditor))]

        [Description("选择食品类别")]

        public string SelectFood

        {

            get

            {

                String s = (String)ViewState["SelectFood"];

                return ((s == null) ? String.Empty : s);

            }

     

            set

            {

                ViewState["SelectFood"] = value;

            }

        }

        //… …

    }

    u  主控件中仅包括一个SelectFood属性。这里仅需要说明的是它的设计时元数据代码段,如下:

    [Editor(typeof(CategoryDropDownEditor), typeof(System.Drawing.Design.UITypeEditor))]

    以上代码指定编辑器为我们上面定义的CategoryDropDownEditor编辑器。

    最后,编译控件,在属性窗口中即可看到我们定义的属性,如图4-25所示。

     

    4-25  属性窗口

     

    4.5.2.4  定制计算器属性编辑器

    本节没有讲解新的控件开发知识,而是利用前面所讲解的知识编写了一个有用的自定义属性编辑器—计算器属性编辑器,如图4-26所示。

     

    4-26  计算器属性编辑器

    这个属性编辑器比较简单,下面就简要地说一下它的实现过程。首先看一下计算器面板Form的实现代码:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. public class FormKeyBoard : System.Windows.Forms.Form
    6. {
    7.     private System.Windows.Forms.Label label2;
    8.     private System.Windows.Forms.TextBox expressBox;
    9.     private System.ComponentModel.Container components = null;
    10.     public string strReturnValue = "";
    11.     
    12.     //定义存放运算符(包括:'+','-',...,'sin',...,'arcsin',...,'(',...等)及其特
    13.     //性的数据结构
    14.     public struct opTable   //定义存放运算符及其优先级和单双目的结构
    15.     {
    16.         public string op;   //用于存放运算符,op为operator的简写
    17.         public int code;    //用于存放运算符的优先级
    18.         public char grade;  //用于判断存放的运算符是单目还是双目
    19.     }
    20.     
    21.     //用于存放制定好的运算符及其特性(优先级和单双目)的运算符表,其初始化在方
    22.     //法Initialize()中
    23.     public opTable[] opchTbl=new opTable[19];  
    24.     public opTable[] operateStack=new opTable[30];//用于存放从键盘扫描的运算符的栈
    25.     
    26.     //定义优先级列表1,2,3,4,5,6,7,8,9,
    27.     //数组中元素依次为: //"sin","cos","tan","cot","arcsin","arccos","arctan", "sec","csc","ln","^","*","/","+","-","(",")",""   的栈外(因为有的运算符是从右向左计算,有的是从左往右计算,用内外优先级可以限制其执行顺序)优先级
    28. public int[]osp=new int[19]{6,6,6,6,6,6,6,6,6,6,6,5,3,3,2,2,7,0,1};  
    29.     //数组中元素依次为: //"sin","cos","tan","cot","arcsin","arccos","arctan", "sec","csc","ln","^","*","/","+","-","(" ,"end" 的栈内(因为有的运算符是从右向左计算,有的是从左往右计算,用内外优先级可以限制其执行顺序)优先级
    30. public int[]isp=new int[18]{5,5,5,5,5,5,5,5,5,5,5,4,3,3,2,2,1,1};      
    31.         
    32.     //定义存放从键盘扫描的数据的栈
    33.     public double[]dataStack=new double[30]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0};
    34.     //定义表态指针
    35.     public int opTop=-1;  //指向存放(从键盘扫描的)运算符栈的指针
    36.     public int dataTop=-1;//指向存放(从键盘扫描的)数据栈指针
    37.     
    38.     //定义存放从键盘输入的起始字符串
    39.     public string startString;
    40.     public int startTop=0;
    41.     public double variableX=0;
    42.     public double variableY=0;
    43.     const double PI=3.1415926;
    44.     int number=1;
    45.     public int startTopMoveCount=0;  
    46.     private System.Windows.Forms.Button button1;
    47.     private System.Windows.Forms.Button button2;
    48.     private System.Windows.Forms.Button button3;
    49.     private System.Windows.Forms.Button button4;
    50.     private System.Windows.Forms.Button button5;
    51.     private System.Windows.Forms.Button button6;
    52.     private System.Windows.Forms.Button button7;
    53.     private System.Windows.Forms.Button button8;
    54.     private System.Windows.Forms.Button button9;
    55.     private System.Windows.Forms.Button button10;
    56.     private System.Windows.Forms.Button button11;
    57.     private System.Windows.Forms.Button button12;
    58.     private System.Windows.Forms.Button button13;
    59.     private System.Windows.Forms.Button button14;
    60.     private System.Windows.Forms.Button button15;
    61.     private System.Windows.Forms.Button button16;
    62.     private System.Windows.Forms.Button button17;
    63.     private System.Windows.Forms.Button button18;
    64.     private System.Windows.Forms.Button button19;
    65.     private System.Windows.Forms.Button button20;
    66.     private System.Windows.Forms.Button button21;
    67.     private System.Windows.Forms.Button button22;
    68.     private System.Windows.Forms.Button button23;
    69.     private System.Windows.Forms.Button button24;
    70.     private System.Windows.Forms.Button button25;
    71.     private System.Windows.Forms.Button button26;
    72.     private System.Windows.Forms.Button button27;
    73.     private System.Windows.Forms.Button button28;
    74.     private System.Windows.Forms.Button button29;
    75.     private System.Windows.Forms.Button button30;
    76.     private System.Windows.Forms.Button button31;
    77.     private System.Windows.Forms.Button button32;
    78.     private System.Windows.Forms.Label label1;
    79.     private System.Windows.Forms.TextBox endbox;
    80.     private System.Windows.Forms.Button button33;
    81.     private Button btnClear;
    82.     private Button button34;
    83.     private System.Windows.Forms.Button btnReturn;   
    84.     
    85.     
    86.     
    87.     #region Windows Form Designer generated code
    88.     public FormKeyBoard()
    89.     {
    90.         InitializeComponent();      
    91.     }
    92.     protected override void Dispose( bool disposing )
    93.     {
    94.         if( disposing )
    95.         {
    96.             if (components != null
    97.             {
    98.                 components.Dispose();
    99.             }
    100.         }
    101.         base.Dispose( disposing );
    102.     }
    103.     #endregion 
    104.     #region Windows Form Designer generated code
    105.     /// <summary>
    106.     /// 设计器支持所需的方法- 不要使用代码编辑器修改
    107.     /// 此方法的内容。
    108.     /// </summary>
    109.     private void InitializeComponent()
    110.     {
    111.     //本方法主要完成控件的创建,初始化,注册事件等逻辑。完整代码在本书随书光盘中
    112.     }
    113.     #endregion
    114.     private void Form1_Load(object sender, System.EventArgs e)
    115.     {
    116.     }
    117.     //制定运算符及其特性(优先级和单双目)的运算符表
    118.     public void InitializeOpchTblStack()  
    119.     {
    120.         opchTbl[0].op="sin"; opchTbl[0].code=1; opchTbl[0].grade='s';
    121.         opchTbl[1].op="cos"; opchTbl[1].code=2; opchTbl[1].grade='s'
    122.         opchTbl[2].op="tan"; opchTbl[2].code=3; opchTbl[2].grade='s'
    123.         opchTbl[3].op="cot"; opchTbl[3].code=4; opchTbl[3].grade='s';
    124.         opchTbl[4].op="arcsin"; opchTbl[4].code=5; opchTbl[4].grade='s';
    125.         opchTbl[5].op="arccos"; opchTbl[5].code=6; opchTbl[5].grade='s';
    126.         opchTbl[6].op="arctan"; opchTbl[6].code=7; opchTbl[6].grade='s';
    127.         opchTbl[7].op="arccot"; opchTbl[7].code=8; opchTbl[7].grade='s';
    128.         opchTbl[8].op="sec"; opchTbl[8].code=9; opchTbl[8].grade='s';
    129.         opchTbl[9].op="csc"; opchTbl[9].code=10; opchTbl[9].grade='s';
    130.         opchTbl[10].op="ln"; opchTbl[10].code=11; opchTbl[10].grade='s';
    131.         opchTbl[11].op="^"; opchTbl[11].code=12; opchTbl[11].grade='d';
    132.         opchTbl[12].op="*"; opchTbl[12].code=13; opchTbl[12].grade='d';
    133.         opchTbl[13].op="/"; opchTbl[13].code=14; opchTbl[13].grade='d';
    134.         opchTbl[14].op="+"; opchTbl[14].code=15; opchTbl[14].grade='d';
    135.         opchTbl[15].op="-"; opchTbl[15].code=16; opchTbl[15].grade='d';
    136.         opchTbl[16].op="("; opchTbl[16].code=17; opchTbl[16].grade='d';
    137.         opchTbl[17].op=")"; opchTbl[17].code=18; opchTbl[17].grade='d';
    138.         opchTbl[18].op=" "; opchTbl[18].code=19; opchTbl[18].grade='d';
    139.         startString=expressBox.Text;
    140.     }
    141.     public void CreterionFaction()
    142.     {
    143.         //以下代码消去待扫描字符串中的所有空格字符
    144.         for(int i=0;i<startString.Length;i++)
    145.             if(startString[i].Equals(' '))
    146.             {
    147.                 startString=startString.Remove(i,1);
    148.                 i--;
    149.             }
    150.         //以下代码使待扫描字符串的单目('+'和'-')变为双目
    151.         if(startString.Length!=0)
    152.             if(startString[0]=='+'||startString[0]=='-')
    153.             {
    154.                 startString=startString.Insert(0,"0");
    155.             }
    156.         for(int i=0;i<startString.Length-1;i++)
    157.         {
    158.             if((startString[i]=='(')&&(startString[i+1]=='-'))
    159.                 startString=startString.Insert(i+1,"0");
    160.         }
    161.         startString=startString.Insert(startString.Length,")");
    162.         //将待扫描字符串转化为小写字母
    163.         startString=startString.ToLower();
    164.     }
    165.     public bool CheckParentthese() //检查括号是否匹配
    166.     {
    167.         int number=0;
    168.         for(int i=0;i<startString.Length-1;i++)
    169.         {
    170.             if(i=='(') number++;
    171.             if(i==')') number--;
    172.             if(number<0) return false;
    173.         }
    174.         if(number!=0)  
    175.         {
    176.             return false;
    177.         }
    178.         return true;
    179.     }
    180.     //给运算表达式分块(三角函数、算术运算符等),再根据其返回值来检验其属于哪类错误
    181.     public int CheckFollowCorrect()    
    182.     {
    183.         string str,oldString="",newString="";
    184.         int dataCount=0,characterCount=0;
    185.         if(startString.Equals(")"))       
    186.             return 0;         //输入字符串为空返回值
    187. if((startString[0]=='*')||(startString[0]=='/')||(startString[0]=='^')||
    188.     (startString[0]==')'))
    189.             return 11;        //首字符输入错误返回值
    190. for(int i=0;i<startString.Length;i++)
    191. {
    192.     if((oldString.Equals("三角函数"))&&(newString.Equals("右括号")))
    193.         return 2;     //三角函数直接接右括号错误返回值
    194.     if((oldString.Equals("左括号"))&&(newString.Equals("算术运算符")))
    195.         return 3;     //左括号直接接算术运算符错误返回值
    196.     if((oldString.Equals("数字序列"))&&(newString.Equals("三角函数")))
    197.         return 4;     //数字序列后直接接三角函数错误返回值
    198.     if((oldString.Equals("数字序列"))&&(newString.Equals("左括号")))
    199.         return 5;     //数字序列后直接接左括号错误返回值
    200.     if((oldString.Equals("算术运算符"))&&(newString.Equals("右括号")))
    201.         return 6;     //算术运算符后直接接右括号错误返回值
    202.     if((oldString.Equals("右括号"))&&(newString.Equals("左括号")))
    203.         return 7;     //右括号直接接左括号错误返回值
    204.     if((oldString.Equals("右括号"))&&(newString.Equals("三角函数")))
    205.         return 8;     //右括号直接接三角函数错误返回值
    206.     if((oldString.Equals("数字序列"))&&(newString.Equals("数字序列")))
    207.         return 9;     //数字序列后直接接'pi'/'e'或'pi'/'e'直接接数字序列错误返回值
    208.     if((oldString.Equals("算术运算符"))&&(newString.Equals("算术运算符")))
    209.         return 10;    //算术运算符后直接接算术运算符错误返回值
    210.     oldString=newString;
    211.     if(i<startString.Length-5&&startString.Length>=6)
    212.     {
    213.         str=startString.Substring(i,6); 
    214. if((str.CompareTo("arcsin")==0)||(str.CompareTo("arccos")==0)||(str.Compar
    215.     eTo("arctan")==0)||(str.CompareTo("arccot")==0))
    216.     {
    217.         newString="三角函数";
    218.         i+=5; characterCount++;
    219.         continue;
    220.     }
    221. }
    222. if(i<startString.Length-2&&startString.Length>=3)
    223. {
    224.     str=startString.Substring(i,3);
    225. if((str.CompareTo("sin")==0)||(str.CompareTo("cos")==0)||(str.CompareTo("tan")==0)||(str.CompareTo("cot")==0)||(str.CompareTo("sec")==0)||(str.CompareTo("csc")==0))
    226.     {
    227.         newString="三角函数";
    228.         i+=2; characterCount++;
    229.         continue;
    230.     }
    231. }
    232. if(i<(startString.Length-1)&&(startString.Length)>=2)
    233. {
    234.     str=startString.Substring(i,2);
    235.     if(str.CompareTo("ln")==0)
    236.     {
    237.         newString="三角函数";
    238.         i+=1; characterCount++;
    239.         continue;
    240.     }
    241.     if(str.CompareTo("pi")==0)
    242.     {
    243.         newString="数字序列";
    244.         i+=1;dataCount++;
    245.         continue;
    246.     }
    247.     }
    248.     str=startString.Substring(i,1);
    249. if(str.Equals("^")||str.Equals("*")||str.Equals("/")||str.Equals("+")||str.Equals("-"))
    250.     {
    251.         newString="算术运算符";
    252.         characterCount++;
    253.         continue;
    254.     }
    255.     if(str.Equals("e"))
    256.     {
    257.         newString="数字序列";
    258.         dataCount++;
    259.         continue;
    260.     }
    261.     if(str.Equals("("))
    262.     {
    263.         newString="左括号";
    264.         characterCount++;
    265.         continue;
    266.     }
    267.     if(str.Equals(")"))
    268.     {
    269.         newString="右括号";
    270.         characterCount++;
    271.         continue;
    272.     }
    273.     if(Char.IsDigit(startString[i]))
    274.     {
    275.         while(Char.IsDigit(startString[i]))
    276.     {
    277.         i++;                                          
    278.     }
    279. if(startString[i]=='.'&&(!Char.IsDigit(startString[i+1]))&&(i+1)!=startString.Length)
    280.         return 13;
    281.     if(startString[i]=='.')
    282.     {
    283.         i++;
    284.     }                    
    285.     while(Char.IsDigit(startString[i]))
    286.     {
    287.         i++;  
    288.     }
    289.     newString="数字序列";
    290.     i--; dataCount++;
    291.     continue;
    292.     }
    293.     return 1;         //非法字符
    294. if((dataCount==0&&characterCount!=0)||(startString[0]=='0'&&dataCount==1&
    295.    characterCount>1&&startString.Length!=2))
    296.             return 12;
    297.         return 100;
    298.     }
    299.     public int IsCharacterOrData(ref double num)
    300.     {
    301.         string str="";
    302.         startTop+=startTopMoveCount; startTopMoveCount=0;
    303.         int i=startTop;
    304.         if(i<startString.Length-5&&startString.Length>=6)
    305.         {
    306.             str=startString.Substring(i,6); 
    307.             for(int j=4;j<=7;j++)
    308.                 if(str.Equals(opchTbl[j].op))
    309.                 {
    310.                     startTopMoveCount=6;
    311.                     return opchTbl[j].code;
    312.                 }
    313.         }
    314.         if(i<startString.Length-2&&startString.Length>=3)
    315.         {                 
    316.             str=startString.Substring(i,3);                 
    317.             for(int j=0;j<10;j++)
    318.                 if(str.CompareTo(opchTbl[j].op)==0)
    319.                 {
    320.                     startTopMoveCount=3;
    321.                     return opchTbl[j].code;
    322.                 }
    323.         }
    324.         if(i<(startString.Length-1)&&(startString.Length)>=2)
    325.         {
    326.             str=startString.Substring(i,2);
    327.             if(str.CompareTo("ln")==0)
    328.             {
    329.                 startTopMoveCount=2;
    330.                 return 11;
    331.             }
    332.             if(str.CompareTo("pi")==0)
    333.             {
    334.                 startTopMoveCount=2;
    335.                 num=Math.PI;
    336.                 return 100;
    337.             }
    338.         }
    339.         //以下开始确认一个字符是属于什么值类型
    340.         if(i<startString.Length)
    341.         {
    342.             str=startString.Substring(i,1);
    343.             for(int j=11;j<19;j++)
    344.             {
    345.                 if(str.Equals(opchTbl[j].op))
    346.                 {startTopMoveCount=1;return opchTbl[j].code;}                
    347.             }
    348.             if(str.CompareTo("e")==0)
    349.             {
    350.                 startTopMoveCount=1; num=Math.E;
    351.                 return 100;
    352.             }  
    353.             if(Char.IsDigit(startString[i]))
    354.             {
    355.                 double temp=0,M=10; int j=i;
    356.                 while(Char.IsDigit(startString[j]))
    357.                 {
    358.                     temp=M*temp+Char.GetNumericValue(startString[j]);
    359.                     startTop++;
    360.                     j++;                      
    361.                 }
    362.                 if(startString[j]=='.')
    363.                 {
    364.                     j++;startTop++;                       
    365.                 }
    366.                 while(Char.IsDigit(startString[j]))
    367.                 {
    368.                     temp+=1.0/M*Char.GetNumericValue(startString[j]);
    369.                     M/=10;j++;    
    370.                     startTop++;
    371.                 }
    372.                 startTopMoveCount=0;
    373.                 num=temp;
    374.                 return 100;
    375.             }
    376.         }
    377.         return -1;
    378.     }
    379.     public double DoubleCount(string opString,double data1,double data2)
    380.     {   //双目运算
    381.         if(opString.CompareTo("+")==0) return (data1+data2);
    382.         if(opString.CompareTo("-")==0) return (data1-data2);
    383.         if(opString.CompareTo("*")==0) return (data1*data2);
    384.         if(opString.CompareTo("/")==0) return (data1/data2);
    385.         if(opString.CompareTo("^")==0) 
    386.         {
    387.             double end=data1;
    388.             for(int i=0;i<data2-1;i++)
    389.                 end*=data1;
    390.             return (end);
    391.         }            
    392.         return Double.MaxValue;    //定义域不对,返回
    393.     }
    394.     public double DoubleCount(string opString,double data1)
    395.     {   //单目运算
    396.         if(opString.CompareTo("sin")==0) return Math.Sin(data1);
    397.         if(opString.CompareTo("cos")==0) return Math.Cos(data1);
    398.         if(opString.CompareTo("tan")==0) return Math.Tan(data1);
    399.         if(opString.CompareTo("cot")==0) return (1/(Math.Tan(data1)));
    400.         if(opString.CompareTo("arcsin")==0)
    401.         if(-1<=data1&&data1<=1)    return Math.Asin(data1);
    402.     
    403.         if(opString.CompareTo("arccos")==0) 
    404.             if(-1<=data1&&data1<=1)      return Math.Acos(data1);
    405.         
    406.         if(opString.CompareTo("arctan")==0)
    407.             if(-Math.PI/2<=data1&&data1<=Math.PI/2)return Math.Atan(data1);
    408.         if(opString.CompareTo("arccot")==0)
    409.             if(-Math.PI/2<=data1&&data1<=Math.PI/2)return (-Math.Atan(data1));
    410.         if(opString.CompareTo("sec")==0) return (1/(Math.Cos(data1)));
    411.         if(opString.CompareTo("csc")==0) return (1/(Math.Sin(data1)));
    412.         if(data1>0) if(opString.CompareTo("ln")==0) return  Math.Log(data1);
    413.         return Double.MaxValue;   //定义域不对
    414.     }
    415.     public bool CountValueY(ref double tempY)  //此方法功能为求解
    416.     {
    417.         int type=-1;       //存放正在扫描的字符串是为数字类型还是单双目运算符
    418.         double num=0;      //如果是数据,则返回数据的值
    419.         //进栈底结束符"end"
    420.         opTop++;
    421.         operateStack[opTop].op="end"; operateStack[opTop].code=18; 
    422.         operateStack[opTop].grade=' ';
    423.         while(startTop<=startString.Length-1)
    424.         {
    425.         start:
    426.             type=IsCharacterOrData(ref num);  //调用判断返回值类型函数
    427.             if(type==-1){return false;}                
    428.             if(type==100) 
    429.             {                
    430.                 dataTop=dataTop+1;
    431.                 dataStack[dataTop]=num;                                                            
    432.             }    
    433.             else
    434.             {   
    435.                 if(osp[type-1]>isp[operateStack[opTop].code-1])   //操作符进栈
    436.                 {
    437.                     opTop++;
    438.                     operateStack[opTop].op=opchTbl[type-1].op; 
    439.                     operateStack[opTop].code=opchTbl[type-1].code; 
    440.                     operateStack[opTop].grade=opchTbl[type-1].grade; 
    441.                 }
    442.                 else
    443.     {
    444.         //弹出操作符跟数据计算,并存入数据
    445.         while(osp[type-1]<=isp[operateStack[opTop].code-1])  
    446.         {    
    447.             //当遇到"end"结束符表示已经获得结果
    448.             if(operateStack[opTop].op.CompareTo("end")==0) 
    449.             {
    450.                 if(dataTop==0)
    451.                 {
    452.                     tempY=dataStack[dataTop];  startTop=0; startTopMoveCount=0; 
    453.                     opTop=-1; dataTop=-1;
    454.                     return true;
    455.                 }
    456.                 else return false;//运算符和数据的个数不匹配造成的错误
    457.             }
    458.             if(operateStack[opTop].op.CompareTo("(")==0)  //如果要弹出操作数为
    459.                                                                    //'( ',则消去左括号
    460.             {
    461.                 opTop--; goto start;
    462.             }  
    463.             //弹出操作码和一个或两个数据计算,并将计算结果存入数据栈
    464.             double data1,data2; opTable operate;
    465.             if(dataTop>=0)    data2=dataStack[dataTop];
    466.             else return false;
    467.             operate.op=operateStack[opTop].op; operate.code=operateStack
    468.             [opTop].code; operate.grade=operateStack[opTop].grade;
    469.             opTop--;  //处理一次,指针必须仅且只能下移一个单位
    470.             if(operate.grade=='d')
    471.                 {
    472.                 if(dataTop-1>=0)    data1=dataStack[dataTop-1];
    473.                 else return false;
    474.                 double tempValue=DoubleCount(operate.op,data1,data2);
    475.                 if(tempValue!=Double.MaxValue)dataStack[--dataTop]=tempValue;
    476.                 else return false;
    477.             }
    478.             if(operate.grade=='s')
    479.             {
    480.                 double tempValue=DoubleCount(operate.op,data2);
    481.                 if(tempValue!=Double.MaxValue)
    482.                     dataStack[dataTop]=tempValue;
    483.                     else return false;
    484.                 }
    485.             }                                        
    486.             //如果当前栈外操作符比栈顶的操作符优先级别高,则栈外操作符进栈
    487.                 opTop++;
    488.                 operateStack[opTop].op=opchTbl[type-1].op; 
    489.                 operateStack[opTop].code=opchTbl[type-1].code; 
    490.                 operateStack[opTop].grade=opchTbl[type-1].grade;
    491.             }
    492.         }
    493.     }
    494.     return false
    495. }
    496. public void StartExcute()
    497. {
    498.     InitializeOpchTblStack();
    499.     CreterionFaction();
    500.     if(CheckParentthese()==false)
    501.     {
    502.         MessageBox.Show("括号不匹配,请重新输入!!!","错误",MessageBoxButtons.OK, 
    503.              MessageBoxIcon.Error);
    504.         return
    505.     }
    506.     switch(CheckFollowCorrect())
    507.     {
    508.         case 0: MessageBox.Show("表达式为空,请先输入表达式!!!","错误"
    509.                   MessageBoxButtons.OK,MessageBoxIcon.Warning); 
    510.         return;
    511.         case 1:  MessageBox.Show("表达式中有非法字符!!!","错误"
    512.                   MessageBoxButtons.OK,MessageBoxIcon.Error); 
    513.         return;
    514.         case 2:  MessageBox.Show("三角函数运算符与) 之间应输入数据或其他表达式!!!","
    515.                   错误",MessageBoxButtons.OK,MessageBoxIcon.Error);
    516.          return;
    517.         case 3:  MessageBox.Show("' (  ' 与算术运算符之间应输入数据或其他表达
    518.                   式!!!","错误",MessageBoxButtons.OK,MessageBoxIcon.Error);
    519.         return;
    520.         case 4:  MessageBox.Show("数字数列与三角函数之间应输入算术运算符或其他表达
    521.                   式!!!","错误",MessageBoxButtons.OK,MessageBoxIcon.Error);
    522.         return;
    523.         case 5:  MessageBox.Show("数字数列与 ' (  '  之间应输入算术运算符或其他表达
    524.                   式!!!","错误",MessageBoxButtons.OK,MessageBoxIcon.Error);
    525.     return;
    526.         case 6:  MessageBox.Show("算术运算符与右括号之间应输入数据或其他表达式!!!","
    527.                   错误",MessageBoxButtons.OK,MessageBoxIcon.Error);
    528.         return;
    529.         case 7:  MessageBox.Show("'  )  ' 与'  (  ' 之间应输入算术运算符或其他表达
    530.                   式!!!","错误",MessageBoxButtons.OK,MessageBoxIcon.Error);
    531.         return;
    532.         case 8:  MessageBox.Show("'   )   ' 与三角函数之间应输入算术运算符或其他表达
    533.              式!!!","错误",MessageBoxButtons.OK,MessageBoxIcon.Error);
    534.         return;
    535.         case 9:  MessageBox.Show("常量'  PI  '  或 '  E  '  或 '  X  '  与数字数据
    536.                   之间应输入算术运算符或其他表达式!!!","错误", 
    537.                   MessageBoxButtons.OK,MessageBoxIcon.Error);
    538.         return;
    539.         case 10: MessageBox.Show("算术运算符与算术运算符之间应输入数据或其他表达
    540.                   式!!!","错误",MessageBoxButtons.OK,MessageBoxIcon.Error); 
    541.         return;
    542.         case 11: MessageBox.Show("表达式头部不能为' + ',' - ',' * ',' / '' ^ ',' )'
    543.                  !!!","错误",MessageBoxButtons.OK,MessageBoxIcon.Error);
    544.         return;    
    545.         case 12: MessageBox.Show("仅有运算符号没有数字数据或数据缺少而无法计算,请输入数
    546.                  字数据!!!","错误",MessageBoxButtons.OK,MessageBoxIcon.Error);
    547.         return;
    548.         case 13: MessageBox.Show("小数点后面缺少小数部分,请输入小数部分!!!","错误",
    549.                   MessageBoxButtons.OK,MessageBoxIcon.Error);
    550.         return;
    551.     }
    552.     double tempY=0;
    553.     switch(CountValueY(ref tempY))
    554.     {                
    555.         case false:MessageBox.Show("输入的表达式不正确或反三角函数定义域在其定义域范围
    556.                     之外!!!","错误",MessageBoxButtons.OK,MessageBoxIcon.Error);
    557.         return;
    558.     }            
    559.     endbox.Text=tempY.ToString();//依次存档计算结果
    560.     number++;            
    561. }
    562. private void button30_Click(object sender, System.EventArgs e)
    563. {
    564.     StartExcute();
    565. }
    566. private void button10_Click(object sender, System.EventArgs e)
    567. {
    568.     expressBox.SelectedText=null;
    569.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button10.Text);            
    570.     expressBox.SelectionStart=expressBox.TextLength;
    571. }
    572. private void button11_Click(object sender, System.EventArgs e)
    573. {
    574.     expressBox.SelectedText=null;
    575.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart,".");            
    576.     expressBox.SelectionStart=expressBox.TextLength;
    577. }
    578. private void button27_Click(object sender, System.EventArgs e)
    579. {
    580.     expressBox.SelectedText=null;
    581.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart,"^");            
    582.     expressBox.SelectionStart=expressBox.TextLength;
    583. }
    584. private void button1_Click_1(object sender, System.EventArgs e)
    585. {        
    586.     expressBox.SelectedText=null;
    587.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button1.Text);
    588.     expressBox.SelectionStart=expressBox.TextLength;
    589. }
    590. private void button4_Click(object sender, System.EventArgs e)
    591. {
    592.     expressBox.SelectedText=null;
    593.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button4.Text);
    594.     expressBox.SelectionStart=expressBox.TextLength;
    595. }
    596. private void button3_Click(object sender, System.EventArgs e)
    597. {
    598.     expressBox.SelectedText=null;
    599.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button3.Text);
    600.     expressBox.SelectionStart=expressBox.TextLength;
    601. }
    602. private void button2_Click_1(object sender, System.EventArgs e)
    603. {
    604.     expressBox.SelectedText=null;
    605.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button2.Text);
    606.     expressBox.SelectionStart=expressBox.TextLength;
    607. }
    608. private void button14_Click(object sender, System.EventArgs e)
    609. {
    610.     expressBox.SelectedText=null;
    611.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button14.Text);
    612.     expressBox.SelectionStart=expressBox.TextLength;
    613. }
    614. private void button15_Click(object sender, System.EventArgs e)
    615. {
    616.     expressBox.SelectedText=null;
    617.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button15.Text);
    618.     expressBox.SelectionStart=expressBox.TextLength;
    619. }
    620. private void button5_Click(object sender, System.EventArgs e)
    621. {            
    622.     expressBox.SelectedText=null;
    623.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button5.Text);
    624.     expressBox.SelectionStart=expressBox.TextLength;
    625. }
    626. private void button6_Click(object sender, System.EventArgs e)
    627. {
    628.     expressBox.SelectedText=null;
    629.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button6.Text);
    630.     expressBox.SelectionStart=expressBox.TextLength;
    631. }
    632. private void button9_Click(object sender, System.EventArgs e)
    633. {
    634.     expressBox.SelectedText=null;
    635.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button9.Text);
    636.     expressBox.SelectionStart=expressBox.TextLength;
    637. }
    638. private void button8_Click(object sender, System.EventArgs e)
    639. {
    640.     expressBox.SelectedText=null;
    641.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button8.Text);
    642.     expressBox.SelectionStart=expressBox.TextLength;
    643. }
    644. private void button7_Click(object sender, System.EventArgs e)
    645. {
    646.     expressBox.SelectedText=null;
    647.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button7.Text);
    648.     expressBox.SelectionStart=expressBox.TextLength;
    649. }
    650. private void button12_Click(object sender, System.EventArgs e)
    651. {
    652.     expressBox.SelectedText=null;
    653.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button12.Text);
    654.     expressBox.SelectionStart=expressBox.TextLength;
    655. }
    656. private void button13_Click(object sender, System.EventArgs e)
    657. {
    658.     expressBox.SelectedText=null;
    659.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button13.Text);
    660.     expressBox.SelectionStart=expressBox.TextLength;
    661. }
    662. private void button29_Click(object sender, System.EventArgs e)
    663. {            
    664.     expressBox.SelectedText=null;
    665.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button29.Text);
    666.     expressBox.SelectionStart=expressBox.TextLength;
    667. }
    668. private void button28_Click(object sender, System.EventArgs e)
    669. {
    670.     expressBox.SelectedText=null;
    671.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button28.Text);
    672.     expressBox.SelectionStart=expressBox.TextLength;
    673. }
    674. private void button16_Click(object sender, System.EventArgs e)
    675. {
    676.     expressBox.SelectedText=null;
    677.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button16.Text);
    678.     expressBox.SelectionStart=expressBox.TextLength;
    679. }
    680. private void button20_Click(object sender, System.EventArgs e)
    681. {
    682.     expressBox.SelectedText=null;
    683.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button20.Text);            
    684.     expressBox.SelectionStart=expressBox.TextLength;
    685. }
    686. private void button17_Click(object sender, System.EventArgs e)
    687. {
    688.     expressBox.SelectedText=null;
    689.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button17.Text);
    690.     expressBox.SelectionStart=expressBox.TextLength;
    691. }
    692. private void button21_Click(object sender, System.EventArgs e)
    693. {
    694.     expressBox.SelectedText=null;
    695.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button21.Text);            
    696.     expressBox.SelectionStart=expressBox.TextLength;
    697. }
    698. private void button24_Click(object sender, System.EventArgs e)
    699. {
    700.     expressBox.SelectedText=null;
    701.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button24.Text);
    702.     expressBox.SelectionStart=expressBox.TextLength;
    703. }
    704. private void button18_Click(object sender, System.EventArgs e)
    705. {
    706.     expressBox.SelectedText=null;
    707.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button18.Text);            
    708.     expressBox.SelectionStart=expressBox.TextLength;
    709. }
    710. private void button22_Click(object sender, System.EventArgs e)
    711. {
    712.     expressBox.SelectedText=null;
    713.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button22.Text);
    714.     expressBox.SelectionStart=expressBox.TextLength;
    715. }
    716. private void button25_Click(object sender, System.EventArgs e)
    717. {
    718.     expressBox.SelectedText=null;
    719.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button25.Text);
    720.     expressBox.SelectionStart=expressBox.TextLength;
    721. }
    722. private void button19_Click(object sender, System.EventArgs e)
    723. {
    724.     expressBox.SelectedText=null;
    725.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button19.Text);            
    726.     expressBox.SelectionStart=expressBox.TextLength;
    727. }
    728. private void button23_Click(object sender, System.EventArgs e)
    729. {
    730.     expressBox.SelectedText=null;
    731.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button23.Text);
    732.     expressBox.SelectionStart=expressBox.TextLength;
    733. }
    734. private void button26_Click(object sender, System.EventArgs e)
    735. {
    736.     expressBox.SelectedText=null;
    737.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart, button26.Text);
    738.     expressBox.SelectionStart=expressBox.TextLength;
    739. }
    740. private void button31_Click(object sender, System.EventArgs e)
    741. {
    742.     if(expressBox.Text.Length>0)
    743.         expressBox.Text=expressBox.Text.Remove(expressBox.Text.Length-1,1);
    744. }
    745. private void button32_Click(object sender, System.EventArgs e)
    746. {
    747.     expressBox.Text="";
    748.     endbox.Text="0.000000";
    749. }
    750. private void button33_Click(object sender, System.EventArgs e)
    751. {
    752.     expressBox.SelectedText=null;
    753.     expressBox.Text=expressBox.Text.Insert(expressBox.SelectionStart,"PI");
    754.     expressBox.SelectionStart=expressBox.TextLength;
    755. }
    756. private void button35_Click(object sender, System.EventArgs e)
    757. {            
    758.     strReturnValue = endbox.Text.Trim();
    759. }
    760. private void btnClear_Click(object sender, EventArgs e)
    761. {
    762.     expressBox.Text = "";
    763. }
    764. private void button34_Click(object sender, EventArgs e)
    765. {
    766.     StartExcute();
    767. }
    768. }

    上面代码为一个WinForm窗体主要实现展示一个计算器功能此计算器可以一次性计算多个操作项的值例如:y = 3 + 64 * (2 + 3^5) + sinPI的值,这是它与其他计算器的不同之处比如Windows自带的计算器一次只能计算两个操作数这里可以支持您任意输入多个操作数的表达式最后一块计算出结果支持:sincostancotarcsinarccosarctanseccscln^*/+-()运算符并用括号区分优先级另外此计算器的实现算法也不错采用经典的Stack编译算法感兴趣的读者可以研究一下。

    然后定义一个编辑器,其实所有这些的编辑器功能相当于一个“桥接器”,使属性与自定义Form窗体关联起来。代码如下:

     

    1. /// <summary>
    2. /// 获得本书更多内容,请看:
    3. /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
    4. /// </summary> 
    5. public class CalculatorSelectEditor : System.Drawing.Design.UITypeEditor
    6. {
    7.     public CalculatorSelectEditor()
    8.     {
    9.     }
    10.     public override System.Drawing.Design.UITypeEditorEditStyle GetEditStyle 
    11.          (System.ComponentModel.ITypeDescriptorContext context)
    12.     {            
    13.         return UITypeEditorEditStyle.Modal;
    14.     }
    15.     public override object EditValue(System.ComponentModel.ItypeDescriptor 
    16.         Context context, System.IServiceProvider provider, object value)
    17.     {
    18.         IWindowsFormsEditorService service = (IWindowsFormsEditorService) 
    19.       provider.GetService(typeof(IWindowsFormsEditorService));
    20.         if (service == null)
    21.         {
    22.             return null;
    23.         }
    24.         
    25.         FormKeyBoard form = new FormKeyBoard();
    26.         
    27.         if (service.ShowDialog(form) == DialogResult.OK)
    28.         {
    29.             object strReturn = form.strReturnValue;
    30.             return strReturn;
    31.         }
    32.         
    33.         return value;
    34.     }
    35. }

    此类的功能是弹出一个模式的计算器Form窗体,与4.5.2.2节定义的编辑器功能几乎一样,这里就不作多讲,如果还有不明白的地方请回顾一下前面章节的内容。

    最后定义主控件代码类,如下所示:

    /// <summary>

    /// 获得本书更多内容,请看:

    /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

    /// </summary>

     

    [DefaultProperty("Money")]

    [ToolboxData("<{0}:CalculatorSelectControl

        runat=server></{0}:CalculatorSelectControl>")]

    public class CalculatorSelectControl : WebControl

    {

        [Bindable(true)]

        [Category("自定义计算机属性")]

        [DefaultValue("")]

        [Localizable(true)]

        [Editor(typeof(CalculatorSelectEditor), typeof(System.Drawing.Design. UITypeEditor))]

        [Description("请输入金额")]

        public string Money

        {

            get

            {

                string s = (string)ViewState["Money"];

                return ((s == null) ? "" : s);

            }

     

            set

            {

                ViewState["Money"] = value;

            }

        }

        //… …

    }

     

    u  主控件中定义了一个表示金额的Money属性,并指定设计时的编辑器为我们上面定义的计算器编辑器类:

    [Editor(typeof(CalculatorSelectEditor), typeof(System.Drawing.Design.UITypeEditor))]

    编译此控件,在属性窗口中单击属性旁边的“”按钮即可以看到我们定义的计算器编辑器,如图4-27所示。

     

    4-27  属性窗口

     

    本节内容已经讲解完。本节主要实现了几个自定义功能的编辑器:多类型子项集合编辑器、弹出式模态数据选择编辑器、下拉式数据选择编辑器,最后综合运用前面的知识实现了一个计算器编辑器。限于篇幅,示例都比较简单,但已经足以说明其用法了。另外,在定义自己的编辑器时,除了使用编辑器基类外,还可以把4.5.1节列出的那些系统编辑器作为基类使用。

    4.6  类型转换器

    类型转换器是什么?它主要完成什么样的功能呢?类型转换器可用于在数据类型之间转换值,并通过提供文本到值的转换或待选值的下拉列表来帮助在设计时配置属性。如果配置正确,通过使用InstanceDescriptorSystem.Reflection对象来给设计器序列化系统提供生成在运行时初始化属性的代码所需的信息,类型转换器可以生成属性配置代码。

    类型转换器可用于字符串到值的转换,或用于在设计时和运行时数据类型之间的双向翻译。在宿主(如窗体设计器中的属性浏览器)中,类型转换器允许以文本形式向用户表示属性值,并且可以将用户输入的文本转换为相应数据类型的值。

    大多数本机数据类型(Int32String、枚举类型和其他类型)具有默认的类型转换器,提供从字符串到值的转换并执行验证检查。默认的类型转换器位于System.ComponentModel命名空间中,名为TypeConverterNameConverter。当默认功能无法满足需要时,可以扩展类型转换器;当定义的自定义类型没有关联的类型转换器时,可以实现自定义类型转换器。

    4.6.1  系统类型转换器

    系统默认提供了许多常用的类型转换器,其中有不少我们在使用控件时已经用到了。本节主要列举一些常用的转换器,并以其中几个经典的转换器为例说明其使用方式。

    4.6.1.1  整型类型转换器

    /// <summary>

    /// 获得本书更多内容,请看:

    /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

    /// </summary>

     

    [TypeConverter(typeof(Int32Converter))]

    public int IntConverter

    {

        //… …

    }

    转换器类名为Int32Converter,用于32位有符号整数对象与其他类型之相互转换的类型转换。它的展示样式与一般字符串属性完全一样,只是假如我们输入非整型的值,它会提示格式不正确,要求重新输入,例如输入一个字符“a”,会弹出如图4-28所示的提示输入格式错误的窗口。

     

     

    4-28  类型不匹配提示窗口

     

    在实际应用中,像stringint等类型的属性不需要我们指定转换器,它会自动关联系统默认的转换器,即上面的[TypeConverter(…)]语句可以去掉。

    这里要说明的重点是,为属性增加转换器的方法:

    [TypeConverter(typeof(Int32Converter))]

    该方法在属性上方增加TypeConverter设计时属性,参数为转换器的类型(系统提供的或自定义的),后面小节会介绍怎样为特性的属性类型定制自定义转换器。

    4.6.1.2  WebColor类型转换器

    [TypeConverter(typeof(WebColorConverter))]

    public Color WebColorConverter

    {

        //… …

    }

     

    属性窗口中显示效果如图4-29所示。

     

     

    图4-29 颜色类型转换器

     

    这也是系统提供的一个常用转换器,转换器类为WebColor Converter,注意单击下拉的颜色选择面板不是WebColorConverter提供的,是由默认的颜色编辑器提供(在4.5.1.2节有讲解),WebColorConverter主要用于设计或运行时从WebColor类型到字符串类型的转换。WebColorColor相比,提供了更多表示颜色格式的类型。

    4.6.1.3  控件ID列表类型转换器

    如果在设计器中的某几个控件具有关联关系,比如一个控件在运行时要获取另一个控件的一些属性值,则可以用控件列表转换器ControlIDConverter来建立两个控件之间的关联关系。

    [TypeConverter(typeof(ControlIDConverter))]             

    public string TargetControl

    {

        //… …

    }

    转换器类型为System.Web.UI.WebControls.ControlIDConverter,指定此类型转换器类型的属性展示效果如图4-30所示。

     

    4-30  属性展示效果

     

    在属性列表中,已经列出了设计器中其他几个控件的ID。在实际应用中,知道了控件的ID,就可以通过FindControl方法获取到整个控件了,FindControl使用方法在第3章有讲解。

    除了上面介绍的三个外,系统还提供了好多转换器,限于篇幅就不一一作讲解,请看表4-1

    在实际开发时,可以参考上面列表选择适合的类型转换器。提醒一点,上面的转换器除了可以用作开发控件设计时的属性类型转换器;也可以在其他地方直接使用类转换器中的功能方法,即把类型转换器作为普通类使用,这种方式也用得比较广泛,在后面讲解视图状态机制时就利用了自定义类型转换器(接下来要讲的SolidCoordinateConverter类型转换器)对视图对象进行正反序列化。

    4-1  系统转换器

    系统转换器类型

       

    Int32Converter

    32位有符号整数对象与其他表示形式相互转换

    Int64Converter

    64位有符号整数对象与各种其他表示形式相互转换

    Int16Converter

    16位有符号整数对象与各种其他表示形式相互转换

    续表 

    系统转换器类型

       

    ByteConverter

    字节类型与其他类型相互转换

    BooleanConverter

    Boolean对象与其他各种表示形式相互转换

    CharConverter

    Unicode字符对象与各种其他表示形式相互转换

    UnitConverter

    Unit对象转换为其他数据类型的对象,或从其他类型转换为 UNIT对象

    EnumConverter

    Enum对象与其他各种表示形式相互转换

    DateTimeConverter

    将日期类型与其他类型相互转换

    DecimalConverter

    Decimal对象与其他各种表示形式相互转换

    StringConverter

    在字符串对象与其他表示形式之间实现相互转换

    DoubleConverter

    将双精度对象与其他各种表示形式相互转换

    SingleConverter

    将单精度浮点数字对象与各种其他表示形式相互转换

    TimeSpanConverter

    TimeSpan对象与其他表示形式相互转换

    WebColorConverter

    在预定义的颜色名称或RGB颜色值与System.Drawing.Color 对象之间相互转换

    ArrayConverter

    Array对象与其他各种表示形式相互转换

    CollectionConverter

    将集合对象与各种其他表示形式相互转换

    ExpandableObjectConverter

    在可扩展对象与其他各种表示形式之间实现转换

    GuidConverter

    Guid对象与其他各种表示形式相互转换

    BaseNumberConverter

    为非浮点数字类型提供基类型转换器,上面几个整型转换器就是从此类派生的

    ReferenceConverter

    将对象引用与其他表示形式相互转换

    TypeListConverter

    以可用类型填充列表框的类型转换器

    ObjectConverter

    Object类型与其他类型相互转换

    PropertyConverter

    用于在属性值和字符串之间进行转换的转换器

    DataBindingCollectionConverter

    DataBindingCollection 对象的类型转换器

    DataFieldConverter

    可从当前组件的选定数据源中检索数据字段的列表

    DataMemberConverter

    可从当前组件选定的数据源中检索数据成员的列表

    DataSourceConverter

    数据源类型转换器

    CursorConverter

    Cursor对象与其他各种表示形式相互转换

    FontNamesConverter

    包含字体名称列表的字符串转换为包含个别名称的字符串数组,它还执行反转功能

    FontUnitConverter

    转换字体单位类型

    StringArrayConverter

    在以由逗号分隔的值组成的字符串与字符串数组之间进行转换

    ControlIDConverter

    控件ID列表转换器,4.6.1.3小节已经作过示例

    TargetConverter

    将从Web导航产生的内容的位置(目标)的值转换为字符串。该类还将字符串转换为目标值

    ValidatedControlConverter

    初始化ValidatedControlConverter类的新实例

    4.6.2  定制自己的类型转换器

    系统已经提供了很多的类型转换器,能够满足一般情况下开发的需要。但开发控件时,并不是所有的属性类型都是那些简单的且系统已知的int, string等类型,即控件的属性类型可以是我们定义的任意类型,因此系统不能够自动检测到该使用哪个类型转换器,这种情况就需要我们为自己的属性定制专门的类型转换器。

    实现自己的类型转换器,一般需要以下5个步骤:

     定义一个从TypeConverter派生的类,TypeConverter类提供了将值的类型转换为其他类型,以及访问标准值和子属性的统一方法。其主要是重载类的一些正反向转换方法。

     重写CanConvertFrom方法,在方法中指定是否可以从字符串转换为指定的类型。

     重写ConvertFrom方法,实现从字符串到指定类型的转换。

     重写CanConvertTo方法,指定是否能从SolidCoordinate类转换为stringInstanceDescriptor类型InstanceDescriptor是提供创建对象实例所需信息的类。转换为字符串类型不需要重写此方法。

     重写ConvertTo方法,实现转换。

    其中上面2345都是重载方法。下面就以两个例子说明类型转换器的创建过程。

    4.6.2.1  三维坐标类型转换器

    家都知道在.NET Framework中有Point类,如果把该类作为属性的类型,则系统会自动调用它的类型转换器进行类型转换。比如在属性窗口中设置属性值,切换到源代码视图时即调用类型转换器进行转换;或在运行时控件状态或视图状态对存储的对象进行序列化和反序列化。

     

    这里我们定义一种新的坐标类型SolidCoordinate类,并为其定义匹配的类型转换器,以此说明如何自定义和使用类型转换器。

    来看一下实现后的效果,在属性窗口中设置SolidCoordinate类型的属性,如图4-31所示。

     

    图4-31 三维坐标类型转换器

     

    然后,切换到源代码视图,则会看到如下代码:

    <cc1:CustomeTypeConverterControl ID="CustomeTypeConverterControl1" runat= "server" SolidCoordinate="3, 5, 8" />

    在切换到源代码视图时,转换器类就起作用了,它会把 SolidCoordinate转换成字符串类型,因为在源代码模式下所有代码类型只能以字符串格式存在,所以要求转换为字符串格式;反之,会把字符串逆向转换为SolidCoordinate类。这就是类型转换器的功能。

    格式SolidCoordinate="3,5,8"是可以自定义的,比如可以定义成SolidCoordinate="3-5-8"格式,规则可以在转换器类中任意指定,只要是字符串格式且保证正反向转换规则一致即可。

    接下来开始讲解代码部分。SolidCoordinate类共有三个属性(XYZ),前两个值(XY)与Point类型的(XY)属性一致,表示平面上的横坐标和纵坐标;(Z)属性表示平面之外的第三维坐标,类代码如下:

    /// <summary>

    /// 获得本书更多内容,请看:

    /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx

    /// </summary>

     

    [TypeConverter(typeof(SolidCoordinateConverter))]

    public class SolidCoordinate

    {

        private int x;

        private int y;

        private int z;

     

        public SolidCoordinate()

        {       

        }

     

        public SolidCoordinate(int x, int y, int z)

        {

            this.x = x;

            this.y = y;

            this.z = z;

        }

     

        [NotifyParentProperty(true)]

        public int X

        {

            get

            {

                return this.x;

            }

            set