Надстройка для работы с Тонкими линиями с помощью UI Automation
Разработчика надстроек для Revit давно уже просят добавить методы для включения отключения тонких линий.
К счастью, в Revit 2015 R2 это наконец-то стало возможным.
Revit API Thin Lines Options
Класс ThinLinesOptions содержит настройки, связанные с Тонкими линиями, отображаемыми в пользовательском интерфейсе.
Статическое свойство:
- ThinLinesOptions.AreThinLinesEnabled
Определяет, включены или нет «Тонкие линии» в текущем сеансе работы.
Разное API для Revit 2015 и Revit 2015 R2
Остается одна маленькая проблемка. Что делать если Revit 2015 R2 все еще не установлен у пользователя?
Или если обобщить проблему: как можно избежать поддержки двух отдельных версий надстройки для Revit 2015 и Revit 2015 R2?
Использование UI Automation для версий до R2
Rudolf Honke предложил использовать UI Automation для определения состояния кнопки «Тонкие линии» и метод PostCommand для программного нажатия на кнопку для изменения состояния.
Предупреждение: данные метод не является официальным методом и не должен использоваться в реальных проектах.
Реализация метода
Rudolf: Как обычно, я думаю, что можно использовать UI Automation для данных целей.
Вот проект для Visual Studio для работы с Тонкими линиями.
Вкладка LineTools содержит три кнопки:
Пара замечаний:
- Нужно проверить, будет ли работать данный способ, если кнопка Тонкии линии удалена с панели быстрого запуска
- Также я столкнулся с ошибкой «Команда не может быть выполнена несколько раз», когда я нажимаю на кнопки слишком быстро.
Возможно нужно обернуть это в try…catch.
Изображения кнопок находятся в файлах ресурсов.
Очевидно, что нужно добавить ссылки на библиотеки UI Automation:
Три внешние команды для работы с включения тонких линий, толстых линий и переключатель толстые/тонкие линии довольно просты в реализации, так как они лишь вызывают вспомогательные методы, реализованные в другом классе:
- using Autodesk.Revit.UI;
- namespace ThinLines
- {
- [Autodesk.Revit.Attributes.Transaction(
- Autodesk.Revit.Attributes.TransactionMode.ReadOnly )]
- [Autodesk.Revit.Attributes.Regeneration(
- Autodesk.Revit.Attributes.RegenerationOption.Manual )]
- public class Command_ThinLines : IExternalCommand
- {
- public Result Execute(
- ExternalCommandData commandData,
- ref string message,
- Autodesk.Revit.DB.ElementSet elements )
- {
- ThinLinesApp.SetThinLines( commandData.Application, true );
- return Result.Succeeded;
- }
- }
- }
В классе приложение реализовано следующее:
- Создание пользовательской панели на ленте
- Обработка изображений для кнопок
- Использование P/Invoke и Windows API функций, определенных в User32.dll для поиска нужного окна.
- Использование метода PostCommand для выполнения встроенной команды Тонкие линии.
- Доступ к текущему состоянию кнопки Тонкие линии.
Вот код:
- using Autodesk.Revit.UI;
- using System;
- using System.Collections.Generic;
- using System.Drawing;
- using System.Runtime.InteropServices;
- using System.Windows;
- using System.Windows.Automation;
- using System.Windows.Interop;
- using System.Windows.Media;
- using System.Windows.Media.Imaging;
- namespace ThinLines
- {
- public class ThinLinesApp : IExternalApplication
- {
- #region Windows API, get from pinvoke.net
- [DllImport( "user32.dll", SetLastError = true )]
- static extern IntPtr FindWindowEx(
- IntPtr hwndParent, IntPtr hwndChildAfter,
- string lpszClass, string lpszWindow );
- [DllImport( "user32.dll" )]
- [return: MarshalAs( UnmanagedType.Bool )]
- public static extern bool EnumChildWindows(
- IntPtr window, EnumWindowProc callback,
- IntPtr i );
- public delegate bool EnumWindowProc(
- IntPtr hWnd, IntPtr parameter );
- public 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;
- }
- #endregion
- public Result OnShutdown( UIControlledApplication a )
- {
- return Result.Succeeded;
- }
- public Result OnStartup( UIControlledApplication a )
- {
- string tabName = "LineTools";
- string panelName = "LineTools";
- string buttonThinName = "Thin";
- string buttonThickName = "Thick";
- string buttonToggleName = "Toggle";
- try
- {
- List<RibbonPanel> panels = a.GetRibbonPanels(
- tabName );
- }
- catch
- {
- a.CreateRibbonTab( tabName );
- }
- RibbonPanel panelViewExport = a.CreateRibbonPanel(
- tabName, panelName );
- panelViewExport.Name = panelName;
- panelViewExport.Title = panelName;
- PushButtonData buttonThin = new PushButtonData(
- buttonThinName, buttonThinName,
- System.Reflection.Assembly.GetExecutingAssembly().Location,
- typeof( Command_ThinLines ).FullName );
- buttonThin.ToolTip = buttonThinName;
- ImageSource iconThin = GetIconSource( Images.Thin );
- buttonThin.LargeImage = iconThin;
- buttonThin.Image = Thumbnail( iconThin );
- panelViewExport.AddItem( buttonThin );
- PushButtonData buttonThick = new PushButtonData(
- buttonThickName, buttonThickName,
- System.Reflection.Assembly.GetExecutingAssembly().Location,
- typeof( Command_ThickLines ).FullName );
- buttonThick.ToolTip = buttonThickName;
- ImageSource iconThick = GetIconSource( Images.Thick );
- buttonThick.LargeImage = iconThick;
- buttonThick.Image = Thumbnail( iconThick );
- panelViewExport.AddItem( buttonThick );
- PushButtonData buttonToggle = new PushButtonData(
- buttonToggleName, buttonToggleName,
- System.Reflection.Assembly.GetExecutingAssembly().Location,
- typeof( Command_ToggleLineThickness ).FullName );
- buttonToggle.ToolTip = buttonToggleName;
- ImageSource iconToggle = GetIconSource( Images.ToggleLineThickness );
- buttonToggle.LargeImage = iconToggle;
- buttonToggle.Image = Thumbnail( iconToggle );
- panelViewExport.AddItem( buttonToggle );
- return Result.Succeeded;
- }
- public static ImageSource GetIconSource( Bitmap bmp )
- {
- BitmapSource icon
- = Imaging.CreateBitmapSourceFromHBitmap(
- bmp.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty,
- System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions() );
- return (System.Windows.Media.ImageSource) icon;
- }
- public static ImageSource Thumbnail(
- ImageSource source )
- {
- Rect rect = new Rect( 0, 0, 16, 16 );
- DrawingVisual drawingVisual = new DrawingVisual();
- using( DrawingContext drawingContext
- = drawingVisual.RenderOpen() )
- {
- drawingContext.DrawImage( source, rect );
- }
- RenderTargetBitmap resizedImage
- = new RenderTargetBitmap(
- (int) rect.Width, (int) rect.Height, 96, 96,
- PixelFormats.Default );
- resizedImage.Render( drawingVisual );
- return resizedImage;
- }
- public static AutomationElement GetThinLinesButton()
- {
- IntPtr revitHandle
- = System.Diagnostics.Process.GetCurrentProcess()
- .MainWindowHandle;
- IntPtr outerToolFrame = FindWindowEx( revitHandle,
- IntPtr.Zero, "AdImpApplicationFrame",
- "AdImpApplicationFrame" );
- IntPtr innerToolFrame = GetChildWindows(
- outerToolFrame )[0];
- AutomationElement innerToolFrameElement
- = AutomationElement.FromHandle( innerToolFrame );
- PropertyCondition typeRibbonCondition
- = new PropertyCondition(
- AutomationElement.ControlTypeProperty,
- ControlType.Custom );
- AutomationElement lowestPanel
- = innerToolFrameElement.FindFirst(
- TreeScope.Children, typeRibbonCondition );
- PropertyCondition nameRibbonCondition
- = new PropertyCondition(
- AutomationElement.AutomationIdProperty,
- "ID_THIN_LINES_RibbonItemControl" );
- AndCondition andCondition = new AndCondition(
- typeRibbonCondition, nameRibbonCondition );
- AutomationElement buttonContainer
- = lowestPanel.FindFirst( TreeScope.Children,
- andCondition );
- PropertyCondition typeButtonCondition
- = new PropertyCondition(
- AutomationElement.ControlTypeProperty,
- ControlType.Button );
- PropertyCondition nameButtonCondition
- = new PropertyCondition(
- AutomationElement.AutomationIdProperty,
- "ID_THIN_LINES" );
- AndCondition andConditionButton = new AndCondition(
- typeButtonCondition, nameButtonCondition );
- AutomationElement button = buttonContainer.FindFirst(
- TreeScope.Children, andConditionButton );
- return button;
- }
- public static void SetThinLines(
- UIApplication app,
- bool makeThin )
- {
- bool isAlreadyThin = IsThinLines();
- if( makeThin != isAlreadyThin )
- {
- // Switch TL state by invoking
- // PostableCommand.ThinLines
- RevitCommandId commandId
- = RevitCommandId.LookupPostableCommandId(
- PostableCommand.ThinLines );
- if( app.CanPostCommand( commandId ) )
- {
- app.PostCommand( commandId );
- }
- }
- }
- public static bool IsThinLines()
- {
- AutomationElement button = GetThinLinesButton();
- TogglePattern togglePattern
- = button.GetCurrentPattern(
- TogglePattern.Pattern ) as TogglePattern;
- string state = togglePattern.Current
- .ToggleState.ToString().ToUpper();
- return ( state == "ON" );
- }
- }
- }
Исходный код всего проекта можно скачать на GitHub.
Большое спасибо Rudolf за реализацию.
Источник: http://thebuildingcoder.typepad.com/blog/2015/03/thin-lines-add-in-using-ui-automation.html
Обсуждение: http://adn-cis.org/forum/index.php?topic=2622
Опубликовано 10.04.2015