ADN Open CIS
Сообщество программистов Autodesk в СНГ

10/04/2015

Надстройка для работы с Тонкими линиями с помощью 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:

 

Три внешние команды для работы с включения тонких линий, толстых линий и переключатель толстые/тонкие линии довольно просты в реализации, так как они лишь вызывают вспомогательные методы, реализованные в другом классе:

Код - C#: [Выделить]
  1. using Autodesk.Revit.UI;
  2.  
  3. namespace ThinLines
  4. {
  5.   [Autodesk.Revit.Attributes.Transaction(
  6.     Autodesk.Revit.Attributes.TransactionMode.ReadOnly )]
  7.   [Autodesk.Revit.Attributes.Regeneration(
  8.     Autodesk.Revit.Attributes.RegenerationOption.Manual )]
  9.   public class Command_ThinLines : IExternalCommand
  10.   {
  11.     public Result Execute(
  12.       ExternalCommandData commandData,
  13.       ref string message,
  14.       Autodesk.Revit.DB.ElementSet elements )
  15.     {
  16.       ThinLinesApp.SetThinLines( commandData.Application, true );
  17.       return Result.Succeeded;
  18.     }
  19.   }
  20. }

В классе приложение реализовано следующее:

  • Создание пользовательской панели на ленте
  • Обработка изображений для кнопок
  • Использование P/Invoke и Windows API функций, определенных в User32.dll для поиска нужного окна.
  • Использование метода PostCommand для выполнения встроенной команды Тонкие линии.
  • Доступ к текущему состоянию кнопки Тонкие линии.

Вот код:

