Переключение между вариантами конструкции с помощью UI Automation
Переключение между вариантами конструкции с помощью UI Automation
Рассмотрим приложение, демонстрирующее функциональность по определению, перечислению и установки варианта конструкции с помощью самостоятельного отдельного приложения.
Приложение было создано пользователем Revitalizer. Вот его комментарии:
Вот небольшая утилита по переключению между вариантами конструкции.
Это отдельное приложение, которое извлекает и устанавливает Вариант конструкции.
Так как оно работает очень, очень медленно, оно не очень полезно для ежедневного профессионального использования.
Приложение просто считывает и заполняет список вариантов конструкции, но чтение занимает довольно продолжительное время.
Для тестирования приложения откройте проект Revit, в котором содержится несколько вариантов конструкций. Затем запустите приложение. При этом фактически вы будете управлять Revit вне контекста процесса Revit.
Тестовый запуск
Приложение представляет из себя единственную форму на которой находится две кнопки:
Получить все возможные варианты конструкции
Установить выбранный вариант конструкции в Revit.
Вот пример того как это выглядит, после того как была нажата кнопка Get.
После того, как будет выбран нужный вариант конструкции и нажата кнопка Set, этот вариант конструкции станет активным в Revit.
Реализация
Реализация достаточно тривиально, так как все действия происходят в коде формы
В коде демонстрируется как:
- Использование механизма P/Invoke для доступа к методам Windows API, определенных в библиотеке User32.dll для поиска нужного окна
- Поиск списка с вариантами конструкции в строке состояния
- Чтение текущего варианта конструкции
- Обновление выбранного варианта конструкции
- Обработка нажатия кнопок на форме
Вот исходный код формы:
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Diagnostics;
- using System.Drawing;
- using System.Runtime.InteropServices;
- using System.Text;
- using System.Windows.Automation;
- using System.Windows.Forms;
- using System.Windows.Input;
- namespace DesignOptionModifier
- {
- public partial class Form1 : Form
- {
- #region Windows API
- [DllImport( "user32.dll" )]
- [return: MarshalAs( UnmanagedType.Bool )]
- public static extern bool SetForegroundWindow(
- IntPtr hWnd );
- [DllImport( "user32.dll", SetLastError = true, CharSet = CharSet.Auto )]
- static extern int GetWindowText(
- IntPtr hWnd, [Out] StringBuilder lpString,
- int nMaxCount );
- [DllImport( "user32.dll", SetLastError = true, CharSet = CharSet.Auto )]
- static extern int GetWindowTextLength(
- IntPtr hWnd );
- [DllImport( "user32.dll" )]
- [return: MarshalAs( UnmanagedType.Bool )]
- public static extern bool EnumChildWindows(
- IntPtr window, EnumWindowProc callback, IntPtr i );
- [DllImport( "user32.dll", EntryPoint = "GetClassName" )]
- public static extern int GetClass(
- IntPtr hWnd, StringBuilder className, int nMaxCount );
- public delegate bool EnumWindowProc(
- IntPtr hWnd, IntPtr parameter );
- [DllImport( "user32.dll", SetLastError = true )]
- [return: MarshalAs( UnmanagedType.Bool )]
- private static extern bool GetWindowRect(
- IntPtr hWnd, out RECT lpRect );
- public static string GetText( IntPtr hWnd )
- {
- int length = GetWindowTextLength( hWnd );
- StringBuilder sb = new StringBuilder( length + 1 );
- GetWindowText( hWnd, sb, sb.Capacity );
- return sb.ToString();
- }
- private static bool EnumWindow(
- IntPtr handle,
- IntPtr pointer )
- {
- GCHandle gch = GCHandle.FromIntPtr( pointer );
- List<IntPtr> list = gch.Target as List<IntPtr>;
- if( list != null )
- {
- list.Add( handle );
- }
- return true;
- }
- public static List<IntPtr> GetChildWindows(
- IntPtr parent )
- {
- List<IntPtr> result = new List<IntPtr>();
- GCHandle listHandle = GCHandle.Alloc( result );
- try
- {
- EnumWindowProc childProc = new EnumWindowProc( EnumWindow );
- EnumChildWindows( parent, childProc, GCHandle.ToIntPtr( listHandle ) );
- }
- finally
- {
- if( listHandle.IsAllocated )
- listHandle.Free();
- }
- return result;
- }
- internal struct RECT
- {
- public int Left;
- public int Top;
- public int Right;
- public int Bottom;
- }
- #endregion
- /// <summary>
- /// Кэжширование списка
- /// </summary>
- private AutomationElement comboBoxElement = null;
- public Form1()
- {
- InitializeComponent();
- GetComboBox();
- }
- private void button_ok_Click(
- object sender,
- EventArgs e )
- {
- Close();
- }
- private void GetComboBox()
- {
- int maxX = -1;
- Process[] revits = Process.GetProcessesByName(
- "Revit" );
- IntPtr cb = IntPtr.Zero;
- if( revits.Length > 0 )
- {
- List<IntPtr> children = GetChildWindows(
- revits[0].MainWindowHandle );
- foreach( IntPtr child in children )
- {
- StringBuilder classNameBuffer
- = new StringBuilder( 100 );
- int className = GetClass( child,
- classNameBuffer, 100 );
- if( classNameBuffer.ToString().Contains(
- "msctls_statusbar32" ) )
- {
- List<IntPtr> grandChildren
- = GetChildWindows( child );
- foreach( IntPtr grandChild in grandChildren )
- {
- StringBuilder classNameBuffer2
- = new StringBuilder( 100 );
- int className2 = GetClass( grandChild,
- classNameBuffer2, 100 );
- if( classNameBuffer2.ToString().Contains(
- "ComboBox" ) )
- {
- RECT r;
- GetWindowRect( grandChild, out r );
- // в строке состояния два списка,
- // нам нужен тот, который находится правее
- if( r.Left > maxX )
- {
- maxX = r.Left;
- cb = grandChild;
- }
- }
- }
- }
- }
- }
- if( cb != IntPtr.Zero )
- {
- comboBoxElement = AutomationElement.FromHandle(
- cb );
- }
- }
- private void buttonGet_Click(
- object sender,
- EventArgs e )
- {
- if( ( comboBoxElement != null )
- && ( comboBoxElement.Current.IsEnabled ) )
- {
- ExpandCollapsePattern expandPattern
- = (ExpandCollapsePattern) comboBoxElement
- .GetCurrentPattern(
- ExpandCollapsePattern.Pattern );
- expandPattern.Expand();
- listBox1.Items.Clear();
- CacheRequest cacheRequest = new CacheRequest();
- cacheRequest.Add( AutomationElement.NameProperty );
- cacheRequest.TreeScope = TreeScope.Element
- | TreeScope.Children;
- AutomationElement comboboxItems = comboBoxElement
- .GetUpdatedCache( cacheRequest );
- foreach( AutomationElement item
- in comboboxItems.CachedChildren )
- {
- if( item.Current.Name == "" )
- {
- CacheRequest cacheRequest2 = new CacheRequest();
- cacheRequest2.Add( AutomationElement.NameProperty );
- cacheRequest2.TreeScope = TreeScope.Element
- | TreeScope.Children;
- AutomationElement comboboxItems2
- = item.GetUpdatedCache( cacheRequest );
- foreach( AutomationElement item2
- in comboboxItems2.CachedChildren )
- {
- listBox1.Items.Add( item2.Current.Name );
- }
- }
- }
- expandPattern.Collapse();
- }
- GetSelection();
- }
- private void GetSelection()
- {
- if( ( comboBoxElement != null )
- && ( comboBoxElement.Current.IsEnabled ) )
- {
- SelectionPattern selPattern = comboBoxElement
- .GetCurrentPattern( SelectionPattern.Pattern )
- as SelectionPattern;
- AutomationElement[] items = selPattern.Current
- .GetSelection();
- foreach( AutomationElement item in items )
- {
- int index = 0;
- foreach( var listItem in listBox1.Items )
- {
- if( (string) listItem == item.Current.Name )
- {
- listBox1.SelectedIndex = index;
- break;
- }
- index++;
- }
- }
- }
- SetForegroundWindow( this.Handle );
- }
- private void SetSelection( string option )
- {
- if( ( comboBoxElement != null )
- && ( comboBoxElement.Current.IsEnabled ) )
- {
- AutomationElement sel = null;
- ExpandCollapsePattern expandPattern
- = (ExpandCollapsePattern) comboBoxElement
- .GetCurrentPattern(
- ExpandCollapsePattern.Pattern );
- expandPattern.Expand();
- CacheRequest cacheRequest = new CacheRequest();
- cacheRequest.Add( AutomationElement.NameProperty );
- cacheRequest.TreeScope = TreeScope.Element
- | TreeScope.Children;
- AutomationElement comboboxItems
- = comboBoxElement.GetUpdatedCache(
- cacheRequest );
- foreach( AutomationElement item
- in comboboxItems.CachedChildren )
- {
- if( item.Current.Name == "" )
- {
- CacheRequest cacheRequest2 = new CacheRequest();
- cacheRequest2.Add( AutomationElement.NameProperty );
- cacheRequest2.TreeScope = TreeScope.Element
- | TreeScope.Children;
- AutomationElement comboboxItems2
- = item.GetUpdatedCache( cacheRequest );
- foreach( AutomationElement item2
- in comboboxItems2.CachedChildren )
- {
- if( item2.Current.Name == option )
- {
- sel = item2;
- }
- }
- }
- }
- if( sel != null )
- {
- SelectionItemPattern select =
- (SelectionItemPattern) sel.GetCurrentPattern(
- SelectionItemPattern.Pattern );
- select.Select();
- }
- expandPattern.Collapse();
- }
- SetForegroundWindow( this.Handle );
- }
- private void buttonSet_Click(
- object sender,
- EventArgs e )
- {
- if( listBox1.Items.Count > 0
- && listBox1.SelectedIndex > -1 )
- {
- string option = (string)
- listBox1.Items[listBox1.SelectedIndex];
- SetSelection( option );
- }
- SetForegroundWindow( this.Handle );
- }
- }
- }
Загрузка
Скачать исходный код с проектом для Visual Studio можно из репозитория на GitHub. Версия, обсуждаемая в статье – 2015.0.0.0
Помните, что это неофициальные методы, поэтому используйте их с осторожностью на свой страх и риск.
Обсуждение: http://adn-cis.org/forum/index.php?topic=2695
Опубликовано 08.05.2015