1. Обязательно представиться на русском языке кириллицей (заполнить поле "Имя").
  2. Фиктивные имена мы не приветствуем. Ивановых и Пупкиных здесь уже достаточно.
  3. Не писать свой вопрос в первую попавшуюся тему - вместо этого создать новую тему.
  4. За поиск, предложение и обсуждение пиратского ПО и средств взлома - бан без предупреждения.
  5. Рекламу и частные объявления "куплю/продам/есть халтура" мы не размещаем ни на каких условиях.
  6. Перед тем как что-то написать - читать здесь и здесь, а студентам - обязательно здесь.
  7. Не надо писать в ЛС администраторам свои технические вопросы. Администраторы форума отлично знают как работает форум, а не все-все контроллеры, о которых тут пишут.

Скрипты и API

Сценарии / Модули API

Модератор: специалисты Eplan

Закрыто

ids
знаток Eplan
знаток Eplan
Сообщения: 25
Зарегистрирован: 17 сен 2015, 09:25
Имя: Иванюк Дмитрий Сергеевич
Страна: РБ
город/регион: Брест
Благодарил (а): 2 раза

Re: Скрипты и API

Сообщение ids »

И какой вариант реализовали?

Madwolf
знаток Eplan
знаток Eplan
Сообщения: 1454
Зарегистрирован: 17 окт 2012, 11:24
Имя: Виталий
Страна: Беларусь
город/регион: Минск
Благодарил (а): 60 раз
Поблагодарили: 181 раз

Re: Скрипты и API

Сообщение Madwolf »

Пока последний, посмотрим что из этого получится.
Дмитрий Сергеевич, расскажите пожалуйста подробнее про окна редактирования, встроенные в Eplan. Как это реализовывается?
Аватара пользователя

AGorskiy
знаток Eplan
знаток Eplan
Сообщения: 3327
Зарегистрирован: 05 мар 2012, 15:00
Имя: Горский Андрей Сергеевич
город/регион: Санкт-Петербург
Благодарил (а): 176 раз
Поблагодарили: 364 раза

Re: Скрипты и API

Сообщение AGorskiy »

Alexander_Pavlov писал(а):На Хабре появилась публикация про API Eplan http://habrahabr.ru/post/271671/
Ссылка есть на наш форум))))
Все знания для всех. Поиск знаний священен. Обмен знаниями священен. Копирование священно.
Аватара пользователя

AGorskiy
знаток Eplan
знаток Eplan
Сообщения: 3327
Зарегистрирован: 05 мар 2012, 15:00
Имя: Горский Андрей Сергеевич
город/регион: Санкт-Петербург
Благодарил (а): 176 раз
Поблагодарили: 364 раза

Re: Скрипты и API

Сообщение AGorskiy »

Предполагаю, что автор статьи на Хабре - скромный молчаливый (здесь нет ни одного сообщения) najdjel.
Спасибо, Александр.
Все знания для всех. Поиск знаний священен. Обмен знаниями священен. Копирование священно.

Madwolf
знаток Eplan
знаток Eplan
Сообщения: 1454
Зарегистрирован: 17 окт 2012, 11:24
Имя: Виталий
Страна: Беларусь
город/регион: Минск
Благодарил (а): 60 раз
Поблагодарили: 181 раз

Re: Скрипты и API

Сообщение Madwolf »

Возникла следующая проблема: нужно записать в свойства функции данные, причем хочу обращаться к ней по ID и индексу, например: oFunction.Properties[20901, 1].Set(). Еплан постоянно ругается на неправильный индекс, хотя там 100% содержатся данные по данному ID и индексу. При этом oFunction.Properties.FUNC_SUPPLEMENTARYFIELD[1].Set() прекрасно работает.

Для чего нужно? Для того чтобы можно было произвольно копировать некоторые выбранные свойства в другие, т.к. некоторые отчеты не поддерживают сортировку по определенным свойствам. Хотел поручить выбор ID и индекса конечному пользователю.

Может кто уже решал данный вопрос?
Аватара пользователя

Alexander_Pavlov
знаток Eplan
знаток Eplan
Сообщения: 97
Зарегистрирован: 09 апр 2014, 09:55
Имя: Александр Павлов
Страна: РФ
город/регион: Санкт Петербург
Благодарил (а): 12 раз
Поблагодарили: 6 раз

Re: Скрипты и API

Сообщение Alexander_Pavlov »

Хочу написать программку, которая задавала бы толщину линий в обзоре модели. Подскажите пожалуйста, какому классу принадлежит объект обзор модели?
Eplan 2.6 сборка 10395

Madwolf
знаток Eplan
знаток Eplan
Сообщения: 1454
Зарегистрирован: 17 окт 2012, 11:24
Имя: Виталий
Страна: Беларусь
город/регион: Минск
Благодарил (а): 60 раз
Поблагодарили: 181 раз

Re: Скрипты и API

Сообщение Madwolf »

Eplan.EplApi.DataModel.Graphics: ViewPlacement - обзор модели, ViewPart - деталь на обзоре модели.
А вот уже готовый класс, который присваивает данные по толщине линий и др. параметры у графического элемента на обзоре модели:

Код: Выделить всё

static class SetPenClass
    {
        static public void SetPen(ViewPart oVP)
        {
            using (UndoStep oUndo = new UndoManager().CreateUndoStep())
            {
                Pen oPen = new Pen();
                foreach (Placement item in oVP.SubPlacements)
                {
                    try
                    {
                        if (item is GraphicalPlacement)
                        {
                            GraphicalPlacement oGPl = item as GraphicalPlacement;
                            oPen = oGPl.Layer.Pen;
                            oGPl.Pen = oPen;
                        }
                    }
                    catch (Exception ex)
                    {
                        WriteEplanMessage.Message(ex.Message);
                    }
                }

                oUndo.SetUndoDescription("Изменить графические объекты на обзоре модели");
            }
        }
    }
А данные в него поступают таким вот образом:

Код: Выделить всё