Код - C#: [Выделить]
  1. using Autodesk.Revit.UI;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Drawing;
  5. using System.Runtime.InteropServices;
  6. using System.Windows;
  7. using System.Windows.Automation;
  8. using System.Windows.Interop;
  9. using System.Windows.Media;
  10. using System.Windows.Media.Imaging;
  11.  
  12. namespace ThinLines
  13. {
  14.   public class ThinLinesApp : IExternalApplication
  15.   {
  16.     #region Windows API, get from pinvoke.net
  17.  
  18.     [DllImport( "user32.dll", SetLastError = true )]
  19.     static extern IntPtr FindWindowEx(
  20.       IntPtr hwndParent, IntPtr hwndChildAfter,
  21.       string lpszClass, string lpszWindow );
  22.  
  23.     [DllImport( "user32.dll" )]
  24.     [return: MarshalAs( UnmanagedType.Bool )]
  25.     public static extern bool EnumChildWindows(
  26.       IntPtr window, EnumWindowProc callback,
  27.       IntPtr i );
  28.  
  29.     public delegate bool EnumWindowProc(
  30.       IntPtr hWnd, IntPtr parameter );
  31.  
  32.     public static bool EnumWindow(
  33.       IntPtr handle,
  34.       IntPtr pointer )
  35.     {
  36.       GCHandle gch = GCHandle.FromIntPtr( pointer );
  37.       List<IntPtr> list = gch.Target as List<IntPtr>;
  38.       if( list != null )
  39.       {
  40.         list.Add( handle );
  41.       }
  42.       return true;
  43.     }
  44.  
  45.     public static List<IntPtr> GetChildWindows(
  46.       IntPtr parent )
  47.     {
  48.       List<IntPtr> result = new List<IntPtr>();
  49.       GCHandle listHandle = GCHandle.Alloc( result );
  50.       try
  51.       {
  52.         EnumWindowProc childProc = new EnumWindowProc(
  53.           EnumWindow );
  54.  
  55.         EnumChildWindows( parent, childProc,
  56.           GCHandle.ToIntPtr( listHandle ) );
  57.       }
  58.       finally
  59.       {
  60.         if( listHandle.IsAllocated )
  61.           listHandle.Free();
  62.       }
  63.       return result;
  64.     }
  65.     #endregion
  66.  
  67.     public Result OnShutdown( UIControlledApplication a )
  68.     {
  69.       return Result.Succeeded;
  70.     }
  71.  
  72.     public Result OnStartup( UIControlledApplication a )
  73.     {
  74.       string tabName = "LineTools";
  75.       string panelName = "LineTools";
  76.       string buttonThinName = "Thin";
  77.       string buttonThickName = "Thick";
  78.       string buttonToggleName = "Toggle";
  79.  
  80.       try
  81.       {
  82.         List<RibbonPanel> panels = a.GetRibbonPanels(
  83.           tabName );
  84.       }
  85.       catch
  86.       {
  87.         a.CreateRibbonTab( tabName );
  88.       }
  89.  
  90.       RibbonPanel panelViewExport = a.CreateRibbonPanel(
  91.         tabName, panelName );
  92.  
  93.       panelViewExport.Name = panelName;
  94.       panelViewExport.Title = panelName;
  95.  
  96.       PushButtonData buttonThin = new PushButtonData(
  97.         buttonThinName, buttonThinName,
  98.         System.Reflection.Assembly.GetExecutingAssembly().Location,
  99.         typeof( Command_ThinLines ).FullName );
  100.  
  101.       buttonThin.ToolTip = buttonThinName;
  102.       ImageSource iconThin = GetIconSource( Images.Thin );
  103.       buttonThin.LargeImage = iconThin;
  104.       buttonThin.Image = Thumbnail( iconThin );
  105.       panelViewExport.AddItem( buttonThin );
  106.  
  107.       PushButtonData buttonThick = new PushButtonData(
  108.         buttonThickName, buttonThickName,
  109.         System.Reflection.Assembly.GetExecutingAssembly().Location,
  110.         typeof( Command_ThickLines ).FullName );
  111.  
  112.       buttonThick.ToolTip = buttonThickName;
  113.       ImageSource iconThick = GetIconSource( Images.Thick );
  114.       buttonThick.LargeImage = iconThick;
  115.       buttonThick.Image = Thumbnail( iconThick );
  116.       panelViewExport.AddItem( buttonThick );
  117.  
  118.       PushButtonData buttonToggle = new PushButtonData(
  119.         buttonToggleName, buttonToggleName,
  120.         System.Reflection.Assembly.GetExecutingAssembly().Location,
  121.         typeof( Command_ToggleLineThickness ).FullName );
  122.  
  123.       buttonToggle.ToolTip = buttonToggleName;
  124.       ImageSource iconToggle = GetIconSource( Images.ToggleLineThickness );
  125.       buttonToggle.LargeImage = iconToggle;
  126.       buttonToggle.Image = Thumbnail( iconToggle );
  127.       panelViewExport.AddItem( buttonToggle );
  128.  
  129.       return Result.Succeeded;
  130.     }
  131.  
  132.     public static ImageSource GetIconSource( Bitmap bmp )
  133.     {
  134.       BitmapSource icon
  135.         = Imaging.CreateBitmapSourceFromHBitmap(
  136.         bmp.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty,
  137.         System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions() );
  138.  
  139.       return (System.Windows.Media.ImageSource) icon;
  140.     }
  141.  
  142.     public static ImageSource Thumbnail(
  143.       ImageSource source )
  144.     {
  145.       Rect rect = new Rect( 0, 0, 16, 16 );
  146.       DrawingVisual drawingVisual = new DrawingVisual();
  147.  
  148.       using( DrawingContext drawingContext
  149.         = drawingVisual.RenderOpen() )
  150.       {
  151.         drawingContext.DrawImage( source, rect );
  152.       }
  153.  
  154.       RenderTargetBitmap resizedImage
  155.         = new RenderTargetBitmap(
  156.           (int) rect.Width, (int) rect.Height, 96, 96,
  157.           PixelFormats.Default );
  158.  
  159.       resizedImage.Render( drawingVisual );
  160.  
  161.       return resizedImage;
  162.     }
  163.  
  164.     public static AutomationElement GetThinLinesButton()
  165.     {
  166.       IntPtr revitHandle
  167.         = System.Diagnostics.Process.GetCurrentProcess()
  168.           .MainWindowHandle;
  169.  
  170.       IntPtr outerToolFrame = FindWindowEx( revitHandle,
  171.         IntPtr.Zero, "AdImpApplicationFrame",
  172.         "AdImpApplicationFrame" );
  173.  
  174.       IntPtr innerToolFrame = GetChildWindows(
  175.         outerToolFrame )[0];
  176.  
  177.       AutomationElement innerToolFrameElement
  178.         = AutomationElement.FromHandle( innerToolFrame );
  179.  
  180.       PropertyCondition typeRibbonCondition
  181.         = new PropertyCondition(
  182.           AutomationElement.ControlTypeProperty,
  183.           ControlType.Custom );
  184.  
  185.       AutomationElement lowestPanel
  186.         = innerToolFrameElement.FindFirst(
  187.           TreeScope.Children, typeRibbonCondition );
  188.  
  189.       PropertyCondition nameRibbonCondition
  190.         = new PropertyCondition(
  191.           AutomationElement.AutomationIdProperty,
  192.           "ID_THIN_LINES_RibbonItemControl" );
  193.  
  194.       AndCondition andCondition = new AndCondition(
  195.         typeRibbonCondition, nameRibbonCondition );
  196.  
  197.       AutomationElement buttonContainer
  198.         = lowestPanel.FindFirst( TreeScope.Children,
  199.           andCondition );
  200.  
  201.       PropertyCondition typeButtonCondition
  202.         = new PropertyCondition(
  203.           AutomationElement.ControlTypeProperty,
  204.           ControlType.Button );
  205.  
  206.       PropertyCondition nameButtonCondition
  207.         = new PropertyCondition(
  208.           AutomationElement.AutomationIdProperty,
  209.           "ID_THIN_LINES" );
  210.  
  211.       AndCondition andConditionButton = new AndCondition(
  212.         typeButtonCondition, nameButtonCondition );
  213.  
  214.       AutomationElement button = buttonContainer.FindFirst(
  215.         TreeScope.Children, andConditionButton );
  216.  
  217.       return button;
  218.     }
  219.  
  220.     public static void SetThinLines(
  221.       UIApplication app,
  222.       bool makeThin )
  223.     {
  224.       bool isAlreadyThin = IsThinLines();
  225.  
  226.       if( makeThin != isAlreadyThin )
  227.       {
  228.         // Switch TL state by invoking
  229.         // PostableCommand.ThinLines
  230.  
  231.         RevitCommandId commandId
  232.           = RevitCommandId.LookupPostableCommandId(
  233.             PostableCommand.ThinLines );
  234.  
  235.         if( app.CanPostCommand( commandId ) )
  236.         {
  237.           app.PostCommand( commandId );
  238.         }
  239.       }
  240.     }
  241.  
  242.     public static bool IsThinLines()
  243.     {
  244.       AutomationElement button = GetThinLinesButton();
  245.  
  246.       TogglePattern togglePattern
  247.         = button.GetCurrentPattern(
  248.           TogglePattern.Pattern ) as TogglePattern;
  249.  
  250.       string state = togglePattern.Current
  251.         .ToggleState.ToString().ToUpper();
  252.  
  253.       return ( state == "ON" );
  254.     }
  255.   }
  256. }

Исходный код всего проекта можно скачать на 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