Server-Side UI Automation Provider - WinForm Sample
2014-09-14
引用程序集
提供程序接口
公开服务器端 UI 自动化提供程序
从 UI 自动化提供程序返回属性
从 UI 自动化提供程序中引发事件
在 UI 自动化提供程序中支持控件模式
WinForm Sample
参考
引用程序集[1]
UI 自动化提供程序项目必须引用以下程序集:
- UIAutomationProviders.dll
- UIAutomationTypes.dll
- WindowsBase.dll
提供程序接口[1]
每个 UI 自动化提供程序必须实现下列接口之一。
接口 |
说明 |
---|---|
提供窗口中承载的简单控件的功能,包括对控件模式和属性的支持。 |
|
继承自 IRawElementProviderSimple。 为复杂控件中的元素添加功能,包括在片段中导航、设置焦点和返回元素的边框。 |
|
继承自 IRawElementProviderFragment。 为复杂控件中的根元素添加功能,包括将子元素定位于指定坐标以及设置整个控件的焦点状态。 |
IRawElementProviderSimple的metadata见图1
图1 metadata - IRawElementProviderSimple
公开服务器端 UI 自动化提供程序[2]
重写窗口过程以捕获 WM_GETOBJECT,以响应客户端应用程序发送到控件窗口的 WM_GETOBJECT 消息时,返回实现 IRawElementProviderSimple(或派生接口)的对象。
1 /// <summary> 2 /// Handles WM_GETOBJECT message; others are passed to base handler. 3 /// </summary> 4 /// <param name="m">Windows message.</param> 5 /// <remarks> 6 /// This method enables UI Automation to find the control. 7 /// </remarks> 8 [PermissionSetAttribute(SecurityAction.Demand, Unrestricted = true)] 9 protected override void WndProc(ref Message m) 10 { 11 const int WM_GETOBJECT = 0x003D; 12 13 if ((m.Msg == WM_GETOBJECT) && (m.LParam.ToInt32() == AutomationInteropProvider.RootObjectId)) 14 { 15 m.Result = AutomationInteropProvider.ReturnRawElementProvider( 16 Handle, m.WParam, m.LParam, (IRawElementProviderSimple)this); 17 return; 18 } 19 base.WndProc(ref m); 20 }
从 UI 自动化提供程序返回属性[3]
实现接口IRawElementProviderSimple方法GetPropertyValue,使得UI 自动化提供程序将元素的属性返回到客户端应用程序。
对于不显式支持的任意属性,提供程序必须返回 null。这样可以确保 UI 自动化尝试从其他源(如宿主窗口提供程序)获取属性。
1 /// <summary> 2 /// Returns property values. 3 /// </summary> 4 /// <param name="propId">Property identifier.</param> 5 /// <returns>Property value.</returns> 6 object IRawElementProviderSimple.GetPropertyValue(int propId) 7 { 8 if (propId == AutomationElementIdentifiers.ClassNameProperty.Id) 9 { 10 return "CustomButtonControlClass"; 11 } 12 else if (propId == AutomationElementIdentifiers.ControlTypeProperty.Id) 13 { 14 return ControlType.Button.Id; 15 } 16 if (propId == AutomationElementIdentifiers.HelpTextProperty.Id) 17 { 18 return "Change the button color and pattern."; 19 } 20 if (propId == AutomationElementIdentifiers.IsEnabledProperty.Id) 21 { 22 return true; 23 } 24 else 25 { 26 return null; 27 } 28 }
从 UI 自动化提供程序中引发事件[4]
下面的代码在自定义按钮控件的实现中引发了UI自动化事件。该实现使UI自动化客户端应用程序能够模拟按钮单击。
为了避免不必要的处理,示例将检查 ClientsAreListening 以确定是否应该引发事件。
1 /// <summary> 2 /// Responds to a button click, regardless of whether it was caused by a mouse or 3 /// keyboard click or by InvokePattern.Invoke. 4 /// </summary> 5 private void OnCustomButtonClicked() 6 { 7 // TODO Perform program actions invoked by the control. 8 9 // Raise an event. 10 if (AutomationInteropProvider.ClientsAreListening) 11 { 12 AutomationEventArgs args = new AutomationEventArgs(InvokePatternIdentifiers.InvokedEvent); 13 AutomationInteropProvider.RaiseAutomationEvent(InvokePatternIdentifiers.InvokedEvent, this, args); 14 } 15 }
在 UI 自动化提供程序中支持控件模式[5]
支持控件模式
1.为该元素支持的控件模式实现相应的接口,例如,为 InvokePattern 实现 IInvokeProvider。
1 /// <summary> 2 /// Responds to an InvokePattern.Invoke by simulating a MouseDown event. 3 /// </summary> 4 void IInvokeProvider.Invoke() 5 { 6 // If the control is not enabled, we're responsible for letting UI Automation know. 7 // It catches the exception and then throws it to the client. 8 IRawElementProviderSimple provider = this as IRawElementProviderSimple; 9 if (false == (bool)provider.GetPropertyValue(AutomationElementIdentifiers.IsEnabledProperty.Id)) 10 { 11 throw new ElementNotEnabledException(); 12 } 13 14 // Create arguments for the click event. The parameters aren't used. 15 MouseEventArgs mouseArgs = new MouseEventArgs(MouseButtons.Left, 1, 0, 0, 0); 16 17 // Simulate a mouse click. We cannot call RespondToClick directly, 18 // because it is illegal to update the UI from a different thread. 19 MouseEventHandler handler = CustomButton_MouseDown; 20 BeginInvoke(handler, new object[] { this, mouseArgs }); 21 }
若invoke实现如下,则用客户端模拟点击操作,只会弹出对话框。
void IInvokeProvider.Invoke(){ MessageBox.Show("invoke Pattern."); }
我们可以用UISpy模拟客户端操作,引发invoke事件:
- 选中CustomControl
- 菜单‘View'->'Control Pattern,选择'Call Method'
见下图2,只弹出了MessageBox,customControl的图形并没有改变
图2 UISpy模拟客户端操作,引发invoke事件
2.返回一个对象,其中包含 IRawElementProviderSimple.GetPatternProvider 实现中的每个控件接口的实现。
1 /// <summary> 2 /// Returns the object that supports the specified pattern. 3 /// </summary> 4 /// <param name="patternId">ID of the pattern.</param> 5 /// <returns>Object that implements IInvokeProvider.</returns> 6 object IRawElementProviderSimple.GetPatternProvider(int patternId) 7 { 8 if (patternId == InvokePatternIdentifiers.Pattern.Id) 9 { 10 return this; 11 } 12 else 13 { 14 return null; 15 } 16 }
WinForm Sample[6]
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 using System.Windows.Automation.Provider; 5 using System.Windows.Automation; 6 using System.Drawing; 7 using System.Windows.Forms; 8 using System.Diagnostics; 9 using System.Security.Permissions; 10 11 12 namespace ElementProvider 13 { 14 class CustomButton : Control, IRawElementProviderSimple, IInvokeProvider 15 { 16 bool buttonState = false; 17 IntPtr myHandle; 18 19 /// <summary> 20 /// Constructor. 21 /// </summary> 22 /// <param name="rect">Position and size of control.</param> 23 public CustomButton() 24 { 25 myHandle = Handle; 26 27 // Add event handlers. 28 MouseDown += new System.Windows.Forms.MouseEventHandler(this.CustomButton_MouseDown); 29 this.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.CustomButton_KeyPress); 30 this.GotFocus += new EventHandler(CustomButton_ChangeFocus); 31 this.LostFocus += new EventHandler(CustomButton_ChangeFocus); 32 } 33 34 /// <summary> 35 /// Handles WM_GETOBJECT message; others are passed to base handler. 36 /// </summary> 37 /// <param name="m">Windows message.</param> 38 /// <remarks> 39 /// This method enables UI Automation to find the control. 40 /// </remarks> 41 [PermissionSetAttribute(SecurityAction.Demand, Unrestricted = true)] 42 protected override void WndProc(ref Message m) 43 { 44 const int WM_GETOBJECT = 0x003D; 45 46 if ((m.Msg == WM_GETOBJECT) && (m.LParam.ToInt32() == AutomationInteropProvider.RootObjectId)) 47 { 48 m.Result = AutomationInteropProvider.ReturnRawElementProvider( 49 Handle, m.WParam, m.LParam, (IRawElementProviderSimple)this); 50 return; 51 } 52 base.WndProc(ref m); 53 } 54 55 /// <summary> 56 /// Ensure that the focus rectangle is drawn or erased when focus changes. 57 /// </summary> 58 /// <param name="sender"></param> 59 /// <param name="e"></param> 60 void CustomButton_ChangeFocus(object sender, EventArgs e) 61 { 62 Refresh(); 63 } 64 65 66 /// <summary> 67 /// Handles Paint event. 68 /// </summary> 69 /// <param name="e">Event arguments.</param> 70 protected override void OnPaint(PaintEventArgs e) 71 { 72 Rectangle buttonRect = new Rectangle(ClientRectangle.Left + 2, 73 ClientRectangle.Top + 2, 74 ClientRectangle.Width - 4, 75 ClientRectangle.Height - 4); 76 System.Drawing.Drawing2D.HatchBrush brush; 77 if (buttonState) 78 { 79 brush = new System.Drawing.Drawing2D.HatchBrush( 80 System.Drawing.Drawing2D.HatchStyle.DarkHorizontal, Color.Red, Color.White); 81 } 82 else 83 { 84 brush = new System.Drawing.Drawing2D.HatchBrush( 85 System.Drawing.Drawing2D.HatchStyle.DarkVertical, Color.Green, Color.White); 86 } 87 88 e.Graphics.FillRectangle(brush, buttonRect); 89 if (Focused) 90 { 91 ControlPaint.DrawFocusRectangle(e.Graphics, ClientRectangle); 92 } 93 } 94 95 /// <summary> 96 /// Responds to a button click, regardless of whether it was caused by a mouse or 97 /// keyboard click or by InvokePattern.Invoke. 98 /// </summary> 99 private void RespondToClick() 100 { 101 buttonState = !buttonState; 102 this.Focus(); 103 this.Refresh(); 104 105 // Raise an event. 106 if (AutomationInteropProvider.ClientsAreListening) 107 { 108 AutomationEventArgs args = new AutomationEventArgs(InvokePatternIdentifiers.InvokedEvent); 109 AutomationInteropProvider.RaiseAutomationEvent(InvokePatternIdentifiers.InvokedEvent, this, args); 110 } 111 } 112 113 /// <summary> 114 /// Handles MouseDown event. 115 /// </summary> 116 /// <param name="sender">Object that raised the event.</param> 117 /// <param name="e">Event arguments.</param> 118 public void CustomButton_MouseDown(object sender, MouseEventArgs e) 119 { 120 RespondToClick(); 121 } 122 123 /// <summary> 124 /// Handles Keypress event. 125 /// </summary> 126 /// <param name="sender">Object that raised the event.</param> 127 /// <param name="e">Event arguments.</param> 128 public void CustomButton_KeyPress(object sender, KeyPressEventArgs e) 129 { 130 if (e.KeyChar == (char)Keys.Space) 131 { 132 RespondToClick(); 133 } 134 } 135 136 #region IRawElementProviderSimple 137 138 /// <summary> 139 /// Returns the object that supports the specified pattern. 140 /// </summary> 141 /// <param name="patternId">ID of the pattern.</param> 142 /// <returns>Object that implements IInvokeProvider.</returns> 143 object IRawElementProviderSimple.GetPatternProvider(int patternId) 144 { 145 if (patternId == InvokePatternIdentifiers.Pattern.Id) 146 { 147 return this; 148 } 149 else 150 { 151 return null; 152 } 153 } 154 155 /// <summary> 156 /// Returns property values. 157 /// </summary> 158 /// <param name="propId">Property identifier.</param> 159 /// <returns>Property value.</returns> 160 object IRawElementProviderSimple.GetPropertyValue(int propId) 161 { 162 if (propId == AutomationElementIdentifiers.ClassNameProperty.Id) 163 { 164 return "CustomButtonControlClass"; 165 } 166 else if (propId == AutomationElementIdentifiers.ControlTypeProperty.Id) 167 { 168 return ControlType.Button.Id; 169 } 170 if (propId == AutomationElementIdentifiers.HelpTextProperty.Id) 171 { 172 return "Change the button color and pattern."; 173 } 174 if (propId == AutomationElementIdentifiers.IsEnabledProperty.Id) 175 { 176 return true; 177 } 178 else 179 { 180 return null; 181 } 182 } 183 184 185 /// <summary> 186 /// Tells UI Automation that this control is hosted in an HWND, which has its own 187 /// provider. 188 /// </summary> 189 IRawElementProviderSimple IRawElementProviderSimple.HostRawElementProvider 190 { 191 get 192 { 193 return AutomationInteropProvider.HostProviderFromHandle(myHandle); 194 } 195 } 196 197 /// <summary> 198 /// Retrieves provider options. 199 /// </summary> 200 ProviderOptions IRawElementProviderSimple.ProviderOptions 201 { 202 get 203 { 204 return ProviderOptions.ServerSideProvider; 205 } 206 } 207 #endregion IRawElementProviderSimple 208 209 #region IInvokeProvider 210 211 /// <summary> 212 /// Responds to an InvokePattern.Invoke by simulating a MouseDown event. 213 /// </summary> 214 void IInvokeProvider.Invoke() 215 { 216 // If the control is not enabled, we're responsible for letting UI Automation know. 217 // It catches the exception and then throws it to the client. 218 IRawElementProviderSimple provider = this as IRawElementProviderSimple; 219 if (false == (bool)provider.GetPropertyValue(AutomationElementIdentifiers.IsEnabledProperty.Id)) 220 { 221 throw new ElementNotEnabledException(); 222 } 223 224 // Create arguments for the click event. The parameters aren't used. 225 MouseEventArgs mouseArgs = new MouseEventArgs(MouseButtons.Left, 1, 0, 0, 0); 226 227 // Simulate a mouse click. We cannot call RespondToClick directly, 228 // because it is illegal to update the UI from a different thread. 229 MouseEventHandler handler = CustomButton_MouseDown; 230 BeginInvoke(handler, new object[] { this, mouseArgs }); 231 } 232 233 #endregion InvokeProvider 234 235 236 } // CustomButton class. 237 } // Namespace.
图3 UISpy Co年trol view
参考
[3] 从 UI 自动化提供程序返回属性