public bool Execute(ActionCallingContext ctx)
        {
            try
            {
                SelectionSet oSS = new SelectionSet();
                StorableObject[] oSOArray = oSS.Selection;
                if (oSS.Selection.Length < 1)
                {
                    CommandLineInterpreter oCLI = new CommandLineInterpreter();
                    String InteractionName = "XGedStartInteractionAction /Name:SetPenInteraction";
                    oCLI.Execute(InteractionName);
                }

                else
                {

                    foreach (StorableObject oSO in oSOArray)
                    {

                        if (oSO is ViewPlacement)
                        {
                            foreach (Placement subPlacement in ((ViewPlacement)oSO).SubPlacements)
                            {
                                if (subPlacement is ViewPart)
                                {
                                    SetPenClass.SetPen(subPlacement as ViewPart);
                                }
                            }
                        }

                        if (oSO is ViewPart)
                        {
                            SetPenClass.SetPen(oSO as ViewPart);
                        }

                    }

                }

            }

            catch (Exception ex)
            {
                WriteEplanMessage.Message(ex.Message);
            }


            return true;
        }
Интерэкшен я не расписывал, но его суть в том, что если объектов выделенных нет, то запускается этот интерэкшен и пользователю предлагается выделить объекты.

ids
знаток Eplan
знаток Eplan
Сообщения: 25
Зарегистрирован: 17 сен 2015, 09:25
Имя: Иванюк Дмитрий Сергеевич
Страна: РБ
город/регион: Брест
Благодарил (а): 2 раза

Re: Скрипты и API

Сообщение ids »

