added samples
[windows-sources.git] / sdk / samples / WPFSamples / InsertText / csharp / window1.xaml.cs
blob0dac11c2a87332b035918e42924861570b2f305f
1 /******************************************************************************
2 * File: Window1.xaml.cs
4 * Description:
5 * This sample opens a 'canned' Win32 application and shows how to use
6 * UI Automation to input text depending on the text control.
7 *
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:
16 * Automation Class
17 * AddAutomationEventHandler
18 * AddAutomationPropertyChangedEventHandler
19 * Condition Class
20 * AndCondition Class
21 * OrCondition Class
22 * AutomationElementCollection Class
23 * AutomationPattern Class
24 * AutomationElement Class
25 * IsTextPatternAvailableProperty field
26 * TryGetCurrentPattern method
27 * FromHandle method
28 * ControlTypeProperty field
29 * FindAll method
30 * SetFocus method
31 * TreeScope Enumeration
32 * Element member
33 * Descendants member
34 * ControlType Class
35 * Text field
36 * Edit field
37 * Document field
38 * TextPattern Class
39 * Pattern field
40 * ValuePattern Class
41 * Pattern field
42 * SetValue method
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
57 * PARTICULAR PURPOSE.
59 *****************************************************************************/
61 using System;
62 using System.Windows;
63 using System.Diagnostics;
64 using System.Windows.Automation;
65 using System.Threading;
66 using System.IO;
67 using System.Text;
68 using System.Windows.Forms;
69 using System.ComponentModel;
70 using System.Windows.Threading;
72 namespace InsertTextClient
74 ///------------------------------------------------------------------------
75 /// <summary>
76 /// Interaction logic for Window1.xaml
77 /// </summary>
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 ///--------------------------------------------------------------------
97 /// <summary>
98 /// The class constructor.
99 /// </summary>
100 // Initialize both client and target applications.
101 ///--------------------------------------------------------------------
102 public Window1()
104 InitializeComponent();
107 ///--------------------------------------------------------------------
108 /// <summary>
109 /// Handles the click event for the Start App button.
110 /// </summary>
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)
120 return;
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)
129 as TransformPattern;
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");
147 return;
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,
172 targetWindow,
173 TreeScope.Element,
174 targetClosedHandler);
176 SetClientControlProperties(false, true);
178 // Get our collection of interesting controls.
179 textControls = FindTextControlsInTarget();
182 ///--------------------------------------------------------------------
183 /// <summary>
184 /// Handles the click event for the Insert Text buttons.
185 /// </summary>
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.");
195 return;
197 switch (((System.Windows.Controls.Button) sender).Content.ToString())
199 case "UIAutomation":
200 SetValueWithUIAutomation(tbInsert.Text);
201 break;
202 case "Win32":
203 SetValueWithWin32(tbInsert.Text);
204 break;
205 default:
206 Feedback("Insert failed.");
207 return;
211 ///--------------------------------------------------------------------
212 /// <summary>
213 /// Sets the values of the text controls using managed methods.
214 /// </summary>
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 ///--------------------------------------------------------------------
226 /// <summary>
227 /// Inserts a string into each text control of interest.
228 /// </summary>
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,
233 string value)
237 // Validate arguments / initial setup
238 if (value == null)
239 throw new ArgumentNullException(
240 "String parameter must not be null.");
242 if (element == 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.
295 element.SetFocus();
297 // Pause before sending keyboard input.
298 Thread.Sleep(100);
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.
308 else
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.
316 element.SetFocus();
318 ((ValuePattern)valuePattern).SetValue(value);
321 catch (ArgumentNullException exc)
323 feedbackText.Append(exc.Message);
325 catch (InvalidOperationException exc)
327 feedbackText.Append(exc.Message);
329 finally
331 Feedback(feedbackText.ToString());
336 ///--------------------------------------------------------------------
337 /// <summary>
338 /// Sets the values of the text controls using unmanaged methods.
339 /// </summary>
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 ///--------------------------------------------------------------------
355 /// <summary>
356 /// Inserts the specified string into a text control.
357 /// </summary>
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
366 if (value == null)
367 throw new ArgumentNullException(
368 "String parameter 'value' must not be null.");
370 if (element == null)
371 throw new ArgumentNullException(
372 "AutomationElement parameter 'element' must not be null");
374 // Get hwnd
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() +
407 " is read-only.");
410 // Check #3: Is the size of the text we want to set
411 // greater than what the control accepts?
412 IntPtr result;
413 int resultInt;
415 IntPtr resultSendMessage = UnmanagedClass.SendMessageTimeout(
416 hwnd,
417 UnmanagedClass.EM_GETLIMITTEXT,
418 IntPtr.Zero,
419 IntPtr.Zero,
421 10000,
422 out result);
423 int lastWin32Error =
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 ("
438 + value.Length
439 + ") is greater than the upper limit of the "
440 + "control with an AutomationID of "
441 + element.Current.AutomationId.ToString()
442 + " (" + resultInt.ToString() + ")"
443 + ".");
446 // Send the message...!
447 result = UnmanagedClass.SendMessageTimeout(
448 hwnd,
449 UnmanagedClass.WM_SETTEXT,
450 IntPtr.Zero,
451 new StringBuilder(value),
453 10000,
454 out result);
455 resultInt = unchecked((int)(long)result);
456 if (resultInt != 1)
458 throw new InvalidOperationException(
459 "The text of the control with an AutomationID of "
460 + element.Current.AutomationId.ToString() +
461 " cannot be changed.");
463 feedbackText.Append(
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);
480 finally
482 Feedback(feedbackText.ToString());
486 ///--------------------------------------------------------------------
487 /// <summary>
488 /// Gets a pointer to an AutomationElement
489 /// </summary>
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;
504 else
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());
516 return ptr;
519 ///--------------------------------------------------------------------
520 /// <summary>
521 /// Is bit set?
522 /// </summary>
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 ///--------------------------------------------------------------------
532 /// <summary>
533 /// Finds the text controls of interest.
534 /// </summary>
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(
542 new OrCondition(
543 new PropertyCondition(
544 AutomationElement.ControlTypeProperty,
545 ControlType.Edit),
546 new PropertyCondition(
547 AutomationElement.ControlTypeProperty,
548 ControlType.Document),
549 new PropertyCondition(
550 AutomationElement.ControlTypeProperty,
551 ControlType.Text)),
552 new PropertyCondition(
553 AutomationElement.IsTextPatternAvailableProperty,
554 true)
556 return targetWindow.FindAll(TreeScope.Descendants, condition);
559 ///--------------------------------------------------------------------
560 /// <summary>
561 /// Starts the target app that contains the text controls of interest.
562 /// </summary>
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());
573 return null;
575 Process p;
578 // Start application.
579 p = Process.Start(target);
580 if (p == null)
581 return null;
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.
588 Thread.Sleep(2500);
590 // Return the automation element
591 return AutomationElement.FromHandle(p.MainWindowHandle);
593 catch (Win32Exception e)
595 Feedback(e.ToString());
596 return null;
600 ///--------------------------------------------------------------------
601 /// <summary>
602 /// Intercepts the target window closed event and starts the client
603 /// window BackgroundWorker object.
604 /// </summary>
605 /// <param name="sender">The object that raised the event.</param>
606 /// <param name="e">Event arguments.</param>
607 /// <remarks>
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
611 /// UI thread.
612 /// </remarks>
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),
620 true, false);
621 txtFeedback.Dispatcher.BeginInvoke(
622 DispatcherPriority.Send,
623 new FeedbackDelegate(Feedback),
624 "Target closed.");
627 ///--------------------------------------------------------------------
628 /// <summary>
629 /// Sets attributes of the controls in the client app.
630 /// </summary>
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 ///--------------------------------------------------------------------
642 /// <summary>
643 /// Outputs information to the client window.
644 /// </summary>
645 /// <param name="s">The string to display.</param>
646 ///--------------------------------------------------------------------
647 private void Feedback(string s)
649 txtFeedback.Text = s;