-->

If I programmatically activate a window in another

2020-03-30 02:30发布

问题:

Bear with me, I'm a web developer attempting a WinForms application.

When responding please do not comment on whether the purpose of my application is a "best practice" or not, etc... This is the application I need to write, for better or worse, and I cannot waste time explaining the "why". I ask that you please just reply with advice on (1) Is what I'm trying to do possible (2) What is the "best" way to accomplish what I'm trying to do.

So, here's what I'm trying to do: I need to write a WinForms application that will read data from a vendor application, display certain records to the end user and allow them to choose to "transfer" a record to another vendor application. The hang up...the destination system exposes no apis, no sprocs, provides no "good" method for inserting data, other than typing it into their GUI. (trust me on this, a team of 3 people investigated EVERY possibility for automating this)

So this WinForms app will utilize Microsoft.VisualBasic.Interaction.AppActivate(string) and insert data directly into the fields in the input window of the destination system. The destination system has F1 help on certain fields, so if you give focus to the field and press "F1", or alternatively, if you enter invalid data in one of these fields, a child window named "Help" pops up to offer guidance.

So, my app is plugging along, using SendKeys.SendWait(string) to write data into the fields of this application and then some invalid data is encountered and the Help window pops up, and my app continues to write out the rest of the data, but now it's all being written to the 1 field in Help window because that window now has focus.

The million dollar question...

Is there a way to detect, from my WinForms app, that the Help window has been activated in the destination system so that I can work around it?


Updates

First attempt at this application I used the UIAutomationClient library. I was unable to successfully activate the target window and write to the first field. After a day fighting with it I had to start looking for alternatives.

回答1:

I have the start of some working code. I think I have enough that this may be useful to someone else.

I found a way to use the UIAutomation library instead of the Microsoft.VisualBasic library. There was a lot of trial and error, but this is much "slicker" than my first few attempts using the VisualBasic library.

I used the StructureChanged event to detect the Help window popping up in a previous iteration of this proof of concept app. I was able to successfully detect that the Help window popped up, but I was running into snags trying to handle the appearance of the Help window. I still have the event handler in my application, but for some reason the Help window does not pop up now, I tried forcing invalid data into one of the fields to test the handling of the Help window, and the Help window did not appear. This is not an issue for me, since the Help window was causing trouble for me, but I felt that it needed to be mentioned since this post was asking how I could detect the Help window's appearance.

Some resources I used:

http://msdn.microsoft.com/en-us/library/ms747327%28v=vs.110%29.aspx

https://www.universalthread.com/ViewPageArticle.aspx?ID=199

http://msdn.microsoft.com/en-us/library/ms750582%28v=vs.110%29.aspx

http://msdn.microsoft.com/en-us/library/windows/desktop/dd318521%28v=vs.85%29.aspx

