精华内容
下载资源
问答
  • 可以有许多不同的功能来使命令按钮具有吸引力和效果。 由于命令按钮是典型的GUI元素,因此一般而言,对GUI的大多数改进也将适用于命令按钮。 此外,还有一些发明具体地改善了命令按钮的各个方面。 本文说明了从美国...
  • 此代码是OCX按钮的源代码(包括一些不错的3d渐变代码),以及有关如何使用它和更改其属性的演示。 如果您想制作自己的OCX,或者只想在程序中使用更好的按钮,则很好。 请投票并发表评论。 更多信息 提交时间 2002-...
  • 静态文本框、命令按钮和编辑框是Windows应用程序中最基本的控件。静态文本框是CStatic类的对象,命令按钮是CButton类的对象,编辑框是CEdit类的对象。这三个类都是从CWnd类直接派生来的,具有CWnd类的全部功能。 ...
    上一篇:MFC界面编程基础(10):基于对话框的MFC应用程序下一篇:MFC界面编程基础(12):组框、单选按钮和复选框

    静态文本框、命令按钮和编辑框是Windows应用程序中最基本的控件。静态文本框是CStatic类的对象,命令按钮是CButton类的对象,编辑框是CEdit类的对象。这三个类都是从CWnd类直接派生来的,具有CWnd类的全部功能。

    静态文本框

    静态文本框是最简单的控件。它主要用来显示文本信息,不能接受用户输入,一般不需要连接变量,也不需要处理消息。
    静态文本框的重要属性有:

    • ID :所有静态文本框的缺省 ID 都是 IDC_STATIC ,一般不需要重新设置。
    • 标题:需要显示的文本信息是在这里设置的。
    • 边框:可以对属性对话框中的Sunken,Static Edge属性进行设置。
     //静态文本控件:变量为 m.label
     
     //设置静态控件窗口风格为位图居中显示
     //第一个参数非零值,第二个参数显示为位图+居中显示
     m_label.ModifyStyle(0xf,SS_BITMAP| SS_CENTERIMAGE); 
     
    //通过路径获取bitmap句柄
    //LoadImage是一种函数,功能是装载图标,光标,或位图.
    #define  HBMP(filepath,width,height)
    (HBITMAP)LoadImage(AfxGetInstanceHandle(),filepath,IMAGE_BITMAP,width,height,LR_LOADFROMFILE|LR_CREATEDIBSECTION)
    
    //宽高设置 应该按照控件的大小取设置
    CRect rect;
    m_label.GetWindowRect(rect);
    
    //静态控件设置bitmap
    m_label.SetBitmap(HBMP(TEXT("./1.bmp"),rect.Width(),rect.Height()))
    

    命令按钮

    命令按钮是最常见的、应用最广泛的一种控件。在程序执行期间,当单击某个命令按钮后就会执行相应的消息处理函数。
    命令按钮的主要属性是标题属性,标题属性用来设置在命令按钮上显示的文本。
    命令按钮一般不需要连接变量。
    命令按钮处理的最多的消息是:BN_CLICKED 。
    思考问题:如果从CButton类派生新类CMyButton,如何与按钮控件关联?
    只需要给按钮控件添加一个CMyButton类型的变量,按钮控件就可以使用CMyButton类中实现的所有操作。

    • 实现一个逃跑按钮功能,当鼠标移动到按钮上时,按钮自动移动到窗口其他位置上。
      ①:在对话框窗口上,放置一个按钮
      ②:创建一个CButton类的派生类,在该类中捕获鼠标移动事件。
    void CMyButton::OnMouseMove(UINT nFlags, CPoint point)
    {
    	// TODO:  在此添加消息处理程序代码和/或调用默认值
    
    	//获取按钮的大小
    	CRect btnRect;
    	this->GetWindowRect(&btnRect);
    	//按钮的宽度
    	int btnWidth = btnRect.Width();
    	int btnHeight = btnRect.Height();
    
    	//获取父窗口的大小
    	CRect rect;
    	GetParent()->GetWindowRect(&rect);
    	int width = rect.Width();
    	int height = rect.Height();
    
    	//计算新坐标
    	CPoint pt = point;
    	//如果计算出的左边跟鼠标当前位置左边相等重新计算
    	while (pt == point)
    	{
    		//产生随机X坐标
    		pt.x = rand() % (width - btnWidth);
    		//产生随机y坐标
    		pt.y = rand() % (height - btnHeight);
    	}
    	//将按钮移动到新的位置
    	MoveWindow(pt.x, pt.y, btnWidth, btnHeight);
    	CButton::OnMouseMove(nFlags, point);
    }
    
    • 位图按钮实现
      在对话框的OnInitDialog函数最后添加如下代码:
    // 获取位图资源句柄
    HBITMAP hBitmap = LoadBitmap(AfxGetInstanceHandle(),
     MAKEINTRESOURCE(IDB_BITMAP1));
     
    //按钮添加位图资源
    m_btn.SetBitmap(hBitmap);
    
    //将按钮设置成与位图同样大小
    m_btn.MoveWindow(10, 10, 66, 77);
    
    
    

    另外还需注意,在按钮上显示位图,需要将按钮的Bitmap属性设置为TRUE

    启用按钮:EnableWindows(TRUE);
    禁用按钮:EnableWindows(FALSE);

    编辑框

    1.属性设置

    编辑框的属性主要在 Styles 选项卡中设置。重要属性有:

    • Multiline :定义该编辑框为多行文本框。
    • Number :限定输入的字符只能是数字字符。
    • Border :为控件创建边界。
    • Read-only :编辑框成为只读的,禁止用户编辑。

    2.成员函数

    编辑框常用的成员函数见表。
    在这里插入图片描述
    此外 编辑框还可以使用 CWnd 类的成员函数。 CWnd 类的重要成员函数有:.

    • 获取编辑中的内容

    ①:void GetWindowTextW ( CString& ) const;
    说明:将编辑框中的内容复制到 CString 类对象 rString 中。
    示例:将编辑框 m_e 中的内容复制到 CString 类对象 ch 中。

    CString ch; 
    m_e.GetWindowTextW (ch); 
    

    ②:int GetWindowTextW ( LPTSTR lpszStringBuf, int nMaxCount ) const;
    说明:将编辑框中的内容复制到lpszStringBuf中,最多复制nMaxCount 个字符。lpszStringBuf 是字符数组或字符指针。
    示例:将编辑框 m_e 中的内容复制到字符数组 ch 中。

    TCHAR ch[80]; 
    m_e.GetWindowTextW (ch, 80); 
    
    • 设置编辑框中的内容
      形式: void SetWindowText( LPCTSTR lpszString );
      说明: 将 lpszString 中的内容替换编辑框中原有内容,lpszString 是字符数组或字符指针 。
      示例: 设置编辑框中的内容为“ abcdefg ”
    TCHAR ch[20] = "abcdefg"; 
    m_e.SetWindowText(ch);
    

    3.连接变量

    编辑框在连接变量时,除了要指定变量名之外,还要确定变量类别。变量类别有两个可选择:

    • Control,意味着该变量作控件使用,对应的变量类型只能是CEdit,可以使用该控件类的成员函数;

    • Value,意味着该变量当作C/C++中普通的变量使用,对应的变量类型有CString、int、double等,可以使用这些数据类型的函数,但是不能使用控件的成员函数。
      若一个编辑框连接了一个Value类别的变量,则该变量就表示这个编辑框,编辑框中显示的内容就是变量的值。但是,改变了编辑框的内容并不会自动更新对应的变量的值,同样,改变了变量的值也不会自动刷新编辑框的内容。若要保持一致,需要使用UpdateData()函数更新,如图所示。

    • 若编辑框的内容改变了,则应使用语句UpdateData(TRUE) 获取对话框数据

    • 若变量的值改变了,则应使用语句UpdateData(FALSE) 初始化对话框控件。
      在这里插入图片描述

    注:当在编辑框内输入内容并点击回车就退出,只需要要右键属性->重写->Onok->注释掉里面的代码

    【 例 1 】

    编写一个如图所示的应用程序。若单击“复制”按钮,则把上面的编辑框中的内容复制到下面的编辑框中;若单击“结束”按钮,则退出程序的运行。
    在这里插入图片描述

    新建一个基于对话框的MFC应用程序

    • 放置控件
      ①:删除原有的控件。
      ②:放置所需的控件:两个编辑框和两个命令按钮。
      方法是:先单击控件工具栏上的控件图标选择所需的控件,然后在对话框设计界面上按住鼠标左键拖拉出所需要的大小后释放。

    • 设置控件属性
      选择编辑框控件,在属性面板中对该控件属性进行设置。上面编辑框的属性设置为:
      ①:【Multiline】设置为True,编辑框中可以输入多行文本。
      ②:【Vertical scroll】和【Auto Vscroll】 属性设置为True,编辑框将出现垂直滚动条。
      ③:【Want return】设置为True,控件接收回车键。
      在这里插入图片描述
      下面编辑框属性设置与上面编辑框基本一样,只是可以不设定【Want return】为True。从图中可以看到编辑框的 ID 为 IDC_EDIT1 ,这是该控件的标识。任何一个控件都有一个 ID ,某些函数需要通过 ID 对控件进行操作。

    • 连接变量
      为控件连接变量就是为控件起一个名称。每一个控件都是一个对象,调用 MFC 类库中的函数都是通过对象来实现的。为 IDC_EDIT1 连接变量 m_e1 的步骤为:
      ①:在 IDC_EDIT1 编辑框的右键菜单中选“添加变量”,弹出 “添加成员变量向导”对话框。
      在这里插入图片描述
      ②:在“ 添加成员变量向导 ”中,给“ IDC_EDIT1 ”,添加成员变量。
      在这里插入图片描述
      用同样的方法再为 IDC_EDIT2 连接一个变量 m_e2 。

    • 添加并且编写消息处理函数。
      本例要求单击“复制”按钮后上面编辑框中的内容复制到下面的编辑框中。也就是说,在程序运行时,在“复制”按钮上发生单击事件后, Windows 向对话框发出了一个 BN_CLICKED 消息, CTESTDlg 类应有一个处理该消息的函数。添加和编写这个消息处理函数的过程是:
      ①:直接双击“复制”按钮,程序中会自动添加相对应的响应函数。或者在按钮上单击鼠标右键,在右键菜单中单击【类向导】
      在这里插入图片描述
      弹出类向导对话框
      在这里插入图片描述
      选择按钮对应的ID,要添加的消息类型,选择添加处理程序按钮,按钮对应的消息相应函数添加完毕。
      复制按钮的消息处理函数如下:
      在这里插入图片描述

    用同样的方法为“结束”按钮添加 、编写如下的消息处理函数:在这里插入图片描述

    【例2】

    输入一元二次方程 y=ax2 +bx+c 的系数 a 、 b 、 c ,计算并输出两个根 x1 、x2 ,如图所示。
    在这里插入图片描述
    对话框上有5个静态文本框、5个编辑框和2个命令按钮。编辑框按表连接变量,其余控件不需要连接变量。
    在这里插入图片描述
    为了要计算 b2 -4ac 的平方根,需要使用 sqrt() 函数,因此在 TESTDlg.cpp 文件的开始添加了文件包含命令:

    #include "math.h"  // "计算"按钮的函数 这条要放在最后
    …………………………………………………………
    void CTESTDlg::OnCalc() 
    { 
     	// TODO: Add your control notification handler code here
     	UpdateData(TRUE); 
    	double a=m_a; 
    	double b=m_b; 
    	double c=m_c; 
    	m_x1=(-b+sqrt(b*b-4*a*c))/(2*a); 
    	m_x2=(-b-sqrt(b*b-4*a*c))/(2*a);
    	UpdateData(FALSE);
    } 
    // “结束”按钮的函数 
    …………………………………………………………
    void CTESTDlg::OnOk() 
    { 
     	// TODO: Add your control notification handler code here 
     	//退出当前对话框
     	CDialog::OnOK();           //OnOK(); 
     	//CDialog::OnCancel();
    } 
    …………………………………………………………
    

    4.消息处理函数

    在编辑框能处理的消息中是最重要的是 EN_CHANGE ,这是编辑框中的文本被修改后发出的消息。
    【例】字符串转换程序
    转换规则为: 大写字母转换成小写;
    将小写字母转换成大写;
    换行符和回车符不变;
    其余字符转换为“*”
    要求:每输入一个字符立即转换

    在这里插入图片描述

    • 第1个编辑框连接变量m_e1 (Control )
      属性:
      ①:Multiline 多行,设置为True
      ②:Vertical scroll 垂直滚动条,设置为True
      ③:Auto VScroll(在多行控件中,当用户在最后一行按下ENTER键时自动向上滚动文本),设置为True
      ④:Want return(使多行编辑器接收回车键,表示换行。如果不指定,按回车键会选择缺省的命令按钮,这往往会导致对话框的关闭),设置为True。
    • 第2个编辑框连接变量m_e2 (Control )
      属性:
      ①:Multiline 多行,设置为True
      ②:Vertical scroll 垂直滚动条,设置为True
      ③:Auto VScroll(在多行控件中,当用户在最后一行按下ENTER键时自动向上滚动文本),设置为True
      ④:Read-only 设置为True
    • 对第1个编辑框添加EN_CHANGE 消息处理函数,首先在控件上选右键菜单
      在这里插入图片描述

    弹出事件处理程序向导对话框

    在这里插入图片描述

    相应的函数处理如下:

    void CTESTDlg::OnChangeEdit1() 
    {
    	// TODO: Add your control notification handler code here
    	TCHAR s[80];
    	m_e1.GetWindowTextW(s, 80);
    	for (int i = 0; s[i] != '\0'; i++)
    	{
    		if (s[i] >= 'A' && s[i] <= 'Z')
    		{
    			//大写转换成小写
    			s[i] = s[i] + 'a' - 'A';
    		}
    		else if (s[i] >= 'a' && s[i] <= 'z')
    		{
    			//小写转换成大写
    			s[i] = s[i] + 'A' - 'a';
    		}
    		else if (s[i] == '\n' || s[i] == '\r')
    		{
    			//回车符合换行符不变
    			s[i] = s[i];
    		}
    		else
    		{
    			//其余字符全部变成*号
    			s[i] = '*';
    		}
    	}
    	//将转换完成的字符串显示到m_e2中
    	m_e2.SetWindowTextW(s);
    }
    
    上一篇:MFC界面编程基础(10):基于对话框的MFC应用程序下一篇:MFC界面编程基础(12):组框、单选按钮和复选框
    展开全文
  • 参考lyserver 的 “透明浮动按扭”基础上进行调计的,具有mouse移进、移出、按下、弹起 时 ico和caption都有动感!喜欢的朋友可以拿去使用或参与
  • 文字React 添加命令按钮以对具有区域指示符的消息作出React。
  • 在单击 GridView 控件中的按钮时,将引发 RowCommand 事件。 GridView 控件具有内置功能,用于进行编辑、删除和分页等操作。 还可以添加按钮并使用 RowCommand 事件向控件添加自定义功能。 可以通过下面的方式向 ...
  • 摘要:探究托管 Office 解决方案的代码,该代码显示了可用于 Microsoft Office 系统命令栏的所有按钮表面。了解如何在托管代码中使用命令栏对象模型,同时使解决方案可以跨版本兼容。最终的解决方案显示了所有可用的...

     

    摘要:探究托管 Office 解决方案的代码,该代码显示了可用于 Microsoft Office 系统命令栏的所有按钮表面。了解如何在托管代码中使用命令栏对象模型,同时使解决方案可以跨版本兼容。最终的解决方案显示了所有可用的 Office 命令栏按钮表面和 faceIDs(一个对开发自定义命令栏非常有用的工具)。

    *
    本页内容
    简介简介
    创建外接程序创建外接程序
    准备外接程序以供使用准备外接程序以供使用
    对接口进行编码对接口进行编码
    建立和部署外接程序建立和部署外接程序
    为其他应用程序添加支持为其他应用程序添加支持
    小结小结

    简介

    作为 Microsoft ® Office 开发人员,最困难的事情之一就是使用命令栏和命令栏按钮。从简单到复杂的解决方案可能都要求在小按钮上使用常规文字作为标题,但还有一些解决方案使用吸引人的独立小图标或带有文字的小图标来标识特征。此处的图 1 显示了一个包含所有三种方式的示例。

    odc_listbuttonfaces01

    图 1. 按钮可以具有图像、文字或二者兼有

    使用按钮功能图标创建命令栏时,有数千种可能的图标可供选择。尝试找出合适的命令栏表面的最常见做法是用宏编写一个过程,该过程使用循环结构依次通过编号,并将它们指定为测试按钮的 Faceid 属性,以查看生成图标的外观。

    本文的解决方案建立在这一相同的概念上,但是添加了更为优雅的外观(如图 2 所示)。而且,它还可以在不同应用程序的不同 Microsoft Office 版本中使用,例如 Microsoft Office PowerPoint ® 2003、Microsoft Office Word 2003 或 Microsoft Office Excel 2003。

    odc_listbuttonfaces02

    图 2. 外接程序显示按钮表面并在工具提示文本中提供表面 ID

    返回页首

    创建外接程序

    外接程序是 Microsoft .NET 程序集,它通过 COM 互操作性来使用 Office 对象模型。在本例中,.NET 程序集是以 C# 编写的,但是如果需要,也可以将其轻松地转换为另一种与 .NET 兼容的语言。要创建项目,请遵循以下步骤来启动和使用 Extensibility Wizard

    1.

    在 Microsoft Visual Studio ® .NET 中,创建一个项目,并在 New Project 对话框中选择 Extensibility Projects

    2.

    在该对话框的 Templates 窗口中,单击 Shared Add-in,如图 3 所示。

    odc_listbuttonfaces03

    图 3. 在 Visual Studio .NET 中选择“Shared Add-in”项目类型

    3.

    为项目指定一个名称(例如 ButtonFaces),再指定项目位置,然后单击 OK

    4.

    在下一步中(图 4),选择要宿主该外接程序的应用程序。单击 Microsoft Word 和 Microsoft Excel,然后单击 Next

    odc_listbuttonfaces04

    图 4. 使用 Extensibility Wizard 来选择要宿主自定义外接程序的应用程序

    5.

    该对话框可让您为外接程序键入名称和说明。如图 5 所示进行配置。

    odc_listbuttonfaces05

    图 5. 配置外接程序的名称和说明

    6.

    在下一个窗口中(图 6),选中两个复选框,以便在宿主应用程序启动时加载该外接程序,并且任何用户都可以使用该外接程序。单击 Next

    odc_listbuttonfaces06

    图 6. 选择何时加载外接程序以及哪些用户应该访问它

    7.

    单击 Finish 以完成 Extensibility Wizard 中的步骤。

    返回页首

    准备外接程序以供使用

    项目设置完后,Visual Studio .NET 会创建一个名为 Connect 的类。该类包含由 Extensibility Wizard 自动生成的方法,用于实现外接程序的接口(IDTExtensibility2 接口)。所需的方法由以下内容开始:

    public void OnConnection(object application, 
      Extensibility.ext_ConnectMode connectMode, 
      object addInInst, ref System.Array custom)
      {
        applicationObject = application;
        addInInstance = addInInst;
        if(connectMode != Extensibility.ext_ConnectMode.ext_cm_Startup)
        {
          OnStartupComplete(ref custom);
        }
      }
    

    当外接程序在宿主应用程序中启用时,会激发该方法。对宿主应用程序的引用将作为一个参数传递到该方法。这将允许代码设置指向宿主应用程序的内部变量。代码从此处调用另一个向导生成的方法 OnStartupComplete,以开始准备要使用的外接程序。该方法的代码使用某些变量来访问命令栏,向宿主应用程序中的菜单添加新按钮,以及了解宿主应用程序的类型。这些声明的代码和 OnStartupComplete 方法如下所示:

    private Office.CommandBarButton getButtonFaces;
    private Office.CommandBars bars;
    private Type applicationType;
    public void OnStartupComplete(ref System.Array custom)
      {
      Office.CommandBar bar = null;
      string buttonName = "Get Button Faces";
    
      try
        {
         applicationType = applicationObject.GetType();
         bars = 
         (Office.CommandBars)applicationType.InvokeMember(
         "CommandBars", BindingFlags.GetProperty, null, 
         applicationObject, null);
           bar = bars["Tools"];  
           object missing = Missing.Value;
       Office.CommandBarButton button = (Office.CommandBarButton)
       bar.FindControl(Office.MsoControlType.msoControlButton, 
         missing, buttonName, true, true);
       if (button == null)
         {
           getButtonFaces = (Office.CommandBarButton) bar.Controls.Add(
                Office.MsoControlType.msoControlButton, 
                missing, missing, missing, missing);
           getButtonFaces.Caption = buttonName;
           getButtonFaces.Style = Office.MsoButtonStyle.msoButtonCaption;
           getButtonFaces.Tag = buttonName;
         }
         else
         {
              getButtonFaces = button;
         }
         getButtonFaces.Click += 
              new Office._CommandBarButtonEvents_ClickEventHandler(
              getButtonFaces_Click);
        }
      catch(Exception ex)
        {
           MessageBox.Show(ex.Message);
        }      
      }
    

    该代码的目的就是向宿主应用程序的 Tools 菜单添加按钮,如图 7 所示。

    odc_listbuttonfaces07

    图 7. 外接程序从宿主应用程序的 Tools 菜单中启动

    使用 InvokeMember 方法,代码可获得对宿主应用程序的 CommandBars 集合的引用,然后检索 Tools 命令栏。在使用 .NET Framework 中所谓的反射(System.Reflection namespace 的一部分)时,会使用 InvokeMember 方法。反射可允许代码发现和检查程序集中类型的成员。使用反射,您还可以使用指定的绑定约束并为成员匹配指定的参数列表,来调用这些类型的成员。因为该代码包含对宿主应用程序的类型的引用,所以我们可以使用该类型的 InvokeMember 方法来执行诸如检索宿主应用程序中的 CommandBars 集合之类的操作。

       bars = (Office.CommandBars)applicationType.InvokeMember(
         "CommandBars", BindingFlags.GetProperty, null, 
         applicationObject, null);
           bar = bars["Tools"];  
    

    通过使用 FindControl 方法,代码可以查看是否已经安装了所需的按钮。在 Excel 中,试图添加现有的按钮会引发异常,但是 Word 允许将相同的按钮添加多次。在代码中捕获异常不能同时适用于这两种情况,所以在试图添加按钮之前,最好先检查一下该按钮是否已存在。

    bar.FindControl(Office.MsoControlType.msoControlButton, 
         missing, buttonName, true, true);
    

    如果没有安装该按钮,则代码会将该按钮添加到目标命令栏。

       if (button == null)
         {
           getButtonFaces = (Office.CommandBarButton) bar.Controls.Add(
                Office.MsoControlType.msoControlButton, 
                missing, missing, missing, missing);
           getButtonFaces.Caption = buttonName;
           getButtonFaces.Style = Office.MsoButtonStyle.msoButtonCaption;
           getButtonFaces.Tag = buttonName;
         }
    

    否则,会获取一个对现有按钮的引用。

         else
         {
              getButtonFaces = button;
         }
    

    在任何一种情况下,均由按钮 getButtonFaces 来启动应用程序的主功能。因为该按钮是构建命令栏(即大部分外接程序)的方法,所以我们需要一个该按钮的事件过程。可以用以下方法添加一个事件过程:

         getButtonFaces.Click += 
              new Office._CommandBarButtonEvents_ClickEventHandler(
              getButtonFaces_Click);
    
    返回页首

    对接口进行编码

    由代码添加到菜单(图 7)的按钮事件处理程序主要用于启动外接程序的主功能。事件处理程序的代码如下所示:

      private void getButtonFaces_Click(
        Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
        {
          if (buttonFaceBar == null)
            {
              BuildCommandBar();
              SetupNavigationButtons();
              AttachButtonFaces(0);
            }
         }
    

    该事件处理程序代码调用另外三个过程,其中第一个包含外接程序其余部分的大量代码。BuildCommandBar 过程负责构建显示图标的命令栏,以及将对新命令栏的引用存储到 buttonFaceBar 变量中。SetupNavigationButtons 过程将导航按钮放在界面上,以便用户可以按每组一百个的方式来查看按钮表面。AttachButtonFaces 过程实际显示新创建命令栏上的按钮表面。按照执行顺序查看这些过程,BuildCommandBar 代码如下所示:

    private const short buttonsInRow = 20;
    private const short buttonsWithNavigation = 120;
    private Office.CommandBar buttonFaceBar;
    public void BuildCommandBar()
       {
         try
           {
             buttonFaceBar = bars.Add("Button Faces", 
               Office.MsoBarPosition.msoBarFloating, false, true);
             buttonFaceBar.Protection = 
               Office.MsoBarProtection.msoBarNoChangeVisible
                | Office.MsoBarProtection.msoBarNoResize
                | Office.MsoBarProtection.msoBarNoChangeDock;
            int appTop = 0;
            int appLeft = 0;
            string appName = (string)applicationType.InvokeMember(
              "Name", BindingFlags.GetProperty, null, 
              applicationObject, null);
            switch (appName)
              {
                case "Microsoft Excel":
                  appTop = (int)(double)applicationType.InvokeMember(
                    "Top", BindingFlags.GetProperty, 
                    null, applicationObject, null);
                  appLeft = (int)(double)applicationType.InvokeMember(
                    "Left", BindingFlags.GetProperty, 
                    null, applicationObject, null);
                  break;
                case "Microsoft Word":
                  appTop = (int)applicationType.InvokeMember(
                    "Top", BindingFlags.GetProperty, 
                    null, applicationObject, null);
                  appLeft = (int)applicationType.InvokeMember(
                    "Left", BindingFlags.GetProperty, 
                    null, applicationObject, null);
                   break;
                default:
                  break;
              }
            buttonFaceBar.Top = (int) (appTop + 50);
            buttonFaceBar.Left = (int) (appLeft + 50);
            Office.CommandBarButton button = null;
            for (int i = 1; i <= buttonsWithNavigation; i++)
              {
                object missing = Missing.Value;
                button = (Office.CommandBarButton)
                  buttonFaceBar.Controls.Add(
                  Office.MsoControlType.msoControlButton, 1,
                  missing, missing, missing);
                button.FaceId = 1;
               }
            GetHighestFaceIds(
              (Office.CommandBarButton)buttonFaceBar.Controls[1]);
            buttonFaceBar.Visible = true;
            buttonFaceBar.Width = button.Width * buttonsInRow +6;
          }
        catch(Exception ex)
          {
            MessageBox.Show(ex.Message);
          }      
      }
    

    该过程的主要流程是,首先将新命令栏添加到宿主应用程序的 CommandBars 集合。

             buttonFaceBar = bars.Add("Button Faces", 
               Office.MsoBarPosition.msoBarFloating, false, true);
             buttonFaceBar.Protection = 
               Office.MsoBarProtection.msoBarNoChangeVisible
                | Office.MsoBarProtection.msoBarNoResize
                | Office.MsoBarProtection.msoBarNoChangeDock;
    

    CommandBars 集合的 Add 方法接受四个参数。第一个是目标命令栏的名称。第二个是该命令栏的位置,在本例中是浮动命令栏。第三个带有 False 值,它指定新命令栏不应用新命令栏替代活动菜单栏。最后一个参数告诉宿主应用程序,命令栏在应用程序关闭时不应被删除。

    添加新命令栏之后,代码将设置 TopLeft 属性偏移量,以便将新创建的命令栏定位在相对于宿主应用程序的 TopLeft 属性的特定位置上。命令栏本身包括一组按钮,确切地说是 120 个按钮,这些按钮在用户查看成千上万个按钮表面时被重复使用。在请求每个新的按钮表面组时,新的按钮图像会被复制到现有按钮中。即使有 120 个按钮之多,但实际上只使用一行按钮来进行导航,所以用户可以按每组一百个的方式来查看按钮表面。界面按钮包括用于导航的按钮和一个取消按钮。

    Office.CommandBarButton button = null; for (int i = 1; i <= buttonsWithNavigation; i++) { object missing = Missing.Value; button = (Office.CommandBarButton) buttonFaceBar.Controls.Add( Office.MsoControlType.msoControlButton, 1, missing, missing, missing); button.FaceId = 1; }

    在添加新按钮的循环操作中,使用主命令栏的 Controls 属性来添加新的空白按钮。调用 Add 方法来添加和返回新按钮。通过将 msoControlButton 参数传递给该方法,可以指定新按钮应为一个简单的按钮,而非下拉按钮或一长串可能按钮类型中的一种。第二个参数(在这里值为 1)指定将指定类型的空白控件添加到命令栏。然后,将它的 FaceId 属性设为 1,以使其成为空白控件或者其表面没有图像。这与指定控件类型不同,它只规定哪种按钮表面(如果有)应当显示在按钮上。

    界面准备完成后,代码将通过调用自定义过程 GetHighestFaceIds 来查找最后一组按钮的起始点。然后,它使命令栏可见,并设置其宽度。该宽度由这些按钮的宽度乘以按钮的数量得出。此外,还要将一个任意数添加到该计算中。这个数字(下例中的 6)可能不适合所有的屏幕分辨率或风格。当然,应当进行完全测试。也可以设计一个更能区分上下文的计算。

            GetHighestFaceIds(
              (Office.CommandBarButton)buttonFaceBar.Controls[1]);
            buttonFaceBar.Visible = true;
            buttonFaceBar.Width = button.Width * buttonsInRow +6;
    

    为了找到最后一组按钮的起始点,GetHighestFaceIds 过程以 1000 的增量来依次通过每个编号,最多达到不合情理的高限(一千万),尽管该代码足够长,并且在它到达那个编号时尚未结束。设置 FaceId 属性 (testButton.FaceId = i) 会在数字过大时引发错误。在引发异常时,代码会在最终识别最后一组按钮表面的起始点之前,以较小的增量依次通过编号。在退出之前,代码会继续寻找最后一个可用的表面 ID,并将其存储在 finalID 变量中。GetHighestFaceIds 过程将当前 Office 应用程序版本中开始某个组的按钮所能具有的最大表面 ID 值存储在 highestStartID 变量中。

    private int highestStartID;
    private int finalID;
    public void GetHighestFaceIds(Office.CommandBarButton testButton)
    {
       int increment = 1000;
       int loopStart = 1000;
       for (int i = loopStart; i <= 10000000; i += increment)
       {
          try
          {
             testButton.FaceId = i;
          }
          catch (Exception)
          {
             if (increment == 1000)
             {
                i -= 1000 + 100;
                increment = 100;
             }
             else if (increment == 100)
             {
                highestStartID = i - buttonsInGroup;
                i -= 100 + 1;
                increment = 1;
             }
             else
             {
                finalID = i - 1;
                break;
             }
          }
       }
    }
    

    通过调用 BuildCommandBar 准备好命令栏之后,getButtonFaces_Click 过程(在用户决定使用来自宿主应用程序 Tools 菜单的外接程序时引发的事件过程)就可以设置导航按钮,然后开始用可见的图像填充其按钮。设置导航按钮是在 SetUpNavigationButtons 过程中完成的。虽然设置过程有点冗长,但代码却非常简单,只是添加具有它们自己固定的按钮表面和相应事件处理程序的导航按钮。第一个按钮(适当地命名为 firstButton)包括一个与其他按钮明显不同的属性设置,即,它的 BeginGroup 属性设为 True。这可确保该按钮位于命令栏上所有其他按钮的前面。以下代码摘录还包含了适合于每个导航按钮的按钮表面的值。此处,使用的是现有按钮表面的值。该代码还包括了一个设置 firstButton 导航按钮 ID 的常量 (firstNavigationButton),该导航按钮是底行的第七个按钮。因为有一百个按钮,所以该 ID 必须是 100 加上 7。

    private Office.CommandBarButton firstButton;
    private Office.CommandBarButton bigPreviousButton;
    private Office.CommandBarButton previousButton;
    private Office.CommandBarButton nextButton;
    private Office.CommandBarButton bigNextButton;
    private Office.CommandBarButton lastButton;
    private Office.CommandBarButton cancelButton;  
    private const short firstNavigationButton = 107;
    private const short firstFace = 154;
    private const short bigPreviousFace = 41;
    private const short previousFace = 155;
    private const short nextFace = 156;
    private const short bigNextFace = 39;
    private const short lastFace = 157;
    private const short cancelFace = 478;
    public void SetupNavigationButtons()
    {
       try
         {
           firstButton = 
             (Office.CommandBarButton)buttonFaceBar.Controls
             [firstNavigationButton];
    
             firstButton.BeginGroup = true;
             firstButton.FaceId = firstFace;
             firstButton.Click += 
                new Office._CommandBarButtonEvents_ClickEventHandler(
                firstHundred_Click);
             firstButton.TooltipText = "First 100";
    
             bigPreviousButton = 
               (Office.CommandBarButton)buttonFaceBar.Controls
               [firstNavigationButton+1];
             bigPreviousButton.FaceId = bigPreviousFace;
             bigPreviousButton.Click += 
               new Office._CommandBarButtonEvents_ClickEventHandler(
               bigPreviousHundred_Click);
              bigPreviousButton.TooltipText = "Previous 1000";
    
              previousButton = 
                (Office.CommandBarButton)buttonFaceBar.Controls
                [firstNavigationButton+2];
              previousButton.FaceId = previousFace;
              previousButton.Click += 
                new Office._CommandBarButtonEvents_ClickEventHandler(
                previousHundred_Click);
              previousButton.TooltipText = "Previous 100";
    
              nextButton = 
                (Office.CommandBarButton)buttonFaceBar.Controls
                [firstNavigationButton+3];
              nextButton.FaceId = nextFace;
              nextButton.Click += 
                new Office._CommandBarButtonEvents_ClickEventHandler(
                nextHundred_Click);
              nextButton.TooltipText = "Next 100";
    
              bigNextButton = 
                (Office.CommandBarButton)buttonFaceBar.Controls
                [firstNavigationButton+4];
              bigNextButton.FaceId = bigNextFace;
              bigNextButton.Click += 
                new Office._CommandBarButtonEvents_ClickEventHandler(
                bigNextHundred_Click);
              bigNextButton.TooltipText = "Next 1000";
    
              lastButton = 
                (Office.CommandBarButton)buttonFaceBar.Controls
                [firstNavigationButton+5];
              lastButton.FaceId = lastFace;
              lastButton.Click += 
                new Office._CommandBarButtonEvents_ClickEventHandler(
                lastHundred_Click);
              lastButton.TooltipText = "Last 100";
              cancelButton = 
                (Office.CommandBarButton)buttonFaceBar.Controls
                [firstNavigationButton+6];
              cancelButton.FaceId = cancelFace;
              cancelButton.Click += 
                   new Office._CommandBarButtonEvents_ClickEventHandler(
                   cancelFaceBar_Click);
              cancelButton.TooltipText = "Close";
              Office.CommandBarButton button =
                (Office.CommandBarButton)buttonFaceBar.Controls
                [firstNavigationButton+7];
              button.BeginGroup = true;
           }
         catch (Exception ex)
           {
              MessageBox.Show(ex.Message);
           }
      }
    

    按钮及其用途的列表如下所示:

    按钮名称事件过程用途

    firstButton

    firstHundred_Click

    显示前 100 个按钮

    bigPreviousButton

    bigPreviousHundred_Click

    显示当前组之前的 1000 个按钮

    nextButton

    nextHundred_Click

    显示当前组之后的 100 个按钮

    previousHundred

    previousHundred_Click

    显示当前组之前的 100 个按钮

    bigNextButton

    bigNextHundred_Click

    显示当前组之后的 1000 个按钮

    lastButton

    lastHundred_Click

    显示最后 100 个按钮

    cancelButton

    cancelFaceBar_Click

    关闭自定义命令栏

    这些事件过程的相应代码如下所示:

    private void firstHundred_Click(
      Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
    {
      AttachButtonFaces(0);
    }
    private void bigPreviousHundred_Click(
      Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
    {
      if (latestStartID >= buttonsInGroup)
      {
        AttachButtonFaces(Math.Max(0, latestStartID - 1000));
      }
    }
    private void previousHundred_Click(
      Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
    {
      if (latestStartID >= buttonsInGroup)
        {
          AttachButtonFaces(latestStartID - buttonsInGroup);
        }
    }
    private void nextHundred_Click(
      Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
    {
      if (latestStartID < highestStartID)
        {
          AttachButtonFaces(latestStartID + buttonsInGroup);
        }
    }
    private void bigNextHundred_Click(
      Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
    {
      if (latestStartID < highestStartID)
      {
        AttachButtonFaces(Math.Min(highestStartID, latestStartID + 1000));
      }
    }
    private void lastHundred_Click(
      Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
    {
      AttachButtonFaces(highestStartID);
    }
    private void cancelFaceBar_Click(
      Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
    {
      CloseFaceBar();
    }
    

    取消使用自定义命令栏的事件过程会调用一个 CloseFaceBar 过程。该过程可以十分简单地删除自定义命令栏。

    private void CloseFaceBar()
    {
      try
      {
         buttonFaceBar.Delete();
         buttonFaceBar = null;
      }
         catch(Exception ex)
        {
           MessageBox.Show(ex.Message);
        }      
    }
    

    AttachButtonFaces 过程使用起始点,并用图像表面从该起始点开始填充按钮。该过程使用两个变量(latestStartIDfinalID)来记录当前组开头的按钮的 ID。这个 ID 会随着用户在按钮表面组上移动而递增或递减。

    private const short buttonsInGroup = 100;
    private int latestStartID;
    public void AttachButtonFaces(int startID)
    {
      int i = 1;
      latestStartID = startID;
      UpdateNavigationButtons();
      int relativeFinalID = finalID - latestStartID;
    
      try
        {
          for (; i <= buttonsInGroup && i <= relativeFinalID; i++)
            {
               Office.CommandbarButton button = 
                 (Office.CommandBarButton)buttonFaceBar.Controls[i];
               int id = startID + i;
               button.FaceId = id;
               button.TooltipText = id.ToString();
            }
    
            buttonFaceBar.Name = String.Format(
              "Button Faces ({0}..{1})", startID +1, startID + i - 1);
            ClearUnusedFaces(i);
         }
        catch (Exception ex)
         {
           MessageBox.Show(ex.Message);
         }
    }
    

    为了让用户了解显示的是哪一组按钮,应使用具有两个占位符的值的标题串联来设置命令栏的显示名称,一个用于组中的起始 faceID,另一个用于结束 faceID

            buttonFaceBar.Name = String.Format(
              "Button Faces ({0}..{1})", startID +1, startID + i - 1);
    

    通过使用整个组的起始点,代码可以依次通过新的按钮表面直至达到组限制 buttonsInGroup

          for (; i <= buttonsInGroup && i <= relativeFinalID; i++)
            {
               Office.CommandBarButton button = 
                 (Office.CommandBarButton)buttonFaceBar.Controls[i];
               int id = startID + i;
               button.FaceId = id;
               button.TooltipText = id.ToString();
            }
    

    通过该方法,AttachButtonFaces 过程执行了两个较为重要的操作。第一,它更新了导航按钮,以便在按钮无法使用时将其禁用。例如,如果用户位于列表末尾,则让用户查看更多按钮表面的按钮应该被禁用。同样,如果用户位于列表开头,则应该不能够向回导航,因此应当禁用相应的按钮。该逻辑是在 UpdateNavigationButtons 过程中处理的。

    public void UpdateNavigationButtons()
    {
       try
       {
             buttonFaceBar.Controls[firstNavigationButton].Enabled = 
             latestStartID != 0;
             buttonFaceBar.Controls[firstNavigationButton+1].Enabled = 
             latestStartID >= 1000;
             buttonFaceBar.Controls[firstNavigationButton+2].Enabled = 
             latestStartID != 0;
             buttonFaceBar.Controls[firstNavigationButton+3].Enabled = 
             latestStartID != highestStartID;
             buttonFaceBar.Controls[firstNavigationButton+4].Enabled = 
             latestStartID <= highestStartID - 1000;
             buttonFaceBar.Controls[firstNavigationButton+5].Enabled = 
             latestStartID != highestStartID;
           }
       catch(Exception ex)
       {
             MessageBox.Show(ex.Message);
           }      
    }
    

    第二,AttachButtonFaces 过程调用了 ClearUnusedFaces 过程,以便如果按钮表面列表的末尾处有未使用的按钮,则清除这些按钮的任何预先存在的内容。

    public void ClearUnusedFaces(int firstUnusedButton)
    {
       try
       {
          for (int i = firstUnusedButton; i <= buttonsInGroup; i++)
          {
            Office.CommandBarButton button = 
              (Office.CommandBarButton)buttonFaceBar.Controls[i];
            button.FaceId = 1;
            button.TooltipText = "";
          }
        }
        catch(Exception ex)
        {
          MessageBox.Show(ex.Message);
        }      
    }
    

    对整个应用程序进行编码,除了在从宿主应用程序卸载外接程序时运行的例程。还有一个作为 IDTExtensibility2 接口一部分的内置事件过程,它可让您根据条件运行代码,这取决于触发外接程序卸载的原因。

    public void OnDisconnection(
      Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)
    {
       if(disconnectMode != 
         Extensibility.ext_DisconnectMode.ext_dm_HostShutdown)
       {
         OnBeginShutdown(ref custom);
       }
       applicationObject = null;
    }
    public void OnBeginShutdown(ref System.Array custom)
    {
        object missing = Missing.Value ;
        getButtonFaces.Delete(missing);
        getButtonFaces = null;
    }
    

    该代码将检查宿主应用程序是否已关闭,这会导致外接程序被卸载。如果卸载外接程序不是宿主应用程序关闭所导致的结果,那么代码将允许 OnBeginShutdown 过程运行。该事件中的代码只会从宿主应用程序的 Tools 菜单中删除该按钮。

    返回页首

    建立和部署外接程序

    最后一个步骤是建立和部署外接程序。在使用 Visual Studio .NET 中的 Extensibility Wizard 首次创建整个解决方案时,不但创建了实际的外接程序,还创建了第二个项目并将其添加到解决方案中。这第二个项目就是外接程序本身的安装项目。项目文件(请参见图 8)包括为外接程序检测到的依赖项,一旦建立项目,它就会成为可以启动以安装外接程序的 Microsoft Installer 软件包。

    odc_listbuttonfaces08

    图 8. 总体解决方案包括项目和外接程序的设置文件

    要在 Visual Studio .NET 中构建外接程序,请在 Build 菜单上单击 Build Solution。这将创建一个 .NET 程序集,并为 COM 互操作性创建必要的基础结构。生成安装项目的一种方法是,在 Solution Explorer 窗口中右键单击项目,然后单击 Build。这将生成安装项目,并创建 .MSI 文件。要找到生成的 Microsoft Installer 软件包,请查看解决方案和应用程序项目文件的同一目录。在那里,您可以找到 ButtonFacesSetup 目录。在该目录中,您可以看一下已经完成的 build 类型目录,发布或调试,以及查找 .MSI 文件。双击该文件可以启动安装,但这一步在开发计算机上可以不执行,因为它已经注册并可以使用了。要使用外接程序,请启动 Word 或 Excel,并在 Tools 菜单上单击 Get Button Faces

    返回页首

    为其他应用程序添加支持

    迄今为止,外接程序只支持 Word 和 Excel。需要记住的是,每个应用程序(但应该是 Microsoft Office 系统的一部分)都可能具有与同一系列中其他应用程序不同的行为和属性。例如,Word 和 Excel 中命令栏支持的轻微差别,就意味着要对代码进行更改以适应它们。同样,为其他应用程序(例如 PowerPoint)添加支持就意味着,您必须在代码的其他位置上添加条件代码。完全了解对象模型和严格的测试,可确保您将这些差异挑选出来,并调整您的代码以开发稳定可靠的外接程序。

    至于 Visual Studio 中的项目,实际上只需要进行一项调整,就可以使外接程序在其他应用程序中可用。图 9 显示了外接程序安装项目的 Registry Editor 界面。

    odc_listbuttonfaces09

    图 9. 使用 Registry Editor 在目标计算机上配置注册表设置

    安装项目中的 Registry Editor 可让您配置在安装项目运行时,您希望已经在目标计算机上配置好的注册表设置。例如,在 图 9 中,您可看到 Excel、PowerPoint 以及 Word 的注册表项。ButtonFaces.Connect 项具有三个值,其中两个是字符串值,一个是数值,该项可告诉宿主应用程序应该在何时加载外接程序。在本例中,这三个值指定外接程序应该在宿主应用程序启动时加载。在扩展性向导首次运行以创建初始项目时,PowerPoint 项中的外接程序注册表项不存在。这可在稍后手动进行添加,以使外接程序可以用于 PowerPoint。再次运行安装项目可以将该注册表项添加到目标机器的注册表中。此外,您可能需要编写条件代码,以适应宿主应用程序的对象模型中的差异。

    返回页首

    小结

    在设计用户友好的 Office 应用程序时通常会使用命令栏。然而,在命令栏按钮上也经常使用图像,而不是文本。在为按钮表面创建自定义图像之前,最好先查看一下是否已经存在适合您需要的图像。在将近数千个按钮表面中是存在这种可能性的,总会有一个符合您的应用程序要求。

    本文中的代码使得查找按钮表面库以寻找所需按钮表面更为简便。该代码是以 C# 编写的,它创建的外接程序可以用于 Office 2000、Office XP 以及 Office 2003。它对不同 Office 版本的可移植性在某种程度上是可能的,这是因为它并没有对每个版本中提供的按钮表面数量进行假设。这些数量在不同的版本中确实各不相同,但是外接程序代码设计为可智能地查明数量,并相应地进行响应。

    展开全文
  • 路由事件命令

    千次阅读 2011-08-28 10:15:12
    MSDN: http://msdn.microsoft.com/zh-cn/magazine/cc785480.aspx 路由事件概述 刚开始接触 WPF 时,您可能会在自己并知晓的情况下就用到了路由事件。例如,当您在 Visual Studio® 设计器中向窗
    MSDN: http://msdn.microsoft.com/zh-cn/magazine/cc785480.aspx

    路由事件概述


    刚开始接触 WPF 时,您可能会在自己并不知晓的情况下就用到了路由事件。例如,当您在 Visual Studio ® 设计器中向窗口添加一个按钮,并将其命名为 myButton,然后双击该按钮时,Click 事件将挂接在您的 XAML 标记之内,它的事件处理程序会添加到 Window 类的代码隐藏中。这种感觉与在 Windows 窗体和 ASP.NET 中挂接事件并无二致。实际上,它比较接近 ASP.NET 的代码编写模型,但更类似 Windows 窗体的运行时模型。具体来说,在按钮的 XAML 标记中,代码的结尾类似如下所示:

    <Button Name="myButton" Click="myButton_Click">Click Me</Button>
    
    挂接事件的 XAML 声明就象 XAML 中的属性分配,但结果是针对指定事件处理程序的对象产生一个正常的事件挂接。此挂接实际上出现在编译时生成的窗口局部类中。要查看这一挂接,转到类的构造函数,右键单击 InitializeComponent 方法调用,然后从上下文菜单中选择“转到定义”。编辑器将显示生成的代码文件(其命名约定为 .i.g.cs 或 .i.g.vb),其中包括在编译时正常生成的代码。在显示的局部类中向下滚动到 Connect 方法,您会看到下面的内容:

    #line 6 "..\..\Window1.xaml"
    this.myButton.Click += 
      new System.Windows.RoutedEventHandler(
      this.myButton_Click);
    
    这一局部类是在 编译时从 XAML 中生成的,其中包含那些需要设计时编译的 XAML 元素。大部分 XAML 最终都会成为编译后程序集中嵌入了二进制的资源,在运行时会与二进制标记表示的已编译代码合并。
    如果看一下窗口的代码隐藏,您会发现 Click 处理程序如下所示:

    private void myButton_Click(
      object sender, RoutedEventArgs e) { }
    
    到目前为止,它看起来就象任何其他 .NET 事件挂接一样——您有一个显式声明的委托,它挂接到一个对象事件且委托指向某个处理方法。使用路由事件的唯一标记是 Click 事件的事件参数类型,即 RoutedEventArgs。那么路由事件究竟有何独特之处呢?要理解这一点,首先需要了解 WPF 元素化的组合模型。

    WPF 元素树
    如果您在项目中开启一个新窗口并在设计器中将按钮拖入窗口内,您会得到 XAML 格式的元素树,如下所示(为了清楚略去了属性):
    复制代码
    <Window>
      <Grid>
        <Button/>
      </Grid>
    </Window>
    
    其中的每个元素都代表对应 .NET 类型的一个运行时实例,元素的声明分层结构形成了所谓的逻辑树。此外,WPF 中的许多控件不是 ContentControl 就是 ItemsControl,这代表他们可以有子元素。例如,Button 是一个 ContentControl,它可以将复杂的子元素做为其内容。您可以展开逻辑树,如下所示:
    复制代码
    <Window>
      <Grid>
        <Button>
          <StackPanel>
            <Image/>
            <TextBlock/>
          </StackPanel>
        </Button>
      </Grid>
    </Window>
    
    生成的 UI 如 图 1 所示。
    图 1 包含按钮内容的简单窗口
    如您所想,树可以有多个分支(Grid 中的另一 Button),因此逻辑树会变得极为复杂。对于逻辑树的 WPF 元素,您需要意识到您所见到的并不是您在运行时真正得到的内容。每个这样的元素通常都会在运行时扩展为更为复杂的可视元素树。在本例中,元素的逻辑树扩展为可视元素树,如 图 2 所示。
    图 2 简单窗口可视树
    我使用名为 Snoop 的工具 ( blois.us/Snoop) 查看 图 2 中所示可视树的元素。您可以看到窗口 (EventsWindow) 实际是将其内容置入 Border 和 AdornerDecorator 之内,用 ContentPresenter 显示其中的内容。按钮也与此类似,将其内容置入 ButtonChrome 对象,然后用 ContentPresenter 显示内容。
    单击按钮时,我可能实际根本没有单击 Button 元素,可能是单击可视树中的某一子元素,甚至是逻辑树中未显示的元素(如 ButtonChrome)。例如,假设我在按钮内的图像上方单击鼠标。这一单击操作在一开始实际是将其表达为 Image 元素中的 MouseLeftButtonDown 事件。但却需要转化为 Button 层级的 Click 事件。这就要引入路由事件中的路由。

    事件路由

    对逻辑树和可视树有所了解很有必要,因为路由事件主要是根据可视树进行路由。路由事件支持三种路由策略: 气泡、隧道和直接。
    气泡事件最为常见,它表示事件 从源元素扩散(传播)到可视树,直到它被处理或到达根元素。这样您就可以针对源元素的上方层级对象处理事件。例如,您可向嵌入的 Grid 元素附加一个 Button.Click 处理程序,而不是直接将其附加到按钮本身。气泡事件有指示其操作的名称(例如,MouseDown)。

    隧道事件采用另一种方式, 从根元素开始,向下遍历元素树,直到被处理或到达事件的源元素。这样上游元素就可以在事件到达源元素之前先行截取并进行处理。根据命名惯例,隧道事件带有前缀 Preview(例如 PreviewMouseDown)。

    直接事件类似 .NET Framework 中的正常事件。该事件唯一可能的处理程序是与其挂接的委托。
    通常,如果为特殊事件 定义了隧道事件,就会有相应的气泡事件。在这种情况下, 隧道事件先触发,从根元素开始,下行至源元素,查找处理程序。一旦它被处理或到达源元素,即会触发气泡事件,从源元素上行,查找处理程序。气泡或隧道事件不会仅因调用事件处理程序而停止路由。如果您想中止隧道或气泡进程,可使用您传递的事件参数在事件处理程序中将事件标记为已处理。
    复制代码
    private void OnChildElementMouseDown(object sender, 
      MouseButtonEventArgs e) {
      e.Handled = true;
    }
    
    一旦您的处理程序将事件标记为已处理,该事件便不会传给任何其他处理程序。这一论断只是部分正确。实际上,事件路由仍在继续起作用,您可利用 UIElement.AddHandler 的替换方法在代码中显式挂接事件处理程序,该方法有一个额外的标记,可以有效指出“即使事件被标记为已处理也可调用我”。您用类似如下所示的调用指定该标记:
    复制代码
    m_SomeChildElement.AddHandler(UIElement.MouseDownEvent, 
      (RoutedEventHandler)OnMouseDownCallMeAlways,true);
    
    AddHandler 的第一个参数是您想要处理的 RoutedEvent。第二个参数是对事件处理方法(它需要有事件委托的正确签名)的委托。第三个参数指明如果另一个处理程序已将事件标记为已处理,您是否想得到通知。您调用 AddHandler 的元素就是在路由期间观察事件流动的元素。

    路由事件和组合

    现在我们来看一看 Button.Click 事件的形成过程,以了解为什么它如此重要。如前所述,用户将对 Button 可视树中的某些子元素(例如上一示例中的 Image)使用 MouseLeftButtonDown 事件启动 Click 事件。
    在 Image 元素内发生 MouseLeftButtonDown 事件时,PreviewMouseLeftButtonDown 在根元素启动,然后沿隧道下行至 Image。如果没有处理程序为 Preview 事件将 Handled 标记设置为 True,MouseLeftButtonDown 即会从 Image 元素开始向上传播,直至到达 Button。按钮处理这一事件,将 Handled 标记设为 True,然后引发其自身的 Click 事件。本文中的示例代码包括一个应用程序,它带有整个路由链挂接的处理程序,可帮您查看这一进程。
    其蕴含的意义不可小视。例如,如果我选择通过应用包含 Ellipse 元素的控件模板替换默认按钮外观,可以保证在 Ellipse 外部单击即可触发 Click 事件。靠近 Ellipse 的外缘单击仍处于 my button 的矩形边界内,但 Ellipse 有其自身的 MouseLeftButtonDown 击中检测,而 Ellipse 外部按钮的空白区域则没有。
    因此,只有在 Ellipse 内部的单击才会引发 MouseLeftButtonDown 事件。它仍由附加此模板的 Button 类进行处理,所以,即便是自定义的按钮,您也能得到预测的行为。在编写自己自定义的复合控件时也需牢记这一非常重要的概念,因为您的操作很可能类似 Button 对控件内子元素的事件处理。

    附加事件

    为了让元素能处理在不同元素中声明的事件,WPF 支持附加事件。附加事件也是路由事件,它支持元素 XAML 形式的挂接,而非声明事件所用的类型。例如,如果您想要 Grid 侦听采用气泡方式通过的 Button.Click 事件,仅需按如下所示进行挂接即可。
    复制代码
    <Grid Button.Click="myButton_Click">
      <Button Name="myButton" >Click Me</Button>
    </Grid>
    
    在编译时生成的局部类中的最终代码现在如下所示:
    复制代码
    #line 5 "..\..\Window1.xaml"
    ((System.Windows.Controls.Grid)(target)).AddHandler(
    System.Windows.Controls.Primitives.ButtonBase.ClickEvent, 
    new System.Windows.RoutedEventHandler(this.myButton_Click));
    
    附加事件可在挂接事件处理程序位置方面给予您更大的灵活性。但如果元素包含在同一类中(如本例所示),其差异并不会显露出来,这是由于处理方法针对的仍是 Window 类。
    它在两方面产生影响。 第一,事件处理程序根据处理元素在气泡或隧道元素链中的位置进行调用。第二,您可额外执行一些操作,如从所用控件内封装的对象处理事件。例如,您可以象处理 Grid 中所示的事件一样处理 Button.Click 事件,但这些 Button.Click 事件可以从窗口中包含的用户控件内部向外传播。
    提示:事件处理程序命名
    如果您不想一味使用事件处理程序的默认命名约定(objectName_eventName),仅需输入您需要的事件处理程序名称,右键单击,然后单击上下文菜单中的“浏览到事件处理程序”即可。Visual Studio 随即按指定的名称生成事件处理程序。
    在 Visual Studio 2008 SP1 中,“属性”窗口会有一个事件视图,它与 Windows 窗体中的视图类似,因此如果您有 SP1,即可以在那里指定事件名称。但如果您采用的是 XAML,这是生成显式命名的处理程序的便捷方法。
    生成事件处理程序
    并非所有事件都声明为附加事件。实际上,大部分事件都不是这样。但当您需要在控件来源之外处理事件时,附加事件会提供相当大的帮助。

    路由命令概述

    您已看到了路由事件,接下来我来介绍路由命令。WPF 的路由命令为您提供了一种特定的机制,用于将工具栏按钮和菜单项这类 UI 控件挂接到处理程序,并且无需在应用程序中加入许多关联性很强的重复代码。与正常事件处理相比,路由命令有三大优点:
    • 路由命令源元素(调用程序)能够与命令目标(处理程序)分离——它们不需要彼此引用,如果是通过事件处理程序链接,就需要相互引用。
    • 处理程序指出命令被禁用时,路由命令将自动启用或禁用所有相关的 UI 控件。
    • 您可以使用路由命令将键盘快捷方式与其他形式的输入手势(例如,手写)相关联,作为调用命令的另一种方式。
    此外,路由命令特有的 RoutedUICommand 类可以定义单一 Text 属性,用做任何控件(命令调用程序)的命令提示。与访问每个相关的调用程序控件相比,Text 属性的本地化更为容易。
    要在调用程序上声明命令,仅需在触发命令的控件上设置 Command 属性即可。
    复制代码
    <Button Command="ApplicationCommands.Save">Save</Button>
    
    MenuItem、Button、RadioButton、CheckBox、Hyperlink 和许多其他控件都支持 Command 属性。
    对于您想用做命令处理程序的元素,可设置 CommandBinding:
    复制代码
    <UserControl ...>
      <UserControl.CommandBindings>
        <CommandBinding Command="ApplicationCommands.Save"    
          CanExecute="OnCanExecute" Executed="OnExecute"/>
      </UserControl.CommandBindings>
      ...
    </UserControl>
    
    CommandBinding 的 CanExecute 和 Executed 属性指向声明类代码隐藏中的方法,这些方法会在命令处理进程中被调用。此处的要点是命令调用程序既不需要了解,也不需要引用命令处理程序,处理程序不必知道是哪个元素将要调用命令。
    调用 CanExecute 来确定是否应启用命令。要启用命令,应将事件参数的 CanExecute 属性设置为 True,如下所示:
    复制代码
    private void OnCanExecute(object sender, 
      CanExecuteRoutedEventArgs e) {
      e.CanExecute = true;
    }
    
    如果命令处理程序带有定义的 Executed 方法,但没有 CanExecute 方法,命令也会被启用(在这种情况下,CanExecute 隐式为 true)。通过 Executed 方法,根据调用的命令执行相应的操作。这类与命令相关的操作可以是保存文档、提交订单、发送电子邮件等。

    操作中的路由命令
    为了使这一概念更为具体并让路由命令的益处立竿见影,我们来看一个简单的示例。在 图 3 中,您可看到一个简单的 UI,它有两个输入文本框,一个对文本框中的文本执行 Cut 操作的工具栏按钮。
    图 3 包含 Cut 命令工具栏按钮的简单示例
    要使用事件完成挂接,需要为工具栏按钮定义 Click 处理程序,且该代码需要引用两个文本框。您需要根据控件中的文本选择确定哪个文本框是焦点项并调用相应的剪贴板操作。还要根据焦点项的位置和文本框中是否有选项,在适当的时候启用或禁用工具栏按钮。代码十分凌乱且复杂。
    对于这一简单示例,问题还不大,但如果这些文本框深入用户控件或自定义控件的内部,且窗口代码隐藏无法直接访问它们,情况又会如何?您不得不在用户控件的边界显示 API 以便能从容器实现挂接,或公开显露文本框,两者皆不是理想的方法。
    如使用命令,只需将工具栏按钮的 Command 属性设为在 WPF 中定义的 Cut 命令即可。
    复制代码
    <ToolBar DockPanel.Dock="Top" Height="25">
      <Button Command="ApplicationCommands.Cut">
        <Image Source="cut.png"/>
      </Button>
    </ToolBar>
    
    现在您运行应用程序,会看到工具栏按钮一开始是被禁用的。在其中一个文本框中选择了文本后,工具栏按钮会启用,如果单击该按钮,文本会被剪切到剪贴板。这一操作适用于 UI 中任何位置的任何文本框。喔,很不错吧?
    实际上,TextBox 类实现有一个针对 Cut 命令的内置命令绑定,并为您封装了该命令(Copy 和 Paste)的剪贴板处理。那么,命令如何只调用所关注的文本框,消息如何到达文本框并告诉它处理命令?这便是路由命令中路由部件发挥作用的地方。

    命令路由

    路由命令与路由事件的区别在于命令从其调用程序路由至处理程序的方法。具体来说,路由事件是从幕后在命令调用程序和处理程序之间路由消息(通过将其挂接至可视树中的命令绑定)。
    这样,众多元素间都存在关联,但在任何时刻都实际只有一个命令处理程序处于活动状态。活动命令处理程序由可视树中命令调用程序和命令处理程序的位置、以及 UI 中焦点项的位置共同决定。路由事件用于调用活动命令处理程序以询问是否应启用命令,并调用命令处理程序的 Executed 方法处理程序。
    通常,命令调用程序会在自己在可视树中的位置与可视树根项之间查找命令绑定。如找到,绑定的命令处理程序会确定是否启用命令并在调用命令时一并调用其处理程序。如果命令挂接到工具栏或菜单中的一个控件(或将设置 FocusManager.IsFocusScope = true 的容器),则会运行一些其他的逻辑,沿可视树路径从根项到命令绑定的焦点元素进行查看。
    图 3 的简单应用程序中,实际发生的情况是:由于 Cut 命令按钮位于工具栏内,所以由具备焦点项的 TextBox 实例处理 CanExecute 和 Execute。如 图 3 中的文本框包含在用户控件之内,您就有机会对窗口、包含 Grid 的用户控件、包含文本框的用户控件或单个文本框设置命令绑定。有焦点项的文本框将确定其路径的终点(它的起点是根项)。
    要理解 WPF 路由命令的路由,需要认识到一旦调用一个命令处理程序,就不能再调用其他处理程序。因此,如果用户控件处理 CanExecute 方法,就不会再调用 TextBox CanExecute 实现。

    定义命令

    ApplicationCommands.Save 和 ApplicationCommands.Cut 是 WPF 提供的诸多命令中的两个命令。 图 4 中显示了 WPF 中五个内置命令类及其所包含的一些命令示例。
    命令类示例命令
    ApplicationCommandsClose、Cut、Copy、Paste、Save、Print
    NavigationCommandsBrowseForward、BrowseBack、Zoom、Search
    EditingCommandsAlignXXX、MoveXXX、SelectXXX
    MediaCommandsPlay、Pause、NextTrack、IncreaseVolume、Record、Stop
    ComponentCommandsMoveXXX、SelectXXX、ScrollXXX、ExtendSelectionXXX
    XXX 代表操作的集合,例如 MoveNext 和 MovePrevious。每一类中的命令均定义为公用静态(在 Visual Basic ® 中共享)属性,以便您可轻松挂接。通过使用以下方式,您可以轻松定义自己的自定义命令。稍后我会提供相应的示例。
    您也可搭配使用一个简短的注释,如下所示:
    复制代码
      <Button Command="Save">Save</Button>
    
    如您使用此缩写版本,WPF 中的类型转换器将尝试从内置命令集合找到命名的命令。在此例中结果完全相同。我倾向于使用长名版本,这样代码更为明确、更易维护。不会对命令的定义位置产生歧义。即使是内置命令,在 EditingCommands 类和 ComponentCommands 类之间也会有一些重复。

    命令插入
    路由命令是 WPF 所定义的 ICommand 界面的一种特殊实现。ICommand 的定义如下:
    复制代码
      public interface ICommand {
        event EventHandler CanExecuteChanged;
        bool CanExecute(object parameter);
        void Execute(object parameter);
      }
    
    内置的 WPF 命令类型为 RoutedCommand 和 RoutedUICommand。这两种类均实现 ICommand 界面并使用我先前所介绍的路由事件执行路由。
    我们期望命令调用程序调用 CanExecute 来确定是否启用任何相关的命令调用代码。命令调用程序可通过订阅 CanExecuteChanged 事件来确定何时调用该方法。在 RoutedCommand 类中,根据状态或 UI 中焦点项的变化触发 CanExecuteChanged。调用命令时,会调用 Executed 方法并通过路由事件沿可视树分派至处理程序。
    支持 Command 属性的类(如 ButtonBase)实现 ICommandSource 界面:
    复制代码
    public interface ICommandSource {
      ICommand Command { get; }
      object CommandParameter { get; }
      IInputElement CommandTarget { get; }
    }
    
    Command 属性在调用程序和它将调用的命令之间建立关联。CommandParameter 允许调用程序在调用命令的同时传递某些数据。您可使用 CommandTarget 属性根据焦点项的路径替换默认路由,并通知命令系统使用指定的元素做为命令处理程序,而不是依赖路由事件和命令处理程序基于焦点项所做的决定。

    路由命令的局限

    路由命令非常适合单用户界面,挂接工具栏和菜单项以及处理与键盘焦点项目(如剪贴板操作)相关的条目。但是,如果您要构建复杂的用户界面,即命令处理逻辑位于视图定义的支持代码之内,且命令调用程序不总是在工具栏或菜单之内,在这种情况下,路由命令就显得力不从心了。使用 UI 复合模式时,如 Model View Controller 或 MVC ( msdn.microsoft.com/magazine/cc337884)、Model View Presenter 或 MVP ( msdn.microsoft.com/magazine/cc188690)、Presentation Model,在 WPF 循环中亦称做 Model View ViewModel ( msdn.microsoft.com/library/cc707885),通常会出现这种情况。
    此时的问题是启用并处理命令逻辑可能不是直接归属于可视树,而是位于表示器或表示模型。此外,确定是否启用命令的状态与命令调用程序和视图在可视树中的位置无关。有时,您会遇到一个特殊命令在给定时间有多个处理程序的情形。
    要了解在哪些情况下路由命令会出现问题,请查看 图 5。它是一个简单的窗口,包含一对用户控件,这两个控件以 MVP 或 MVC 模式表示视图。主窗口包含一个 File 菜单和工具栏,其中有 Save 命令按钮。在主窗口上方还有一个输入文本框,以及一个将 Command 设为 Save 的 Button。
    图 5 复合用户界面
    提示:挂接匿名方法
    图 6 所示的代码中,我使用了我同事 Juval Lowy 传授给我的技巧,向声明中的委托挂接一个空的匿名方法。
    复制代码
    Action<string> m_ExecuteTargets = delegate { };
    
    这样,在调用委托前,您就不必再检查是否有空值,因为在调用列表中始终都有一个 no-op 订户。您还可能通过在多线程环境中取消订阅避免可能的争用,如果您检查空值,经常会出现争用。
    有关此技巧的详细信息,请参阅 Juval Lowy 撰写的 《Programming .NET Components, Second Edition》。
    UI 的其余部分由两个视图提供,每个都是简单用户控件的实例。每个用户控件实例的边界颜色各不相同,这是为更清楚地显示它们所提供的 UI 内容。每个用户控件实例都有一个 Save 按钮,它将 Command 属性设为 Save 命令。
    路由命令(与可视树中的位置密切相关)带来的困难在这一简单示例中一览无余。在 图 5 中,窗口本身没有针对 Save 命令的 CommandBinding。但它的确包含该命令的两个调用程序(菜单和工具栏)。在此情形中,我不想让顶层窗口在调用命令时必须了解采取何种操作。而是希望由用户控件表示的子视图处理命令。此例中的用户控件类有针对 Save 命令的 CommandBinding,它为 CanExecute 返回 true。
    但在 图 5 中,您可以看到焦点项位于顶部文本框的窗口内,而此级别的命令调用程序却被禁用。此外,尽管用户控件中没有焦点项,但用户控件中的 Save 按钮却被启用。
    如果您将焦点项从一个文本框更改到一个用户控件实例内,菜单和工具栏中的命令调用程序会变为启用状态。但窗口本身的 Save 按钮不会变为启用状态。实际上,在这种情况下无法用正常路由启用窗口上方文本框旁的 Save 按钮。
    原因仍与单个控件的位置相关。由于在窗口级没有命令处理程序,尽管焦点项位于用户控件之外,但可视树上方或焦点项路径上仍没有命令处理程序会启用挂接为命令调用程序的控件。因此一旦涉及这些控件,会默认禁用命令。但是,对于用户控件内的命令调用程序,由于处理程序在可视树的位置靠上,所以会启用命令。
    一旦您将焦点项转到其中一个用户控件内,位于窗口和焦点项路径上文本框之间的用户控件即会提供命令处理程序,用于为工具栏和菜单启用命令,这是因为它们会检查焦点项路径以及其在可视树中的位置与根项之间的路径。由于窗口级按钮和根项之间没有处理程序,所以无法启用该按钮。
    要是这个简单的小示例中的可视树和焦点项路径的繁文缛节就让您倍感头疼,如果 UI 相当复杂,在可视树中众多不同位置有命令调用程序和处理程序,要想理顺命令启用和调用有多难就可想而知了。那会您联想起电影《Scanners》中的怕人情节,让人头昏眼花。

    避免命令出错
    要防止路由命令出现与可视树位置相关的问题,您需要保持简洁。通常应确保命令处理程序位于相同的元素,或在可视树中处于调用命令的元素上方。您可以从包含命令处理程序的控件使用 CommandManager.RegisterClassCommandBinding 方法,在窗口级加入命令绑定,这样就能实现上述目标。
    如果您实现的是本身接受键盘焦点项(像文本框)的自定义控件,那么属于例外情况。在这种情形下,如果您想在控件本身嵌入命令处理且该命令处理仅在焦点项处于您的控件上时产生关联,您可实现这一目标,它的工作状况类似先前所示的 Cut 命令示例。
    您也可通过 CommandTarget 属性明确指定命令处理程序来解决上述问题。例如,对于 图 5 中从未启用过的窗口级 Save 按钮,您可将其命令挂接更改为如下所示:
    复制代码
    <Button Command="Save" 
      CommandTarget="{Binding ElementName=uc1}"
      Width="75" Height="25">Save</Button>
    
    在此代码中,Button 专门将其 CommandTarget 设为 UIElement 实例,该实例中包含一个命令处理程序。在本例中,它指定名为 uc1 的元素,该元素恰好为示例中两个用户控件实例之一。由于该元素有一个始终返回 CanExecute = true 的命令处理程序,窗口级的 Save 按钮始终处于启用状态,并仅调用该控件的命令处理程序,无论调用程序相对于命令处理程序的位置如何都是如此。

    超越路由命令
    由于路由命令存在一定的限制,许多用 WPF 构建复杂 UI 的公司已转为使用自定义 ICommand 实现,这些实现能为它们提供自己的路由机制,特别是与可视树无关联且支持多个命令处理程序的机制。
    创建自定义命令实现并不困难。针对类实现 ICommand 界面后,会为挂接命令处理程序提供一种方式,然后可在调用命令时执行路由。您还必须确定使用何种标准确定引发 CanExecuteChanged 事件的时机。
    创建自定义命令时最好先使用委托。委托已支持调用目标方法,并支持多个订户。
    图 6 显示了名为 StringDelegateCommand 的命令类,它使用委托来允许挂接多个处理程序。它支持向处理程序传递字符串参数,并使用调用程序的 CommandParameter 确定向处理程序传递的消息。
    复制代码
    public class StringDelegateCommand : ICommand {
      Action<string> m_ExecuteTargets = delegate { };
      Func<bool> m_CanExecuteTargets = delegate { return false; };
      bool m_Enabled = false;
    
      public bool CanExecute(object parameter) {
        Delegate[] targets = m_CanExecuteTargets.GetInvocationList();
        foreach (Func<bool> target in targets) {
          m_Enabled = false;
          bool localenable = target.Invoke();
          if (localenable) {
            m_Enabled = true;
            break;
          }
        }
        return m_Enabled;
      }
    
      public void Execute(object parameter) {
        if (m_Enabled)
          m_ExecuteTargets(parameter != null ? parameter.ToString() : null);
      }
    
      public event EventHandler CanExecuteChanged = delegate { };
    
      ...
    }
    
    如您所见,我选择使用 Func<bool> 委托挂接确定是否启用命令的处理程序。在 CanExecute 实现中,类遍历挂接到 m_CanExecuteTargets 委托的处理程序,查看是否有处理程序想执行的委托。如果有,它为要启用的 StringDelegateCommand 返回 true。调用 Execute 方法时,它仅需检查是否启用了命令,如启用,则调用所有挂接到 m_ExecuteTargets Action<string> 委托的处理程序。
    要将处理程序挂接到 CanExecute 和 Execute 方法,StringDelegateCommand 类公开 图 7 中所示的事件访问器,从而允许处理程序从基础委托轻松订阅或取消订阅。注意,您还可以在处理程序订阅或取消订阅时使用事件访问器触发 CanExecuteChanged 事件。
    复制代码
    public event Action<string> ExecuteTargets {
      add {
        m_ExecuteTargets += value;
      }
      remove {
        m_ExecuteTargets -= value;
      }
    }
    
    public event Func<bool> CanExecuteTargets {
      add {
        m_CanExecuteTargets += value;
        CanExecuteChanged(this, EventArgs.Empty);
      }
      remove {
        m_CanExecuteTargets -= value;
        CanExecuteChanged(this, EventArgs.Empty);
      }
    }
    

    路由处理程序示例
    在代码下载的示例应用程序中,我挂接了这个类。该示例有一个简单视图,隐含一个表示器(沿用 MVP,但没有模型)。表示器向视图公开一个表示模型以绑定数据(您可将表示模型想象成位于表示器和视图之间,而 MVP 模型位于表示器之后)。表示模型通常公开视图可以绑定数据的属性。在本例中,它仅公开了一个命令属性,以便可以通过数据绑定在视图的 XAML 中轻松实现挂接。
    复制代码
    <Window x:Class="CustomCommandsDemo.SimpleView" ...>
      <Grid>
        <Button Command="{Binding CookDinnerCommand}" 
          CommandParameter="Dinner is served!" ...>Cook Dinner</Button>
        <Button Click="OnAddHandler" ...>Add Cook Dinner Handler</Button>
      </Grid>
    </Window>
    
    Binding 声明只查找当前 DataContext 的属性(名为 CookDinnerCommand),如找到,则将它传给 Icommand。我们在前面提到过 CommandParameter,调用程序可以用它随同命令传递某些数据。在本例中,请注意我只传递了将通过 StringDelegateCommand 传递给处理程序的字符串。
    此处所示为视图的代码隐藏(Window 类):
    复制代码
    public partial class SimpleView : Window {
      SimpleViewPresenter m_Presenter = new SimpleViewPresenter();
    
      public SimpleView() {
        InitializeComponent();
        DataContext = m_Presenter.Model;
      }
    
      private void OnAddHandler(object sender, RoutedEventArgs e) {
        m_Presenter.AddCommandHandler();
      }
    }
    
    视图构建其表示器,从表示器取得表示模型,然后将其设置为 DataContext。它还有按钮 Click 的处理程序,该处理程序调入表示器,让它为命令添加处理程序。
    复合事件和命令
    今年我一直在与 Microsoft 模式和实践小组合作,帮助为 WPF 开发复合应用程序指南,这组指南用于在 WPF 中开发复杂的复合应用程序。其中包含称为 Composite Application Libraries (CAL) 的库,为复合应用程序提供服务和帮助程序类。
    Glenn Block 的文章“使用 WPF 构建复合应用程序的模式”中有“WPF 复合应用程序指南”的更多信息,网址为 msdn.microsoft.com/magazine/cc785479
    图 8 显示了运行中的这一应用程序。第一个窗口处于初始状态,未挂接命令处理程序。由于没有命令处理程序,所以您会看到第一个按钮(调用程序)被禁用。按第二个按钮时,它会调入表示器并挂接新的命令处理程序。此时会启用第一个按钮,您再单击它时,它会调用其通过数据绑定松散联接的命令处理程序和基础命令的订户列表。
    图 8 运行中的自定义命令示例
    表示器代码如 图 9 中所示。您可以看到表示器构建了表示模型,并通过 Model 属性将其公开给视图。从视图调用 AddCommandHandler 时(响应第二个按钮 Click 事件),它会向模型的 CanExecuteTargets 和 ExecuteTargets 添加一个订户。这些订阅方法是表示器中的简单方法,它们分别返回 true 并显示 MessageBox。
    复制代码
    public class SimpleViewPresenter {
      public SimpleViewPresenter() {
        Model = new SimpleViewPresentationModel();
      }
    
      public SimpleViewPresentationModel Model { get; set; }
    
      public void AddCommandHandler() {
        Model.CookDinnerCommand.CanExecuteTargets += CanExecuteHandler;
        Model.CookDinnerCommand.ExecuteTargets += ExecuteHandler;
      }
    
      bool CanExecuteHandler() {
        return true;
      }
    
      void ExecuteHandler(string msg) {
        MessageBox.Show(msg);
      }
    }
    


    展开全文
  • 快速清理电脑垃圾用cleanmgr命令。方法如下:1、首先用鼠标右击”菜单“按钮,在其弹出的下拉菜单中找到并点击”运行“按钮。2、接着需要在接下来弹出来的页面框中输入“CMD”命令符,点击“确定”按钮选项。3、最后...

    大家好,我是时间财富网智能客服时间君,上述问题将由我为大家进行解答。

    快速清理电脑垃圾用cleanmgr命令。方法如下:

    1、首先用鼠标右击”菜单“按钮,在其弹出的下拉菜单中找到并点击”运行“按钮。

    2、接着需要在接下来弹出来的页面框中输入“CMD”命令符,点击“确定”按钮选项。

    3、最后只需要在接下来弹出的命令提示符窗口中输入“cleanmgr”即可解决清理系统垃圾的cmd命令。

    计算机(computer)俗称电脑,是现代一种用于高速计算的电子计算机器,可以进行数值计算,又可以进行逻辑计算,还具有存储记忆功能。是能够按照程序运行,自动、高速处理海量数据的现代化智能电子设备。

    由硬件系统和软件系统所组成,没有安装任何软件的计算机称为裸机。可分为超级计算机、工业控制计算机、网络计算机、个人计算机、嵌入式计算机五类,较先进的计算机有生物计算机、光子计算机、量子计算机等。计算机发明者约翰・冯・诺依曼。计算机是20世纪最先进的科学技术发明之一,对人类的生产活动和社会活动产生了极其重要的影响,并以强大的生命力飞速发展。

    展开全文
  • 路由事件概述 WPF 元素树 事件路由 路由事件和组合 附加事件 路由命令概述 操作中的路由命令 命令路由 定义命令 命令插入 路由命令的局限 避免命令出错 超越路由命令 路由...
  • MFC按钮控件Button按下和弹起事件实现示例
  • 【C#】按钮长按

    千次阅读 2015-12-01 20:28:51
    如果在C#窗体,单纯点击按钮,之后将鼠标长时间放在这个按钮上,放开,双击按钮默认产生的click事件是不会出现多次相应的。 双击按钮默认产生的click事件只会在鼠标点击按钮,再松开按钮,这样才会执行其中的代码...
  • 在单击 GridView 控件中的按钮时,将引发 RowCommand 事件。GridView 控件具有内置功能,用于进行编辑、删除和分页等操作。还可以添加按钮并使用 RowCommand 事件向控件添加自定义功能。可以通过下面的方式向...
  • WPF Commend 命令

    千次阅读 2015-04-09 06:38:14
    WPF为我们准备了完善的命令系统,你可能会问:“有了路由事件什么还需要命令系统呢?”。事件的作用是发布、传播一些消息,消息传达到了接收者,事件的指令也就算完成了,至于如何响应事件送来的消息事件做...
  • PyQt5 工具按钮、抽象按钮

    千次阅读 2018-04-26 16:56:10
    与普通命令按钮相反,工具按钮通常显示文本标签,而是显示一个图标。 工具按钮通常在使用QToolBar.addAction()创建新的QAction实例时创建,或者使用QToolBar.addAction()将现有的操作添加到工...
  • 1 概述 ...这类标签语言的好处就是可以很轻松的和后台逻辑代码解耦和。 XAML在.NET框架当中的位置:...由于XAML被设计用来专门编写Windows窗口程序的,与BS架构区分客户端和服务器端不同,所以与HTML在浏览器上被解析...
  • 按钮类控件

    2017-07-28 09:50:24
    如果按钮具有焦点,就可以使用鼠标左键、Enter键或空格键触发该按钮的Click事件。通过设置窗体的 AcceptButton 或 CancelButton 属性,无论该按钮是否有焦点,都可以使用户通过按 Enter 或 Esc 键来触发按钮的 Click...
  • 通过关闭窗体示例了解到:要想知道哪个组件具备什么监听器,需要查看该组件对象的功能 查阅APT文档,button按钮支持一个特有监听器addActionListener(ActionListener); ActionListener :事件监听器接口 该...
  • 按钮控件

    千次阅读 2005-04-27 14:37:00
    按钮控件(button) [下载例程] 按钮控件根据其风格属性可派生出:命令按钮(Pushbutton)、检查框(Check Box)、...命令按钮的作用是对用户的鼠标单击作出反应并触发相应的事件,在按钮中既可以显示正文,也可以显示位图
  • 对于开发人员来说,终端是最重要的工具之一。掌握终端,能够有效的提升开发人员的工作流程。本文列举了一系列Linux命令,旨在帮助大家充分利用终端这个工具
  • 【实现目标】 通过“组策略”对话框,用户可以为IE浏览器的工具栏添加按钮,使单击工具栏上的这些按钮时,可以直接调用其他程序。 【操作方法】 单击[开始]/[运行]命令,打开“运行”对话框,在该对话框中输入...
  • 文章基于python异步io框架Tornado,结合JavaScript的onkeydown事件、 jQuery的mousedown、mouseup、touchstart、touchend事件以及Ajax,实现了局域网环境下通过网页对摄像头拍摄角度的实时控制。
  • 命令模式

    2013-06-07 09:59:24
    研磨设计模式之 命令模式-1 命令模式也是开发中常见的一个模式,也不是太难,比较简单,下面来详细的写一下...就是按下启动按钮就可以了吗?难道还有什么玄机不成。  对于使用电脑的客户——就是我们来说,开机确实
  • 按钮具有4个参数:名称,类型,操作,颜色 争论 描述 选项 例子 姓名 按钮的名称 任何字符串 我的按钮 类型 运行命令或打开网址 命令,链接 命令 行动 运行或链接打开的命令 来自命令选项板或URL的任何命令 拨动销 ...
  • 我们的创作,ZOOB博特是和谐哈克2019周期间开发的,具备了1000的命令,是,千COMMANDS充满了乐趣和癌变模因,笑话,流行文化参考,和游戏。 Zoob Bot由Nathan Melwani,Patrick Aventino,Jinsu Hwang和Aurora ...
  • 创建按钮

    2012-03-12 18:44:35
    准备事项 当开始使用按钮时,了解以下 Flash Pro 基础知识很重要: ...在 Flash Pro 中创建按钮有许多不同...多数按钮设计为具有不同的“弹起”、“按下”和“指针经过”状态。这些状态使按钮在光标移动到其上方时
  • C#:C#控件系列三 (按钮类控件)

    千次阅读 2018-07-11 17:41:48
    如果按钮具有焦点,就可以使用鼠标左键、Enter键或空格键触发该按钮的Click事件。通过设置窗体的AcceptButton或CancelButton 属性,无论该按钮是否有焦点,都可以使用户通过按 Enter 或 Esc 键来触发按钮的 Click...
  • Linux命令大全----系统管理相关命令

    千次阅读 2015-10-25 18:18:57
    本文主要讲了Linux命令大全----系统管理相关命令,并附有实例

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 99,270
精华内容 39,708
关键字:

命令按钮不具有什么事件