Patrick Quirk
Patrick Quirk

Reputation: 23747

Using UI Automation, Winforms Button Invoking multiple times

I'm attempting to use the MS UI Automation Framework in my application for some automated testing. My goal for now is to listen to GUI events and record them, similar to the sample provided (TestScriptGeneratorSample) in the Windows SDK.

Just using that demo application, I can launch a simple Windows Forms application with a single button and see that it records click events as "invoke" UIA events. However, for each click of the button, the demo application records 4 "invoke" events. Why is that? I don't see this issue when using a WPF button.

My guess is that since the System.Windows.Forms.Button class supports MSAA instead of the UIA, the part of UIA that is bridging the MSAA interface is misbehaving, or at least behaving in a way that I don't understand and can't find any documentation about. Maybe it's reporting an invoke event for mouse down, up, click, and then the actual button press?

Can anyone explain this behavior, and/or provide a workaround so that one button press results in one invoke event?

Edit: This is under WinXP SP3. I just installed the Windows Automation API 3.0 update and still see the same behavior.

Edit 2: I found this example where the author off-handedly mentions this behavior as a bug in the Win32 control, but cites no evidence...

Here's my sample application (form with a button on it), along with event listening baked into it. Add references to UIAutomationClient and UIAutomationTypes. Click the button and see invoke happen four times instead of one.

using System;
using System.Drawing;
using System.Windows.Automation;
using System.Windows.Forms;

namespace WindowsFormsApplication6
{
    public partial class Form1 : Form
    {
        private TextBox m_Output;

        public Form1()
        {
            InitializeComponent();

            // set up the GUI with a single button...
            Button b = new Button {Location = new Point(5, 5), Name = "Test", Text = "Click Me"};
            // ... and a textbox to write events to.
            m_Output = new TextBox { Location = new Point(5, 30), Name = "Output", Multiline = true, Size = new Size(250, 200) };

            this.Controls.Add(b);
            this.Controls.Add(m_Output);

            // get the button as an UIA element
            AutomationElement button = AutomationElement.FromHandle(b.Handle);
            // attach a handler to the button, listening for the Invoke event
            Automation.AddAutomationEventHandler(
                                                 InvokePattern.InvokedEvent,
                                                 button,
                                                 TreeScope.Element,
                                                 OnInvoked);
        }

        // Handler for button invoke events
        private void OnInvoked(object Sender, AutomationEventArgs E)
        {
            AppendText("Invoked: " + ((AutomationElement)Sender).Current.AutomationId);
        }

        // Just dumps text to the textbox
        private void AppendText(string Text)
        {
            if (m_Output.InvokeRequired)
            {
                m_Output.BeginInvoke((Action<string>)AppendText, Text);
            }
            else
            {
                m_Output.AppendText(DateTime.Now.ToString("hh:mm:ss.fff") + ": " + Text + Environment.NewLine);
            }
        }
    }
}

Upvotes: 4

Views: 3948

Answers (1)

Patrick Quirk
Patrick Quirk

Reputation: 23747

For what it's worth, I've worked around it by filtering the multiple events being issued into one. During testing I found the following:

  • .NET buttons (i.e. Winforms) generate the following events in order when clicked:
    1. Invoked
    2. Invoked
    3. Invoked
    4. Property Changed (the Name property)
    5. Invoked
  • Win32 buttons generate the following events in some scenarios (buttons in calc.exe):
    1. Property Changed (the HasKeyboardFocus property)
    2. Invoked
  • Win32 buttons generate the following events in other scenarios ("Cancel" in a save file dialog):
    1. Invoked
    2. Property Changed (the HasKeyboardFocus property)
    3. Invoked

Using the FrameworkId property on the AutomationElement that is associated with the event, I can distinguish between the first and second two situations (it's "Winform" for .NET buttons and "Win32" for Win32 buttons). Then for the two Win32 scenarios I just ensure I get a HasKeyboardFocus property change event before recording the invoked event.

I haven't seen this not work yet since I always seem to get the HasKeyboardFocus property change event, even if the button already was focused (i.e. I press it twice).

I'd still like to see some more insight if anyone has some...

Upvotes: 0

Related Questions