private void IncidentGridView_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
  if ((e.RowIndex < 0) || (e.ColumnIndex < 0))
    return;

  // Grab the data object that populated the row
  Incident incident = (Incident)IncidentGridView.Rows[e.RowIndex].DataBoundItem;

  // Create a property condition with the element's type
  PropertyCondition typeCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window);
  // Create a property condition with the element's name
  PropertyCondition nameCondition = new PropertyCondition(AutomationElement.NameProperty, "Incident");
  // Create the conjunction condition
  AndCondition andCondition = new AndCondition(typeCondition, nameCondition);
  // Ask the Desktop to find the element within its children with the given condition
  _mainWindow = AutomationElement.RootElement.FindFirst(TreeScope.Children, andCondition);

  #region Register Automation Events
  //AutomationEventHandler handler = new AutomationEventHandler(OnWindowOpened);
  //Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, _mainWindow, TreeScope.Element, handler);
  Automation.AddStructureChangedEventHandler(_mainWindow, TreeScope.Children, new StructureChangedEventHandler(OnStructureChanged));
  #endregion

  // Wait for the application
  Thread.Sleep(2000);

  // Write the incident to the Incident window
  PropertyCondition idCondition = new PropertyCondition(AutomationElement.AutomationIdProperty, "3279");
  AutomationElement reportedDate = _mainWindow.FindFirst(TreeScope.Descendants, idCondition);
  InsertTextUsingUIAutomation(reportedDate, incident.ReportedDate);

  PropertyCondition idCondition2 = new PropertyCondition(AutomationElement.AutomationIdProperty, "4256");
  AutomationElement reportedTime = _mainWindow.FindFirst(TreeScope.Descendants, idCondition2);
  InsertTextUsingUIAutomation(reportedTime, incident.ReportedTime);

  PropertyCondition idCondition4 = new PropertyCondition(AutomationElement.AutomationIdProperty, "7256");
  AutomationElement status = _mainWindow.FindFirst(TreeScope.Descendants, idCondition4);
  InsertTextUsingUIAutomation(status, "WAR");

  PropertyCondition idCondition5 = new PropertyCondition(AutomationElement.AutomationIdProperty, "5404");
  AutomationElement natureOfCall = _mainWindow.FindFirst(TreeScope.Descendants, idCondition5);
  InsertTextUsingUIAutomation(natureOfCall, "TRAF WARN");

  PropertyCondition idCondition11 = new PropertyCondition(AutomationElement.AutomationIdProperty, "4935");
  AutomationElement location = _mainWindow.FindFirst(TreeScope.Descendants, idCondition11);
  InsertTextUsingUIAutomation(location, incident.Location);

  PropertyCondition idCondition12 = new PropertyCondition(AutomationElement.AutomationIdProperty, "2876");
  AutomationElement city = _mainWindow.FindFirst(TreeScope.Descendants, idCondition12);
  InsertTextUsingUIAutomation(city, incident.City);

  PropertyCondition idCondition15 = new PropertyCondition(AutomationElement.AutomationIdProperty, "5693");
  AutomationElement officer = _mainWindow.FindFirst(TreeScope.Descendants, idCondition15);
  InsertTextUsingUIAutomation(officer, incident.Officer);

  PropertyCondition idCondition19 = new PropertyCondition(AutomationElement.AutomationIdProperty, "4023");
  AutomationElement fromDate = _mainWindow.FindFirst(TreeScope.Descendants, idCondition19);
  InsertTextUsingUIAutomation(fromDate, incident.FromDate);

  PropertyCondition idCondition20 = new PropertyCondition(AutomationElement.AutomationIdProperty, "4042");
  AutomationElement fromTime = _mainWindow.FindFirst(TreeScope.Descendants, idCondition20);
  InsertTextUsingUIAutomation(fromTime, incident.FromTime);

  PropertyCondition idCondition21 = new PropertyCondition(AutomationElement.AutomationIdProperty, "7556");
  AutomationElement toDate = _mainWindow.FindFirst(TreeScope.Descendants, idCondition21);
  InsertTextUsingUIAutomation(toDate, incident.ToDate);

  PropertyCondition idCondition22 = new PropertyCondition(AutomationElement.AutomationIdProperty, "7576");
  AutomationElement toTime = _mainWindow.FindFirst(TreeScope.Descendants, idCondition22);
  InsertTextUsingUIAutomation(toTime, incident.ToTime);

  PropertyCondition idCondition30 = new PropertyCondition(AutomationElement.AutomationIdProperty, "2001");
  AutomationElement remarks = _mainWindow.FindFirst(TreeScope.Descendants, idCondition30);
  InsertTextUsingUIAutomation(remarks, incident.Remarks);


  MessageBox.Show("Incident was transferred.");
}

Here's the event handler for the StructureChangedEvent:

 private void OnStructureChanged(object sender, StructureChangedEventArgs e)
{
  AutomationElement element = sender as AutomationElement;

  if (e.StructureChangeType == StructureChangeType.ChildAdded)
  {
    Object windowPattern;
    if (false == element.TryGetCurrentPattern(WindowPattern.Pattern, out windowPattern))
      return;

    if (element.Current.Name == "Help")
    {
      // How do we want to handle this???
      MessageBox.Show("Waiting 30 seconds to allow user to resolve data issue.");
      Thread.Sleep(30000);
    }
  }
}