С помощью Add-In'a можно реализовать дополнительную функциональность, серьезно облегчающую жизнь инженера по автоматизации. Часто бывает необходимо, чтобы дополнение постоянно отображало какую-то визуальную информации, нужную при разработке проекта. При простом решении данной проблемы - создание пользовательского окна и отображении его либо поверх всех окон, либо перемещением его на передний план - мы получаем некоторые неудобства с организацией пользовательского пространства. С ними можно кое-как мириться, но что делать, если таких окон должно быть несколько? В самой среде все организовано как надо - нужные действия реализованы в виде дополнительных окон, расположение и размер которых пользователь как хочет, так и настраивает для себя. Можно предположить, что Eplan API должен содержать данную функциональность интеграции пользовательских окон в среду, но как ни странно, такого нет. Но, если очень нужно, это можно реализовать.
Предполагается, что уже есть опыт разработки типового дополнения (если нет, то можно ознакомиться с основами здесь, поэтому будут комментироваться только действия, связанные с реализацией визуального окна дополнения. Рассмотрим, для начала, вариант дополнения, отображающего в отдельном окне (на переднем плане) список текущих открытых страниц.
Основной программный файл дополнения, main.cs.

Код: Выделить всё

//@file main.cs
//@brief Классы, реализующие дополнение.

// @author  idimm.

// @par Текущая версия:
// @$Rev: 1 $.\n
// @$Author: idimm $.\n
// @$Date:: 2015-12-03 12:05:13#$.

using System;
using System.Windows.Forms;

using Eplan.EplApi.ApplicationFramework;
using Eplan.EplApi.Gui;
using Eplan.EplApi.DataModel;
using Eplan.EplApi.HEServices;

namespace SimpleAddIn
    {
    public class AddInModule : IEplAddIn
        {
        public bool OnRegister( ref bool bLoadOnStart )
            {
            bLoadOnStart = true;
            return true;
            }

        public bool OnUnregister()
            {
            return true;
            }

        public bool OnInit()
            {
            return true;
            }

        public bool OnInitGui()
            {
            Eplan.EplApi.Gui.Menu oMenu = new Eplan.EplApi.Gui.Menu();
            uint menuID = oMenu.AddMainMenu(
                "eplaner", Eplan.EplApi.Gui.Menu.MainMenuName.eMainMenuHelp,
                "Окно помощи", "showWndAction",
                "Окно помощи по проекту", 0 /*inserts before*/ );

            return true;
            }

        public bool OnExit()
            {
            return true;
            }
        }


    public class Action_Test : IEplAction
        {
        public Action_Test()
            {
            frm = new System.Windows.Forms.Form();
            rtbox = new System.Windows.Forms.RichTextBox();
            
            rtbox.Dock = System.Windows.Forms.DockStyle.Fill;
            rtbox.Location = new System.Drawing.Point( 0, 0 );
            frm.Controls.Add( rtbox );            
            }

        

        public bool OnRegister( ref string Name, ref int Ordinal )
            {
            Name = "showWndAction";
            Ordinal = 20;
            
            return true;
            }

        public bool Execute( ActionCallingContext oActionCallingContext )
            {
            SelectionSet set = new SelectionSet();
            Page[] pages = set.OpenedPages;

            rtbox.Clear();
            foreach ( Page pg in pages ) 
                {
                rtbox.AppendText( pg.Name + "\n" );
                }            
            frm.ShowDialog();

            return true;
            }

        public void GetActionProperties( ref ActionProperties actionProperties )
            {
            }

        System.Windows.Forms.Form frm;
        System.Windows.Forms.RichTextBox rtbox;
        }
    }
Здесь реализовано простейшее дополнение, в процессе выполнения действия (Action Action_Test) заполняем текстовое поле нужной информацией и отображаем форму в диалоговом режиме. Результаты приведены на рисунках ниже.
Окно дополнения - вызов.png
Окно дополнения - поверх всех окон.png

Как видим, окно отображается поверх среды Eplan, работа в котором возможна только после закрытия окна. Это очень неудобно. Рассмотрим второй вариант дополнения, отображающего информацию в обычном отдельном окне. Для этого заменим строку frm.ShowDialog() на frm.Show() в методе Execute для класса Action_Test. Теперь можно переключаться между окном дополнения и средой. Но для корректной работы после закрытия окна дополнения необходимо модернизировать код класса Action_Test:

Код: Выделить всё

public class Action_Test : IEplAction
        {
        public Action_Test()
            {
            frm = new System.Windows.Forms.Form();
            rtbox = new System.Windows.Forms.RichTextBox();
            
            rtbox.Dock = System.Windows.Forms.DockStyle.Fill;
            rtbox.Location = new System.Drawing.Point( 0, 0 );
            frm.Controls.Add( rtbox );
            frm.FormClosing += FormClosing;
            }
        

        public bool OnRegister( ref string Name, ref int Ordinal )
            {
            Name = "showWndAction";
            Ordinal = 20;
            
            return true;
            }

        private void FormClosing( object sender, FormClosingEventArgs e )
            {
            e.Cancel = true;
            ( sender as System.Windows.Forms.Form ).Hide(); 
            }

        public bool Execute( ActionCallingContext oActionCallingContext )
            {
            SelectionSet set = new SelectionSet();
            Page[] pages = set.OpenedPages;

            rtbox.Clear();
            foreach ( Page pg in pages ) 
                {
                rtbox.AppendText( pg.Name + "\n" );
                }            
            frm.Show();            

            return true;
            }

        public void GetActionProperties( ref ActionProperties actionProperties )
            {
            }

        System.Windows.Forms.Form frm;
        System.Windows.Forms.RichTextBox rtbox;
        }
Как видно необходимо было добавить корректную обработку закрытия окна (метод FormClosing). Такое решение может использоваться, но все равно оно не самое удобное для пользователя. Для того, чтобы формы вела себя как встроенная, поместим ее на какое-либо стандартное окно среды, предварительно скрыв ее содержимое. Для реализации данной функциональности окна будем использовать функции WinApi. Код дополнения приведен ниже. Алгоритм работы подробно расписан в комментариях метода ShowDlg() класса EplanWindowWrapper.
Основной программный файл дополнения, main.cs.

Код: Выделить всё

//@file main.cs
//@brief Классы, реализующие дополнение.

// @author  idimm.

// @par Текущая версия:
// @$Rev: 1 $.\n
// @$Author: idimm $.\n
// @$Date:: 2015-12-03 12:05:13#$.

using System;
using System.Windows.Forms;

using Eplan.EplApi.ApplicationFramework;
using Eplan.EplApi.Gui;
using Eplan.EplApi.DataModel;
using Eplan.EplApi.HEServices;

namespace SimpleAddIn
    {
    public class AddInModule : IEplAddIn
        {
        public bool OnRegister( ref bool bLoadOnStart )
            {
            bLoadOnStart = true;
            return true;
            }

        public bool OnUnregister()
            {
            return true;
            }

        public bool OnInit()
            {
            return true;
            }

        public bool OnInitGui()
            {
            Eplan.EplApi.Gui.Menu oMenu = new Eplan.EplApi.Gui.Menu();
            uint menuID = oMenu.AddMainMenu(
                "eplaner", Eplan.EplApi.Gui.Menu.MainMenuName.eMainMenuHelp,
                "Окно помощи", "showWndAction",
                "Окно помощи по проекту", 0 /*inserts before*/ );

            return true;
            }

        public bool OnExit()
            {
            return true;
            }
        }


    public class Action_Test : IEplAction
        {
        public Action_Test()
            {
            rtbox = new System.Windows.Forms.RichTextBox();            
            rtbox.Dock = System.Windows.Forms.DockStyle.Fill;
            rtbox.Location = new System.Drawing.Point( 0, 0 );
            }        

        public bool OnRegister( ref string Name, ref int Ordinal )
            {
            Name = "showWndAction";
            Ordinal = 20;
            
            return true;
            }

        private void FormClosing( object sender, FormClosingEventArgs e )
            {
            e.Cancel = true;
            ( sender as System.Windows.Forms.Form ).Hide(); 
            }

        public bool Execute( ActionCallingContext oActionCallingContext )
            {
            if ( eww == null )
                {               
                eww = new EplanWindowWrapper( rtbox, new ToolStrip(), "Стр" );
                }

            if ( !wasInit )
                {
                SelectionSet set = new SelectionSet();
                Page[] pages = set.OpenedPages;

                rtbox.Clear();
                foreach ( Page pg in pages )
                    {
                    rtbox.AppendText( pg.Name + "\n" );
                    }
                }
            eww.ShowDlg();

            return true;
            }

        public void GetActionProperties( ref ActionProperties actionProperties )
            {
            }

        System.Windows.Forms.RichTextBox rtbox;
        EplanWindowWrapper eww;

        bool wasInit = false;
        }
    }
Дополнительный программный модуль, EplanWindowWrapper.cs.

Код: Выделить всё

///@file EplanWindowWrapper.cs
///@brief Классы, реализующие минимальную функциональность, необходимую для 
///отображения пользовательских окон поверх окон среды Eplan.
///
/// @author idimm.
///
/// @par Текущая версия:
/// @$Rev: 476 $.\n
/// @$Author: id $.\n
/// @$Date:: 2012-04-07 16:45:35#$.
/// 

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

/// <summary>
/// Окно, отображающееся в Eplan'е и ведущее себя как встроенное в среду.
/// </summary>
public class EplanWindowWrapper
    {
    private IntPtr wndHandle = IntPtr.Zero;     ///Найденное окно.
    private IntPtr dialogHandle = IntPtr.Zero;  ///Найденный диалог.
    private IntPtr panelPtr;                    ///Найденная панель.
                                                  
    IntPtr oldDialogWndProc = IntPtr.Zero;      ///Старая оконная процедура.
    IntPtr oldPanelProc = IntPtr.Zero;          ///Старая оконная процедура.
    pi.Win32WndProc newWndProc;                 ///Новая оконная процедура.                         

    Control mainCntrl;          ///Пользовательское окно.
    byte[] newCaption;          ///Новый заголовок окна. 
    string newCaptionStr;

    private string windowName;
    private int panelDlgItemId;
    private int dialogDlgItemId;
    private int panelToHideDlgItemId;
    private int menuClickId;


    /// <summary>    
    /// Новая оконная процедура для обработки перехваченных сообщений 
    /// диалогового окна Eplan'а.
    /// </summary>
    private IntPtr DialogWndProc( IntPtr hWnd, int msg, int wPar, IntPtr lPar )
        {
        if ( hWnd == panelPtr )
            {
            switch ( ( pi.WM ) msg )
                {
                case pi.WM.MOVE:
                case pi.WM.SIZE:
                    IntPtr dialogPtr = pi.GetParent( mainCntrl.Handle );

                    pi.RECT rctDialog;
                    pi.RECT rctPanel;
                    pi.GetWindowRect( dialogPtr, out rctDialog );
                    pi.GetWindowRect( panelPtr, out rctPanel );

                    int w = rctDialog.Right - rctDialog.Left;
                    int h = rctDialog.Bottom - rctDialog.Top;

                    int dx = rctPanel.Left - rctDialog.Left;
                    int dy = rctPanel.Top - rctDialog.Top;
                    mainCntrl.Location =
                        new System.Drawing.Point( dx, dy );

                    mainCntrl.Width = w - dx;
                    mainCntrl.Height = h - dy;
                    break;
                }

            return pi.CallWindowProc( oldPanelProc, panelPtr, msg, wPar, lPar );
            }

        if ( hWnd == dialogHandle )
            {
            switch ( ( pi.WM ) msg )
                {
                case pi.WM.GETTEXTLENGTH:
                    return ( IntPtr ) newCaption.Length;

                case pi.WM.SETTEXT:
                    return IntPtr.Zero;

                case pi.WM.DESTROY:
                    dialogHandle = IntPtr.Zero;

                    pi.SetParent( mainCntrl.Handle, IntPtr.Zero );
                    mainCntrl.Hide();

                    System.Threading.Thread.Sleep( 1 );
                    break;

                case pi.WM.GETTEXT:
                    System.Runtime.InteropServices.Marshal.Copy(
                        newCaption, 0, lPar, newCaption.Length );

                    return ( IntPtr ) newCaption.Length;
                }
            }

        return pi.CallWindowProc( oldDialogWndProc, hWnd, msg, wPar, lPar );
        }


    /// <summary>
    /// Конструктор с параметрами окна, содержимое которого будет 
    /// изменена на пользовательское.
    /// </summary>
    /// <param name="mainCntrl">
    /// Визуальный пользовательский компонент, отображающийся на форме.
    /// </param>
    /// <param name="caption">
    /// Новый заголовок окна.
    /// </param>
    /// <param name="windowName">
    /// Заголовок окна (окна-основы), содержимое которого будет изменена на 
    /// пользовательское.
    /// </param>
    /// <param name="panelDlgItemId">
    /// Идентификатор панели-контейнера окна-основы.
    /// </param>
    /// <param name="dialogDlgItemId">
    ///  Идентификатор диалогового представления окна-основы.
    /// </param>
    /// <param name="panelToHideDlgItemId">
    /// Идентификатор панели с элементами среды Eplan, которую необходимо
    /// скрыть.
    /// </param>
    /// <param name="menuClickId">
    /// Идентификатор пункта меню для вызова окна-основы.
    /// </param>
    public EplanWindowWrapper( Control mainCntrl, 
        string caption, string windowName = "Пространство листа",
        int panelDlgItemId = 0xE81F, int dialogDlgItemId = 0x3458,
        int panelToHideDlgItemId = 0xBC2, int menuClickId = 35506 )
        {
        this.mainCntrl = mainCntrl;
        mainCntrl.Hide();

        newCaptionStr = caption;
        newCaption = System.Text.Encoding.GetEncoding( 1251 ).GetBytes(
            caption + '\0' );

        this.windowName = windowName;
        this.panelDlgItemId = panelDlgItemId;
        this.dialogDlgItemId = dialogDlgItemId;
        this.panelToHideDlgItemId = panelToHideDlgItemId;
        this.menuClickId = menuClickId;

        newWndProc = DialogWndProc;
        }


    /// <summary>
    /// Инициализация формы данными для редактирования.
    ///
    /// Так как данная форма отображается как внутреннее окно, то алгоритм
    /// следующий:
    /// 1 Поиск окна (по умолчанию "Пространство листа", меню Пространство
    /// листа -> Навигатор).
    /// 1.1 Поиск плавающего представления: через FindWindowByCaption ,
    /// потом для поиска панели и диалога DlgItemId (0xE81F - базовая панель,
    /// 0x3458 - диалог). Если окно найдено, то переходим к 4, иначе к 1.2.
    /// 1.2 Поиск закрепленного представления: через GetDlgItem для всех 
    /// дочерних окон (GetChildWindows) приложения Eplan по DlgItemId 
    /// (0x3458 - диалог).
    /// Если окно найдено, то переходим к 4, иначе к 2.
    /// 2 Симулируем нажатие пункта меню (Пространство листа -> Навигатор)
    /// для его отображения.
    /// 3 Повторяем поиск окна (1.1 и 1.2). Если окно не найдено выводим
    /// сообщение об ошибке, завершаем редактирование, иначе к 4.
    /// 4 Скрываем панель с элементами управления Eplan'а
    /// (GetDlgItem, 0xBC2 - родительская панель, ShowWindow).
    /// 5. Переносим на найденное окно свои элементы (SetParent) и подгоняем
    /// их размеры и позицию.
    /// 6. Устанавливаем свою оконную процедуру (для изменения размеров
    /// своих элементов, обработки закрытия окна).
    /// </summary>
    public void ShowDlg()
        {
        if ( mainCntrl.Visible )
            {
            return;
            }

        bool isDocked = false;
        System.Diagnostics.Process oCurrent =
            System.Diagnostics.Process.GetCurrentProcess();

        IntPtr res = pi.FindWindowByCaption( IntPtr.Zero, windowName );//1.1
        if ( res != IntPtr.Zero )
            {
            res = pi.GetDlgItem( res, panelDlgItemId );

            wndHandle = pi.GetParent( res );
            dialogHandle = pi.GetDlgItem( res, dialogDlgItemId );
            }
        else                                                            //1.2
            {
            System.Collections.Generic.List<IntPtr> resW = 
                pi.GetChildWindows( oCurrent.MainWindowHandle );
            foreach ( IntPtr panel in resW )
                {
                dialogHandle = pi.GetDlgItem( panel, dialogDlgItemId );
                if ( dialogHandle != IntPtr.Zero )
                    {
                    isDocked = true;
                    res = dialogHandle;
                    break;
                    }
                }

            if ( res == IntPtr.Zero )
                {
                pi.SendMessage( oCurrent.MainWindowHandle,
                    ( uint ) pi.WM.COMMAND, menuClickId, 0 );           //2

                res = pi.FindWindowByCaption( IntPtr.Zero, windowName );//3

                if ( res != IntPtr.Zero )
                    {
                    res = pi.GetDlgItem( res, panelDlgItemId );

                    wndHandle = pi.GetParent( res );
                    dialogHandle = pi.GetDlgItem( res, dialogDlgItemId );
                    }
                else
                    {
                    resW = pi.GetChildWindows( oCurrent.MainWindowHandle );
                    foreach ( IntPtr panel in resW )
                        {
                        dialogHandle = pi.GetDlgItem( panel, dialogDlgItemId );
                        if ( dialogHandle != IntPtr.Zero )
                            {
                            isDocked = true;
                            break;
                            }
                        }

                    if ( dialogHandle == IntPtr.Zero )
                        {
                        System.Windows.Forms.MessageBox.Show(
                            "Не удалось найти окно!" );
                        return;
                        }
                    }
                }
            }

        panelPtr = pi.GetDlgItem( dialogHandle, panelToHideDlgItemId ); //4
        if ( panelPtr == IntPtr.Zero )
            {
            System.Windows.Forms.MessageBox.Show( "Не удалось скрыть окно!" );
            return;
            }

        pi.ShowWindow( panelPtr, 0 );
                
        pi.SetParent( mainCntrl.Handle, dialogHandle );                 //5           
        mainCntrl.Show();

        int dy = 0;
        if ( isDocked )
            {
            dy = 17;            
            }

        pi.RECT dialogRect;
        pi.GetWindowRect( dialogHandle, out dialogRect );
                
        mainCntrl.Location = new System.Drawing.Point( 0, dy );

        int w = dialogRect.Right - dialogRect.Left;
        int h = dialogRect.Bottom - dialogRect.Top - dy;

        mainCntrl.Width = w;
        mainCntrl.Height = h;

        oldDialogWndProc = pi.SetWindowLong( dialogHandle, pi.GWL_WNDPROC,
            newWndProc );
        oldPanelProc = pi.SetWindowLong( panelPtr, pi.GWL_WNDPROC, 
            newWndProc );

        pi.SetWindowText( dialogHandle, newCaptionStr );
        pi.SetWindowText( wndHandle, newCaptionStr );
        }
    }

/// <summary>
/// Platform Invoke functions.
/// </summary>
public class pi
    {
    /// <summary>
    /// Window procedure.
    /// </summary>
    public delegate IntPtr Win32WndProc( IntPtr hWnd, int msg, int wParam,
        IntPtr lParam );

    public const int GWL_WNDPROC = -4;

    /// <summary>
    /// Changes an attribute of the specified window. The function also sets
    /// the 32-bit (long) value at the specified offset into the extra window
    /// memory.
    /// </summary>
    /// <param name="hWnd">
    /// A handle to the window and, indirectly, the class to which the window
    /// belongs.
    /// </param>
    /// <param name="nIndex">The zero-based offset to the value to be set.
    /// Valid values are in the range zero through the number of bytes of
    /// extra window memory, minus the size of an integer. To set any other 
    /// value, specify one of the following values: GWL_EXSTYLE,
    /// GWL_HINSTANCE, GWL_ID, GWL_STYLE, GWL_USERDATA, GWL_WNDPROC 
    /// </param>
    /// <param name="dwNewLong">The replacement value.</param>
    /// <returns>
    /// If the function succeeds, the return value is the previous value of 
    /// the specified 32-bit integer. If the function fails, the return value
    /// is zero. To get extended error information, call GetLastError.
    /// </returns>
    [DllImport( "user32" )]
    public static extern IntPtr SetWindowLong( IntPtr hWnd, int nIndex,
        Win32WndProc newProc );

    /// <summary>
    /// Set window text.
    /// </summary>
    [DllImport( "user32.dll", SetLastError = true, CharSet = CharSet.Auto )]
    public static extern bool SetWindowText( IntPtr hwnd, String lpString );

    /// <summary>
    /// Find window by Caption only. Note you must pass IntPtr.
    /// Zero as the first parameter.
    /// </summary>
    [DllImport( "user32.dll", EntryPoint = "FindWindow", SetLastError = true )]
    public static extern System.IntPtr FindWindowByCaption( IntPtr ZeroOnly, 
        string lpWindowName );

    /// <summary>
    /// Windows Messages
    /// Defined in winuser.h from Windows SDK v6.1
    /// Documentation pulled from MSDN.
    /// </summary>
    public enum WM : uint
        {
        /// <summary>
        /// The WM_NULL message performs no operation. An application sends
        /// the WM_NULL message if it wants to post a message that the 
        /// recipient window will ignore.
        /// </summary>
        NULL = 0x0000,
        /// <summary>
        /// The WM_CREATE message is sent when an application requests that
        /// a window be created by calling the CreateWindowEx or CreateWindow
        /// function. (The message is sent before the function returns.) 
        /// The window procedure of the new window receives this message
        /// after the window is created, but before the window becomes visible.
        /// </summary>
        CREATE = 0x0001,
        /// <summary>
        /// The WM_DESTROY message is sent when a window is being destroyed.
        /// It is sent to the window procedure of the window being destroyed
        /// after the window is removed from the screen.
        /// This message is sent first to the window being destroyed and then
        /// to the child windows (if any) as they are destroyed. During the
        /// processing of the message, it can be assumed that all child
        /// windows still exist.
        /// /// </summary>
        DESTROY = 0x0002,
        /// <summary>
        /// The WM_MOVE message is sent after a window has been moved.
        /// </summary>
        MOVE = 0x0003,
        /// <summary>
        /// The WM_SIZE message is sent to a window after its size has changed.
        /// </summary>
        SIZE = 0x0005,
        //...
        /// <summary>
        /// An application sends a WM_SETTEXT message to set the text of a 
        /// window.
        /// </summary>
        SETTEXT = 0x000C,
        /// <summary>
        /// An application sends a WM_GETTEXT message to copy the text that
        /// corresponds to a window into a buffer provided by the caller.
        /// </summary>
        GETTEXT = 0x000D,
        /// <summary>
        /// An application sends a WM_GETTEXTLENGTH message to determine the
        /// length, in characters, of the text associated with a window.
        /// </summary>
        GETTEXTLENGTH = 0x000E,
        //...
        /// <summary>
        /// The WM_COMMAND message is sent when the user selects a command
        /// item from a menu, when a control sends a notification message to
        /// its parent window, or when an accelerator keystroke is translated.
        /// </summary>
        COMMAND = 0x0111,
        }
    
    /// <summary>
    /// Get window parent.
    /// </summary>
    [DllImport( "user32.dll", ExactSpelling = true, CharSet = CharSet.Auto )]
    public static extern IntPtr GetParent( IntPtr hWnd );

    /// <summary>
    /// Set window parent.
    /// </summary>
    [DllImport( "user32.dll", SetLastError = true )]
    public static extern IntPtr SetParent( IntPtr hWndChild,
        IntPtr hWndNewParent );

    /// <summary>
    /// Windows rectangle structure.
    /// </summary>
    [StructLayout( LayoutKind.Sequential )]
    public struct RECT
        {
        public int Left;        // x position of upper-left corner
        public int Top;         // y position of upper-left corner
        public int Right;       // x position of lower-right corner
        public int Bottom;      // y position of lower-right corner
        }

    /// <summary>
    /// Get window rectangle.
    /// </summary>
    [DllImport( "user32.dll" )]
    [return: MarshalAs( UnmanagedType.Bool )]
    public static extern bool GetWindowRect( IntPtr hWnd, out RECT lpRect );

    /// <summary>
    /// Calls window procedure.
    /// </summary>
    [DllImport( "user32.dll" )]
    public static extern IntPtr CallWindowProc( IntPtr lpPrevWndFunc,
        IntPtr hWnd, int Msg, int wParam, IntPtr lParam );

    /// <summary>
    /// Gets dialog item by its ID.
    /// </summary>
    [DllImport( "user32.dll" )]
    public static extern IntPtr GetDlgItem( IntPtr hDlg, int nIDDlgItem );

    /// <summary>
    /// Delegate for the EnumChildWindows method
    /// </summary>
    /// <param name="hWnd">Window handle</param>
    /// <param name="parameter">
    /// Caller-defined variable; we use it for a pointer to our list
    /// </param>
    /// <returns>True to continue enumerating, false to bail.</returns>
    public delegate bool EnumWindowProc( IntPtr hWnd, IntPtr parameter );

    /// <summary>
    /// Returns a list of child windows
    /// </summary>
    /// <param name="parent">Parent of the windows to return</param>
    /// <returns>List of child windows</returns>
    public static System.Collections.Generic.List<IntPtr> GetChildWindows( 
        IntPtr parent )
        {
        System.Collections.Generic.List<IntPtr> result = 
            new System.Collections.Generic.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;
        }

    /// <summary>
    /// Callback method to be used when enumerating windows.
    /// </summary>
    /// <param name="handle">Handle of the next window</param>
    /// <param name="pointer">
    /// Pointer to a GCHandle that holds a reference to the list to fill
    /// </param>
    /// <returns>True to continue the enumeration, false to bail</returns>
    public static bool EnumWindow( IntPtr handle, IntPtr pointer )
        {
        System.Runtime.InteropServices.GCHandle gch =
            System.Runtime.InteropServices.GCHandle.FromIntPtr( pointer );
        System.Collections.Generic.List<IntPtr> list = 
            gch.Target as System.Collections.Generic.List<IntPtr>;
        if ( list == null )
            {
            throw new InvalidCastException( 
                "GCHandle Target could not be cast as List<IntPtr>" );
            }
        list.Add( handle );
        
        return true;
        }

    [DllImport( "user32" )]
    [return: MarshalAs( UnmanagedType.Bool )]
    public static extern bool EnumChildWindows( IntPtr window, 
        EnumWindowProc callback, IntPtr i );

    [DllImport( "user32.dll" )]
    public static extern IntPtr SendMessage( IntPtr hWnd, UInt32 Msg,
        Int32 wParam, Int32 lParam );

    [DllImport( "user32.dll" )]
    [return: MarshalAs( UnmanagedType.Bool )]
    public static extern bool ShowWindow( IntPtr hWnd, Int32 nCmdShow );
    }
Основная функциональность реализована в классе EplanWindowWrapper. При создании экземпляра класса необходимо указать все необходимое для корректной замены содержимого стандартного окна. Для получения необходимых идентификаторов можно использовать утилиту Spy++, которая входит в состав Visual Studio. Для удобства, все необходимые обертки WinApi функций (Platform Invoke) вынесены в отдельный класс pi.
Результат работы такой реализации приведен ниже.
Окно дополнения - встроенное.png

Видим, что теперь окно ведет себя также, как и любое другое стандартное окно среды, но содержит пользовательскую информации, и инженеру по автоматизации максимально удобно с ним теперь работать.
У вас нет необходимых прав для просмотра вложений в этом сообщении.
Аватара пользователя

Alexander_Pavlov
знаток Eplan
знаток Eplan
Сообщения: 97
Зарегистрирован: 09 апр 2014, 09:55
Имя: Александр Павлов
Страна: РФ
город/регион: Санкт Петербург
Благодарил (а): 12 раз
Поблагодарили: 6 раз

Re: Скрипты и API

Сообщение Alexander_Pavlov »

Eplan.EplApi.DataModel.Graphics: ViewPlacement - обзор модели, ViewPart - деталь на обзоре модели.
Делаю только первые шаги, поэтому, возможно, глупый вопрос.

Класс ViewPart имеет свойство SubPlacements, в help на API это есть. В References я добавил Eplan.EplApi.DataModel и прописал using Eplan.EplApi.DataModel в свой Add in, но получаю ошибку

'Eplan.EplApi.DataModel.ViewPart' does not contain a definition for 'SubPlacements' and no extension method 'SubPlacements' accepting a first argument of type 'Eplan.EplApi.DataModel.ViewPart' could be found (are you missing a using directive or an assembly reference?)
foreach (Placement item in oVP.SubPlacements)


Может в студии надо ещё где то открыть доступ?
Последний раз редактировалось Alexander_Pavlov 08 дек 2015, 11:22, всего редактировалось 1 раз.
Eplan 2.6 сборка 10395

ids
знаток Eplan
знаток Eplan
Сообщения: 25
Зарегистрирован: 17 сен 2015, 09:25
Имя: Иванюк Дмитрий Сергеевич
Страна: РБ
город/регион: Брест
Благодарил (а): 2 раза

Re: Скрипты и API

Сообщение ids »

Alexander_Pavlov писал(а):
Eplan.EplApi.DataModel.Graphics: ViewPlacement - обзор модели, ViewPart - деталь на обзоре модели.
Делаю только первые шаги, поэтому, возможно, глупый вопрос.

Класс ViewPart имеет свойство SubPlacements, в help на API это есть. В References я добавил Eplan.EplApi.DataModel и прописал using Eplan.EplApi.DataModel в свой Add in, но получаю ошибку

'Eplan.EplApi.DataModel.ViewPart' does not contain a definition for 'SubPlacements' and no extension method 'SubPlacements' accepting a first argument of type 'Eplan.EplApi.DataModel.ViewPart' could be found (are you missing a using directive or an assembly reference?)

Может в студии надо ещё где то открыть доступ?
Класс Eplan.EplApi.DataModel.ViewPart действительно не содержит свойство SubPlacements. Откройте данный класс в IDE Visual Studio и посмотрите все его свойства и методы. А вот класс Eplan.EplApi.DataModel.Graphics.ViewPlacement - имеет. Может его необходимо использовать?

ids
знаток Eplan
знаток Eplan
Сообщения: 25
Зарегистрирован: 17 сен 2015, 09:25
Имя: Иванюк Дмитрий Сергеевич
Страна: РБ
город/регион: Брест
Благодарил (а): 2 раза

Re: Скрипты и API

Сообщение ids »

Madwolf писал(а):Возникла следующая проблема: нужно записать в свойства функции данные, причем хочу обращаться к ней по ID и индексу, например: oFunction.Properties[20901, 1].Set(). Еплан постоянно ругается на неправильный индекс, хотя там 100% содержатся данные по данному ID и индексу. При этом oFunction.Properties.FUNC_SUPPLEMENTARYFIELD[1].Set() прекрасно работает.

Для чего нужно? Для того чтобы можно было произвольно копировать некоторые выбранные свойства в другие, т.к. некоторые отчеты не поддерживают сортировку по определенным свойствам. Хотел поручить выбор ID и индекса конечному пользователю.

Может кто уже решал данный вопрос?
А можно поподробнее о возникающих ошибках - у меня ваш пример отлично работает.
Аватара пользователя

Alexander_Pavlov
знаток Eplan
знаток Eplan
Сообщения: 97
Зарегистрирован: 09 апр 2014, 09:55
Имя: Александр Павлов
Страна: РФ
город/регион: Санкт Петербург
Благодарил (а): 12 раз
Поблагодарили: 6 раз

Re: Скрипты и API

Сообщение Alexander_Pavlov »

foreach (Placement item in oVP.Group.SubPlacements) - решило вопрос, но далее получил сообщение "Объект не был заблокирован перед обращением для записи ". Объект заблокировал, ошибка исчезла, но толщина не меняется, хотя написал
GraphicalPlacement oGPl = item as GraphicalPlacement;
oPen = oGPl.Layer.Pen;
oGPl.LockObject();
oPen.Width = 2;
oGPl.Pen = oPen;
Eplan 2.6 сборка 10395

Madwolf
знаток Eplan
знаток Eplan
Сообщения: 1454
Зарегистрирован: 17 окт 2012, 11:24
Имя: Виталий
Страна: Беларусь
город/регион: Минск
Благодарил (а): 60 раз
Поблагодарили: 181 раз

Re: Скрипты и API

Сообщение Madwolf »

Во-первый LockObject() не всегда решает вопроса. Я просто открываю проект в эксклюзивном режиме, все работает, либо как уже писали выше, лучше заблокировать весь проект. Во-вторых, если уже блокируете объект, убедитесь что он заблокирован, а то смысла от этого кода -ноль. У меня для моего примера все работает отлично, сразу видна толщина линий, убедитесь что в слое стоит толщина отличная от 0, поставьте 0,5. Какой-слой я уже не помню точно (вроде бы EPLAN577 Графика.Обзор модели), лучше посмотрите под отладкой.

Madwolf
знаток Eplan
знаток Eplan
Сообщения: 1454
Зарегистрирован: 17 окт 2012, 11:24
Имя: Виталий
Страна: Беларусь
город/регион: Минск
Благодарил (а): 60 раз
Поблагодарили: 181 раз

Re: Скрипты и API

Сообщение Madwolf »

Alexander_Pavlov писал(а):
Eplan.EplApi.DataModel.Graphics: ViewPlacement - обзор модели, ViewPart - деталь на обзоре модели.
Делаю только первые шаги, поэтому, возможно, глупый вопрос.

Класс ViewPart имеет свойство SubPlacements, в help на API это есть. В References я добавил Eplan.EplApi.DataModel и прописал using Eplan.EplApi.DataModel в свой Add in, но получаю ошибку

'Eplan.EplApi.DataModel.ViewPart' does not contain a definition for 'SubPlacements' and no extension method 'SubPlacements' accepting a first argument of type 'Eplan.EplApi.DataModel.ViewPart' could be found (are you missing a using directive or an assembly reference?)
foreach (Placement item in oVP.SubPlacements)


Может в студии надо ещё где то открыть доступ?
Ну так студия и пишет что пропущена директива Using, либо в референсы не добавлено. Проще всего - кликаем ПКМ на нужный метод, или название класса или еще что-то на что студия ругается, там если будет Resolve, то значит просто нет в директивах этого пространства имен, выбираем Resolve, и студия сама добавит нужное, если же Resolve нет, то значит нет в референсах.
Вообще SubPlacements это свойство Block (а ViewPart наследуется от него).

Madwolf
знаток Eplan
знаток Eplan
Сообщения: 1454
Зарегистрирован: 17 окт 2012, 11:24
Имя: Виталий
Страна: Беларусь
город/регион: Минск
Благодарил (а): 60 раз
Поблагодарили: 181 раз

Re: Скрипты и API

Сообщение Madwolf »

ids писал(а):
Madwolf писал(а):Возникла следующая проблема: нужно записать в свойства функции данные, причем хочу обращаться к ней по ID и индексу, например: oFunction.Properties[20901, 1].Set(). Еплан постоянно ругается на неправильный индекс, хотя там 100% содержатся данные по данному ID и индексу. При этом oFunction.Properties.FUNC_SUPPLEMENTARYFIELD[1].Set() прекрасно работает.
А можно поподробнее о возникающих ошибках - у меня ваш пример отлично работает.
Вопрос может быть в том, что фактически код выглядит не так, а ID и индексы я передаю "на лету" из формы, может быть в этом и проблема. В общем я пока оставил на выбор пользователю только индекс, все работает.
Аватара пользователя

Alexander_Pavlov
знаток Eplan
знаток Eplan
Сообщения: 97
Зарегистрирован: 09 апр 2014, 09:55
Имя: Александр Павлов
Страна: РФ
город/регион: Санкт Петербург
Благодарил (а): 12 раз
Поблагодарили: 6 раз

Re: Скрипты и API

Сообщение Alexander_Pavlov »

Resolve не предлагает, в References (может быть что то лишнее):
References.jpg
У вас нет необходимых прав для просмотра вложений в этом сообщении.
Eplan 2.6 сборка 10395

Madwolf
знаток Eplan
знаток Eplan
Сообщения: 1454
Зарегистрирован: 17 окт 2012, 11:24
Имя: Виталий
Страна: Беларусь
город/регион: Минск
Благодарил (а): 60 раз
Поблагодарили: 181 раз

Re: Скрипты и API

Сообщение Madwolf »

Вероятная причина - я смотрю Вы используете 2.1, а я 2.4, поэтому возможно и нет даже этих методов в API.
Аватара пользователя

Alexander_Pavlov
знаток Eplan
знаток Eplan
Сообщения: 97
Зарегистрирован: 09 апр 2014, 09:55
Имя: Александр Павлов
Страна: РФ
город/регион: Санкт Петербург
Благодарил (а): 12 раз
Поблагодарили: 6 раз

Re: Скрипты и API

Сообщение Alexander_Pavlov »

Да, именно в этом причина! Работает только в 2.4. требуется Eplan.EplApi.Datamodel ревизия 8366!
Eplan 2.6 сборка 10395

Dmitry_S
новенький
новенький
Сообщения: 1
Зарегистрирован: 10 окт 2013, 09:13
Имя: Шестаков Дмитрий Владимирович
Страна: Россия
город/регион: Санкт-Петербург

Re: Скрипты и API

Сообщение Dmitry_S »

Доброго времени суток. Возможно ли задать слои создаваемому объекту (линия, текст)?

Madwolf
знаток Eplan
знаток Eplan
Сообщения: 1454
Зарегистрирован: 17 окт 2012, 11:24
Имя: Виталий
Страна: Беларусь
город/регион: Минск
Благодарил (а): 60 раз
Поблагодарили: 181 раз

Re: Скрипты и API

Сообщение Madwolf »

Конечно, пример не совсем удачный, но, думаю, для понимания сути подойдет:

Код: Выделить всё


        public void SetLayerToPlacements(List<GraphicalPlacement> oListPlacement)
        {
            string layerName = "EPLAN102";
                GraphicalLayerTable oGLT = Project.LayerTable;
                if (oGLT.Layers[2].Name == layerName)
                {
                    for (int i = 0; i < oListPlacement.Count; i++)
                    {
                        (oListPlacement[i]).Layer = oGLT.Layers[2];
                    }
            }

Madwolf
знаток Eplan
знаток Eplan
Сообщения: 1454
Зарегистрирован: 17 окт 2012, 11:24
Имя: Виталий
Страна: Беларусь
город/регион: Минск
Благодарил (а): 60 раз
Поблагодарили: 181 раз

Re: Скрипты и API

Сообщение Madwolf »

Чтобы изменить функцию (или другой объект) в тот момент, когда окно редактирования этой же функции (объекта) открыто, поможет следующий код:

Код: Выделить всё

                                        using (Transaction txn = new TransactionManager().CreateTransaction())
                                        {
                                            if (new TransactionManager().IsEplanTransactionRunning)
                                            {
                                                // add some code here
                                            }
                                            txn.Commit();
                                        }              

ids
знаток Eplan
знаток Eplan
Сообщения: 25
Зарегистрирован: 17 сен 2015, 09:25
Имя: Иванюк Дмитрий Сергеевич
Страна: РБ
город/регион: Брест
Благодарил (а): 2 раза

Re: Скрипты и API

Сообщение ids »

Madwolf писал(а):Чтобы изменить функцию (или другой объект) в тот момент, когда окно редактирования этой же функции (объекта) открыто, поможет следующий код:

Код: Выделить всё

                                        using (Transaction txn = new TransactionManager().CreateTransaction())
                                        {
                                            if (new TransactionManager().IsEplanTransactionRunning)
                                            {
                                                // add some code here
                                            }
                                            txn.Commit();
                                        }              
Добрый день,
я бы написал так:

Код: Выделить всё

            TransactionManager txnM = new TransactionManager();
            using ( Transaction txn = txnM.CreateTransaction() )
                {
                if ( txnM.IsEplanTransactionRunning )
                    {
                    // add some code here
                    }
                txn.Commit();
                }

хотелось бы также услышать про реальный сценарий такой необходимости.

Madwolf
знаток Eplan
знаток Eplan
Сообщения: 1454
Зарегистрирован: 17 окт 2012, 11:24
Имя: Виталий
Страна: Беларусь
город/регион: Минск
Благодарил (а): 60 раз
Поблагодарили: 181 раз

Re: Скрипты и API

Сообщение Madwolf »

Дмитрий Сергеевич здравствуйте.
Я же написал для чего это нужно: Чтобы изменить функцию (или другой объект) в тот момент, когда окно редактирования этой же функции (объекта) открыто.
Ваш пример не подойдет, т.к. нельзя объявить TransactionManager вне директивы using, по всей видимости. Студия по этому поводу выругалась:
"Embedded statement cannot be a declaration or labeled statement"

ids
знаток Eplan
знаток Eplan
Сообщения: 25
Зарегистрирован: 17 сен 2015, 09:25
Имя: Иванюк Дмитрий Сергеевич
Страна: РБ
город/регион: Брест
Благодарил (а): 2 раза

Re: Скрипты и API

Сообщение ids »

Madwolf писал(а):Дмитрий Сергеевич здравствуйте.
Я же написал для чего это нужно: Чтобы изменить функцию (или другой объект) в тот момент, когда окно редактирования этой же функции (объекта) открыто.
Ваш пример не подойдет, т.к. нельзя объявить TransactionManager вне директивы using, по всей видимости. Студия по этому поводу выругалась:
"Embedded statement cannot be a declaration or labeled statement"
Странно - у меня все собралось без ошибок...
Новый вопрос - а у Вас был опыт реализации Undo действия для Eplan?

Madwolf
знаток Eplan
знаток Eplan
Сообщения: 1454
Зарегистрирован: 17 окт 2012, 11:24
Имя: Виталий
Страна: Беларусь
город/регион: Минск
Благодарил (а): 60 раз
Поблагодарили: 181 раз

Re: Скрипты и API

Сообщение Madwolf »

Да, я очень часто использую Undo, т.к. без него вставка графических объектов (линии, прямоугольники и т.п.) через АПИ приводит к тому, что все имеющиеся обратные шаги стираются.
Вроде бы все в хелпе описано детально, но на всякий случай тут еще примерчик:

Код: Выделить всё

// создать шаг возврата
            using (UndoStep undo = new UndoManager().CreateUndoStep())
            {
                // тут код который нужно выполнить, например создать выноску, обычно я сюда метод или несколько методов вставляю.
                undo.SetUndoDescription("Выноска API");
            }
Закрыто

Вернуться в «Eplan API»