1 /******************************************************************************
2 * File: Window1.xaml.cs
5 * This sample opens a 'canned' Win32 application and shows how to use
6 * UI Automation to input text depending on the text control.
8 * InsertTextTarget.exe should be automatically copied to the InsertText
9 * client folder when you build the sample. You may have to manually copy
10 * this file if you receive an error stating the file cannot be found.
12 * Programming Elements:
13 * The sample demonstrates the following UI Automation programming elements.
15 * System.Windows.Automation Namespace:
17 * AddAutomationEventHandler
18 * AddAutomationPropertyChangedEventHandler
22 * AutomationElementCollection Class
23 * AutomationPattern Class
24 * AutomationElement Class
25 * IsTextPatternAvailableProperty field
26 * TryGetCurrentPattern method
28 * ControlTypeProperty field
31 * TreeScope Enumeration
43 * AutomationPropertyChangedEventHandler Delegate
46 * This file is part of the Microsoft .NET Framework SDK Code Samples.
48 * Copyright (C) Microsoft Corporation. All rights reserved.
50 * This source code is intended only as a supplement to Microsoft
51 * Development Tools and/or on-line documentation. See these other
52 * materials for detailed information regarding Microsoft code samples.
54 * THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
55 * KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
56 * IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
59 *****************************************************************************/
63 using System
.Diagnostics
;
64 using System
.Windows
.Automation
;
65 using System
.Threading
;
68 using System
.Windows
.Forms
;
69 using System
.ComponentModel
;
70 using System
.Windows
.Threading
;
72 namespace InsertTextClient
74 ///------------------------------------------------------------------------
76 /// Interaction logic for Window1.xaml
78 ///------------------------------------------------------------------------
79 public partial class Window1
: Window
81 private AutomationElement targetWindow
;
82 private AutomationElementCollection textControls
;
83 // InsertTextTarget.exe should be automatically copied to the
84 // InsertText client folder when you build the sample.
85 // You may have to manually copy this file if you receive an error
86 // stating the file cannot be found.
87 private readonly string filePath
=
88 System
.Windows
.Forms
.Application
.StartupPath
89 + "\\InsertTextTarget.exe";
90 private StringBuilder feedbackText
;
92 // Delegates to be used in placing jobs onto the Dispatcher.
93 private delegate void ControlsDelegate(bool arg1
, bool arg2
);
94 private delegate void FeedbackDelegate(string arg1
);
96 ///--------------------------------------------------------------------
98 /// The class constructor.
100 // Initialize both client and target applications.
101 ///--------------------------------------------------------------------
104 InitializeComponent();
107 ///--------------------------------------------------------------------
109 /// Handles the click event for the Start App button.
111 /// <param name="sender">The object that raised the event.</param>
112 /// <param name="e">Event arguments.</param>
113 ///--------------------------------------------------------------------
114 private void btnStartApp_Click(object sender
, RoutedEventArgs e
)
116 targetWindow
= StartTargetApp(filePath
);
118 if (targetWindow
== null)
123 Feedback("Target started.");
125 double clientLocationTop
= Client
.Top
;
126 double clientLocationRight
= Client
.Left
+ Client
.Width
+ 100;
127 TransformPattern transformPattern
=
128 targetWindow
.GetCurrentPattern(TransformPattern
.Pattern
)
130 if (transformPattern
!= null)
132 transformPattern
.Move(clientLocationRight
, clientLocationTop
);
135 // Get required control patterns
137 // Once you have an instance of an AutomationElement for the target
138 // obtain a WindowPattern object to handle the WindowClosed event.
141 WindowPattern windowPattern
=
142 targetWindow
.GetCurrentPattern(WindowPattern
.Pattern
) as WindowPattern
;
144 catch (InvalidOperationException
)
146 Feedback("Object does not support the Window Pattern");
150 // Register for an Event
152 // The WindowPattern allows you to programmatically
153 // manipulate a window.
154 // It also exposes a window closed event.
155 // The following code shows an example of listening
156 // for the WindowClosed event.
158 // To intercept the WindowClosed event for our target application
159 // you define an AutomationEventHandler delegate.
160 AutomationEventHandler targetClosedHandler
=
161 new AutomationEventHandler(OnTargetClosed
);
163 // Use AddAutomationEventHandler() to add this event handler.
164 // When listening for a WindowClosed event you must either scope
165 // the event to the automation element as done here, or cast
166 // the AutomationEventArgs in the handler to WindowClosedEventArgs
167 // and compare the RuntimeId of the automation element that raised
168 // the WindowClosed event to the automation element in the
169 // class member data.
170 Automation
.AddAutomationEventHandler(
171 WindowPattern
.WindowClosedEvent
,
174 targetClosedHandler
);
176 SetClientControlProperties(false, true);
178 // Get our collection of interesting controls.
179 textControls
= FindTextControlsInTarget();
182 ///--------------------------------------------------------------------
184 /// Handles the click event for the Insert Text buttons.
186 /// <param name="sender">The object that raised the event.</param>
187 /// <param name="e">Event arguments.</param>
188 ///--------------------------------------------------------------------
189 private void btnInsert_Click(object sender
, RoutedEventArgs e
)
191 feedbackText
= new StringBuilder();
192 if (string.IsNullOrEmpty(tbInsert
.Text
))
194 Feedback("Please enter some text to insert.");
197 switch (((System
.Windows
.Controls
.Button
) sender
).Content
.ToString())
200 SetValueWithUIAutomation(tbInsert
.Text
);
203 SetValueWithWin32(tbInsert
.Text
);
206 Feedback("Insert failed.");
211 ///--------------------------------------------------------------------
213 /// Sets the values of the text controls using managed methods.
215 /// <param name="s">The string to be inserted.</param>
216 ///--------------------------------------------------------------------
217 private void SetValueWithUIAutomation(string s
)
219 foreach (AutomationElement control
in textControls
)
221 InsertTextUsingUIAutomation(control
, s
);
225 ///--------------------------------------------------------------------
227 /// Inserts a string into each text control of interest.
229 /// <param name="element">A text control.</param>
230 /// <param name="value">The string to be inserted.</param>
231 ///--------------------------------------------------------------------
232 private void InsertTextUsingUIAutomation(AutomationElement element
,
237 // Validate arguments / initial setup
239 throw new ArgumentNullException(
240 "String parameter must not be null.");
243 throw new ArgumentNullException(
244 "AutomationElement parameter must not be null");
246 // A series of basic checks prior to attempting an insertion.
248 // Check #1: Is control enabled?
249 // An alternative to testing for static or read-only controls
250 // is to filter using
251 // PropertyCondition(AutomationElement.IsEnabledProperty, true)
252 // and exclude all read-only text controls from the collection.
253 if (!element
.Current
.IsEnabled
)
255 throw new InvalidOperationException(
256 "The control with an AutomationID of "
257 + element
.Current
.AutomationId
.ToString()
258 + " is not enabled.\n\n");
261 // Check #2: Are there styles that prohibit us
262 // from sending text to this control?
263 if (!element
.Current
.IsKeyboardFocusable
)
265 throw new InvalidOperationException(
266 "The control with an AutomationID of "
267 + element
.Current
.AutomationId
.ToString()
268 + "is read-only.\n\n");
272 // Once you have an instance of an AutomationElement,
273 // check if it supports the ValuePattern pattern.
274 object valuePattern
= null;
276 // Control does not support the ValuePattern pattern
277 // so use keyboard input to insert content.
279 // NOTE: Elements that support TextPattern
280 // do not support ValuePattern and TextPattern
281 // does not support setting the text of
282 // multi-line edit or document controls.
283 // For this reason, text input must be simulated
284 // using one of the following methods.
286 if (!element
.TryGetCurrentPattern(
287 ValuePattern
.Pattern
, out valuePattern
))
289 feedbackText
.Append("The control with an AutomationID of ")
290 .Append(element
.Current
.AutomationId
.ToString())
291 .Append(" does not support ValuePattern.")
292 .AppendLine(" Using keyboard input.\n");
294 // Set focus for input functionality and begin.
297 // Pause before sending keyboard input.
300 // Delete existing content in the control and insert new content.
301 SendKeys
.SendWait("^{HOME}"); // Move to start of control
302 SendKeys
.SendWait("^+{END}"); // Select everything
303 SendKeys
.SendWait("{DEL}"); // Delete selection
304 SendKeys
.SendWait(value);
306 // Control supports the ValuePattern pattern so we can
307 // use the SetValue method to insert content.
310 feedbackText
.Append("The control with an AutomationID of ")
311 .Append(element
.Current
.AutomationId
.ToString())
312 .Append((" supports ValuePattern."))
313 .AppendLine(" Using ValuePattern.SetValue().\n");
315 // Set focus for input functionality and begin.
318 ((ValuePattern
)valuePattern
).SetValue(value);
321 catch (ArgumentNullException exc
)
323 feedbackText
.Append(exc
.Message
);
325 catch (InvalidOperationException exc
)
327 feedbackText
.Append(exc
.Message
);
331 Feedback(feedbackText
.ToString());
336 ///--------------------------------------------------------------------
338 /// Sets the values of the text controls using unmanaged methods.
340 /// <param name="s">The string to be inserted.</param>
341 ///--------------------------------------------------------------------
342 private void SetValueWithWin32(string s
)
344 foreach (AutomationElement control
in textControls
)
346 // An alternative to testing for static or read-only controls
347 // is to filter using
348 // PropertyCondition(AutomationElement.IsEnabledProperty, true)
349 // and exclude all read-only text controls from the collection.
350 InsertTextUsingWin32(control
, s
);
354 ///--------------------------------------------------------------------
356 /// Inserts the specified string into a text control.
358 /// <param name="element">A text control.</param>
359 /// <param name="value">The string to be inserted.</param>
360 ///--------------------------------------------------------------------
361 private void InsertTextUsingWin32(AutomationElement element
, string value)
365 // Validate arguments / initial setup
367 throw new ArgumentNullException(
368 "String parameter 'value' must not be null.");
371 throw new ArgumentNullException(
372 "AutomationElement parameter 'element' must not be null");
375 IntPtr _hwnd
= new IntPtr(element
.Current
.NativeWindowHandle
);
376 if (_hwnd
== IntPtr
.Zero
)
377 throw new InvalidOperationException(
378 "Unable to get handle to object.");
380 // A series of basic checks for the text control
381 // prior to attempting an insertion.
383 // Check #1: Is control enabled?
384 // An alternative to testing for static or read-only controls
385 // is to filter using
386 // PropertyCondition(AutomationElement.IsEnabledProperty, true)
387 // and exclude all read-only text controls from the collection.
388 if (!UnmanagedClass
.IsWindowEnabled(_hwnd
))
390 throw new InvalidOperationException(
391 "The control with an AutomationID of "
392 + element
.Current
.AutomationId
.ToString()
393 + " is not enabled.\n");
396 // Check #2: Are there styles that prohibit us from
397 // sending text to this control?
398 UnmanagedClass
.HWND hwnd
= UnmanagedClass
.HWND
.Cast(_hwnd
);
399 int ControlStyle
= UnmanagedClass
.GetWindowLong(hwnd
,
400 UnmanagedClass
.GCL_STYLE
);
402 if (IsBitSet(ControlStyle
, UnmanagedClass
.ES_READONLY
))
404 throw new InvalidOperationException(
405 "The control with an AutomationID of "
406 + element
.Current
.AutomationId
.ToString() +
410 // Check #3: Is the size of the text we want to set
411 // greater than what the control accepts?
415 IntPtr resultSendMessage
= UnmanagedClass
.SendMessageTimeout(
417 UnmanagedClass
.EM_GETLIMITTEXT
,
424 System
.Runtime
.InteropServices
.Marshal
.GetLastWin32Error();
426 if (resultSendMessage
== IntPtr
.Zero
)
428 throw new InvalidOperationException(
429 "SendMessageTimeout() timed out.");
431 resultInt
= unchecked((int)(long)result
);
433 // A result of -1 means that no limit is set.
434 if (resultInt
!= -1 && resultInt
< value.Length
)
436 throw new InvalidOperationException(
437 "The length of text entered ("
439 + ") is greater than the upper limit of the "
440 + "control with an AutomationID of "
441 + element
.Current
.AutomationId
.ToString()
442 + " (" + resultInt
.ToString() + ")"
446 // Send the message...!
447 result
= UnmanagedClass
.SendMessageTimeout(
449 UnmanagedClass
.WM_SETTEXT
,
451 new StringBuilder(value),
455 resultInt
= unchecked((int)(long)result
);
458 throw new InvalidOperationException(
459 "The text of the control with an AutomationID of "
460 + element
.Current
.AutomationId
.ToString() +
461 " cannot be changed.");
464 "The text of the control with an AutomationID of ")
465 .Append(element
.Current
.AutomationId
.ToString())
466 .AppendLine(" has been set.\n");
468 catch (EntryPointNotFoundException exc
)
470 feedbackText
.AppendLine(exc
.Message
);
472 catch (ArgumentNullException exc
)
474 feedbackText
.AppendLine(exc
.Message
);
476 catch (InvalidOperationException exc
)
478 feedbackText
.AppendLine(exc
.Message
);
482 Feedback(feedbackText
.ToString());
486 ///--------------------------------------------------------------------
488 /// Gets a pointer to an AutomationElement
490 /// <param name="element">A text control.</param>
491 /// <returns>A pointer to an AutomationElement</returns>
492 ///--------------------------------------------------------------------
493 private IntPtr GetWindowHandleFromAutomationElement
494 (AutomationElement element
)
496 IntPtr ptr
= IntPtr
.Zero
;
499 object objHwnd
= element
.GetCurrentPropertyValue(
500 AutomationElement
.NativeWindowHandleProperty
);
502 if (objHwnd
is IntPtr
)
503 ptr
= (IntPtr
)objHwnd
;
505 ptr
= new IntPtr(Convert
.ToInt64(objHwnd
));
507 if (ptr
== IntPtr
.Zero
)
508 throw new InvalidOperationException
509 ("Unable to get a handle for the element with an AutomationID of "
510 + element
.Current
.AutomationId
.ToString() + ".");
512 catch (InvalidOperationException exc
)
514 Feedback(exc
.Message
.ToString());
519 ///--------------------------------------------------------------------
523 /// <param name="flags">The flag(s) of interest.</param>
524 /// <param name="bit">The bit value(s).</param>
525 ///--------------------------------------------------------------------
526 private bool IsBitSet(int flags
, int bit
)
528 return (flags
& bit
) == bit
;
531 ///--------------------------------------------------------------------
533 /// Finds the text controls of interest.
535 /// <returns>A collection of Automation Elements
536 /// that satisfy the specified conditions.</returns>
537 ///--------------------------------------------------------------------
538 // Find all 'text' controls that support TextPattern.
539 private AutomationElementCollection
FindTextControlsInTarget()
541 AndCondition condition
= new AndCondition(
543 new PropertyCondition(
544 AutomationElement
.ControlTypeProperty
,
546 new PropertyCondition(
547 AutomationElement
.ControlTypeProperty
,
548 ControlType
.Document
),
549 new PropertyCondition(
550 AutomationElement
.ControlTypeProperty
,
552 new PropertyCondition(
553 AutomationElement
.IsTextPatternAvailableProperty
,
556 return targetWindow
.FindAll(TreeScope
.Descendants
, condition
);
559 ///--------------------------------------------------------------------
561 /// Starts the target app that contains the text controls of interest.
563 /// <param name="sTarget">The target exe.</param>
564 /// <returns>An Automation Element from our target window.</returns>
565 ///--------------------------------------------------------------------
566 // Start our target Win32 application
567 private AutomationElement
StartTargetApp(string target
)
569 if (!File
.Exists(target
))
571 feedbackText
.Append(target
).Append(" not found.");
572 Feedback(feedbackText
.ToString());
578 // Start application.
579 p
= Process
.Start(target
);
583 // Give the target application some time to startup.
584 // For Win32 applications, WaitForInputIdle can be used instead.
585 // Another alternative is to listen for WindowOpened events.
586 // Otherwise, an ArgumentException results when you try to
587 // retrieve an automation element from the window handle.
590 // Return the automation element
591 return AutomationElement
.FromHandle(p
.MainWindowHandle
);
593 catch (Win32Exception e
)
595 Feedback(e
.ToString());
600 ///--------------------------------------------------------------------
602 /// Intercepts the target window closed event and starts the client
603 /// window BackgroundWorker object.
605 /// <param name="sender">The object that raised the event.</param>
606 /// <param name="e">Event arguments.</param>
608 /// It is not advisable to operate on your own UI within an
609 /// event handler. This is especially true in a multi-threaded
610 /// environment as the event handler is unlikely to be called on the
613 ///--------------------------------------------------------------------
614 private void OnTargetClosed(object sender
, AutomationEventArgs e
)
616 // Schedule the update function in the UI thread.
617 spInsert
.Dispatcher
.BeginInvoke(
618 DispatcherPriority
.Send
,
619 new ControlsDelegate(SetClientControlProperties
),
621 txtFeedback
.Dispatcher
.BeginInvoke(
622 DispatcherPriority
.Send
,
623 new FeedbackDelegate(Feedback
),
627 ///--------------------------------------------------------------------
629 /// Sets attributes of the controls in the client app.
631 /// <param name="e">Enabled property.</param>
632 /// <param name="v">Visibility property.</param>
633 ///--------------------------------------------------------------------
634 private void SetClientControlProperties(bool e1
, bool e2
)
636 btnStartApp
.IsEnabled
= e1
;
637 tbkInsert
.IsEnabled
= e2
;
638 spInsert
.IsEnabled
= e2
;
641 ///--------------------------------------------------------------------
643 /// Outputs information to the client window.
645 /// <param name="s">The string to display.</param>
646 ///--------------------------------------------------------------------
647 private void Feedback(string s
)
649 txtFeedback
.Text
= s
;