Инструменты пользователя

Инструменты сайта


developers:tutorial:undoredo

Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

Предыдущая версия справа и слева Предыдущая версия
Следующая версия
Предыдущая версия
developers:tutorial:undoredo [2019/03/23 20:10]
vasya
developers:tutorial:undoredo [2022/03/15 19:16] (текущий)
proxor
Строка 5: Строка 5:
 Классы,​ используемые для реализации моделей с поддержкой истории изменений расположены в пространстве имён [[developers:​references:​topomatic.foundationclasses.undo|Topomatic.FoundationClasses.Undo]]. Классы,​ используемые для реализации моделей с поддержкой истории изменений расположены в пространстве имён [[developers:​references:​topomatic.foundationclasses.undo|Topomatic.FoundationClasses.Undo]].
  
-Для реализации поддержки истории изменений в Вашей ​модели необходимо:​+Для реализации поддержки истории изменений в модели необходимо:​
  
   * Все изменяемые поля должны быть обвёрнуты в классы [[developers:​references:​topomatic.foundationclasses.undo.basetransactablefield_1|BaseTransactableField<​T>​]]. ​   * Все изменяемые поля должны быть обвёрнуты в классы [[developers:​references:​topomatic.foundationclasses.undo.basetransactablefield_1|BaseTransactableField<​T>​]]. ​
   * Изменяемые списки должны быть реализованы через [[developers:​references:​topomatic.foundationclasses.undo.basetransactablelist_1|BaseTransactableList<​T>​]]   * Изменяемые списки должны быть реализованы через [[developers:​references:​topomatic.foundationclasses.undo.basetransactablelist_1|BaseTransactableList<​T>​]]
   * Изменяемые словари должны быть реализованы через [[developers:​references:​topomatic.foundationclasses.undo.transactabledictionary_2|TransactableDictionary<​T,​ U>]]   * Изменяемые словари должны быть реализованы через [[developers:​references:​topomatic.foundationclasses.undo.transactabledictionary_2|TransactableDictionary<​T,​ U>]]
-  * Содержащие поля данных,​ списки и словари объекты должны поддерживать интерфейс [[developers:​references:​topomatic.foundationclasses.undo.itransactable|ITransactable]] 
  
 <​note>​Если необходимо отслеживать изменение значения полей или содержимого списков,​ то вместо [[developers:​references:​topomatic.foundationclasses.undo.basetransactablefield_1|BaseTransactableField<​T>​]] и [[developers:​references:​topomatic.foundationclasses.undo.basetransactablelist_1|BaseTransactableList<​T>​]] нужно использовать [[developers:​references:​topomatic.foundationclasses.undo.transactablefield_1|TransactableField<​T>​]] и [[developers:​references:​topomatic.foundationclasses.undo.transactablelist_1|TransactableList<​T>​]]. Эти классы поддерживают события позволяющие следить за изменением своего содержимого.</​note>​ <​note>​Если необходимо отслеживать изменение значения полей или содержимого списков,​ то вместо [[developers:​references:​topomatic.foundationclasses.undo.basetransactablefield_1|BaseTransactableField<​T>​]] и [[developers:​references:​topomatic.foundationclasses.undo.basetransactablelist_1|BaseTransactableList<​T>​]] нужно использовать [[developers:​references:​topomatic.foundationclasses.undo.transactablefield_1|TransactableField<​T>​]] и [[developers:​references:​topomatic.foundationclasses.undo.transactablelist_1|TransactableList<​T>​]]. Эти классы поддерживают события позволяющие следить за изменением своего содержимого.</​note>​
  
 +Содержащие поля данных,​ списки и словари объекты должны поддерживать интерфейс [[developers:​references:​topomatic.foundationclasses.undo.itransactable|ITransactable]].  ​
 +
 +Для совершения изменений модели необходимо выполнить следующую последовательность действий:​
 +
 +  * Вызвать у родительского объекта,​ в котором происходят изменения,​ метод [[developers:​references:​topomatic.foundationclasses.iupdatable.beginupdate|BeginUpdate]] перед началом изменений.
 +  * Произвести необходимые изменения используя [[developers:​references:​topomatic.foundationclasses.undo.basetransactablefield_1.value|Value]] у [[developers:​references:​topomatic.foundationclasses.undo.basetransactablefield_1|BaseTransactableField<​T>​]] или изменив содержимое контейнеров [[developers:​references:​topomatic.foundationclasses.undo.basetransactablelist_1|BaseTransactableList<​T>​]] и [[developers:​references:​topomatic.foundationclasses.undo.transactabledictionary_2|TransactableDictionary<​T,​ U>]]
 +  * Вызвать у родительского объекта,​ в котором происходят изменения,​ метод [[developers:​references:​topomatic.foundationclasses.iupdatable.endupdate|EndUpdate]] после завершения изменений.
 +
 +<​note>​При изменении только одного поля [[developers:​references:​topomatic.foundationclasses.undo.basetransactablefield_1|BaseTransactableField<​T>​]] или одного элемента контейнеров [[developers:​references:​topomatic.foundationclasses.undo.basetransactablelist_1|BaseTransactableList<​T>​]] и [[developers:​references:​topomatic.foundationclasses.undo.transactabledictionary_2|TransactableDictionary<​T,​ U>]] допустимо не вызывать методы [[developers:​references:​topomatic.foundationclasses.iupdatable.beginupdate|BeginUpdate]] и [[developers:​references:​topomatic.foundationclasses.iupdatable.endupdate|EndUpdate]]. В этом случае они будут вызваны автоматически перед началом и в конце изменения соответственно.</​note>​
 +
 +Для работы истории изменений,​ необходимо чтобы свойству [[developers:​references:​topomatic.foundationclasses.undo.itransactable.transactionmanager|TransactionManager]] интерфейса [[developers:​references:​topomatic.foundationclasses.undo.itransactable|ITransactable]] был назначен корректный экземпляр интерфейса [[developers:​references:​topomatic.foundationclasses.undo.itransactionmanager|ITransactionManager]].
 +Для этого все основные объекты программного комплекса [[http://​topomatic.ru|Топоматик Робур]] наследуются от базового класса [[developers:​references:​topomatic.foundationclasses.updatableobject|UpdatableObject]] и поддерживают интерфейс [[developers:​references:​topomatic.foundationclasses.iowned|IOwned]]. ​
 +<​note>​
 +Если необходимо отслеживать изменение объекта,​ при изменении каких либо дочерних элементов,​ то необходимо наследоваться от [[developers:​references:​topomatic.foundationclasses.undoobject|UndoObject]]. Это наследник от [[developers:​references:​topomatic.foundationclasses.updatableobject|UpdatableObject]] в котором дополнительно поддерживаются события [[developers:​references:​topomatic.foundationclasses.undoobject.changed|Changed]] и [[developers:​references:​topomatic.foundationclasses.undoobject.undo|Undo]]
 +</​note>​
 +
 +Класс [[developers:​references:​topomatic.foundationclasses.updatableobject|UpdatableObject]] отвечает за реализацию интерфейса [[developers:​references:​topomatic.foundationclasses.undo.itransactable|ITransactable]],​ а интерфейс [[developers:​references:​topomatic.foundationclasses.iowned|IOwned]] позволяет реализовать свойство [[developers:​references:​topomatic.foundationclasses.undo.itransactable.transactionmanager|TransactionManager]] запрашивая его по цепочке родителей с самого верхнего. В этом случае экземпляр [[developers:​references:​topomatic.foundationclasses.undo.itransactable.transactionmanager|TransactionManager]] назначается самому верхнему элементу в цепочке,​ обычно это наследник от [[developers:​references:​topomatic.foundationclasses.statecontrollerobject|StateControllerObject]],​ а все дочерние элементы получают его автоматически.
 +<​note>​Часто возникает ситуация,​ когда в модель необходимо внести какие либо изменения,​ а потом либо отменить внесенные изменения без записи в истории либо применить их. В таких случаях перед началом изменений нужно вызвать метод **BeginTransaction** а в конце в либо метод **Commit** чтобы применить изменения,​ либо метод **Rollback** для сброса изменений</​note>​
 +
 +Создайте новую [[developers:​tutorial:​gripsandsnaps|модель и видовой слой]] и подключите к программному комплексу [[http://​www.topomatic.ru|Топоматик Робур]].
 +
 +Измените класс Points.cs. Сделаем его наследником от [[developers:​references:​topomatic.foundationclasses.updatableobject|UpdatableObject]] и поддержим интерфейс [[developers:​references:​topomatic.foundationclasses.iowned|IOwned]],​ а список сохраняющий значения точек заменяем на [[developers:​references:​topomatic.foundationclasses.undo.basetransactablelist_1|BaseTransactableList<​T>​]].
 +<code csharp>
 +    //​Класс для хранения точек в нашей модели
 +    //​поддерживаем интерфейсы IStgSerializable для сохранения и IOwned для определения владельца списка точек
 +    //​Также поддерживаем интерфейс IObjectDisjoiner для привязок
 +    class Points : UpdatableObject,​ IStgSerializable,​ IOwned, IObjectDisjoiner
 +    {
 +        //​Поддержка цепочки родителей
 +        private Model m_Owner;
 +
 +        //​Список структур с поддержкой истории изменений
 +        private TransactableList<​Vector2D>​ m_Points;
 +
 +        public Points(Model owner)
 +        {
 +            m_Owner = owner;
 +            m_Points = new TransactableList<​Vector2D>​(this);​
 +        }
 +
 +        //​Метод для добавления точки в список
 +        public void AddPoint(Vector2D point)
 +        {
 +            m_Points.Add(point);​
 +        }
 +
 +        //​Метод для удаления точки из списка
 +        public void RemovePoint(int index)
 +        {
 +            m_Points.RemoveAt(index);​
 +        }
 +
 +        //​Метод для получения точки из списка
 +        public Vector2D this[int index]
 +        {
 +            get
 +            {
 +                return m_Points[index];​
 +            }
 +            set
 +            {
 +                m_Points[index] = value;
 +            }
 +        }
 +
 +        //​Количество точек в списке
 +        //​Свойство декорировано атрибутом DisplayName для отображения в инспекторе объектов
 +        [DisplayName("​Количество точек"​)]
 +        public int Count
 +        {
 +            get
 +            {
 +                return m_Points.Count;​
 +            }
 +        }
 +
 +        //​Владелец нашего списка точек, у нас это наша модель
 +        //​Свойство декорировано атрибутом Browsable чтобы исключить отображение в инспекторе объектов
 +        [Browsable(false)]
 +        public object Owner
 +        {
 +            get
 +            {
 +                return m_Owner;
 +            }
 +            set
 +            {
 +                //​Владелец назначается один раз на конструкторе объекта,​ назначение отдельно недопустимо
 +                throw new NotSupportedException();​
 +            }
 +        }
 +
 +        //​Реализация загрузки
 +        public void LoadFromStg(StgNode node)
 +        {
 +            //​обратите внимание,​ загрузку мы производим во внутренний список
 +            //это позволяет избежать записи значений в историю изменений
 +            m_Points.InnerList.Clear();​
 +            //При загрузке массива указывается тип составляющих массив значений
 +            var array = node.GetArray("​Values",​ StgType.Node);​
 +            for (int i = 0; i < array.Count;​ i++)
 +            {
 +                m_Points.InnerList.Add(Vector2D.LoadFromStg(array.GetNode(i)));​
 +            }
 +        }
 +
 +        //​Реализация сохранения
 +        public void SaveToStg(StgNode node)
 +        {
 +            //​Сохраняем значения в узел
 +            //​Сохраняем массив с указанием типа значений
 +            var array = node.AddArray("​Values",​ StgType.Node);​
 +            for (int i = 0; i < m_Points.Count;​ i++)
 +            {
 +                m_Points[i].SaveToStg(array.AddNode());​
 +            }
 +        }
 +
 +        //​Дополнительно перекрываем метод ToString() для отображения типа объекта в инстпекторе объектов
 +        public override string ToString()
 +        {
 +            return "​Точки модели";​
 +        }
 +
 +        //​Привязка ​ КОНЕЧНАЯ ТОЧКА
 +        public void GetEndPoint(ObjectsDisjointerArgs e, IList<​Vector3D>​ list)
 +        {
 +            for (int i = 0; i < m_Points.Count;​ i++)
 +            {
 +                list.Add(m_Points[i]);​
 +            }
 +        }
 +
 +        //​Привязка ​ ЦЕНТРАЛЬНАЯ ТОЧКА
 +        public void GetCenterPoint(ObjectsDisjointerArgs e, IList<​Vector3D>​ list)
 +        {
 +            //Do nothing
 +        }
 +
 +        //​Привязка ​ СЕРЕДИНА ОТРЕЗКА
 +        public void GetMiddlePoint(ObjectsDisjointerArgs e, IList<​Vector3D>​ list)
 +        {
 +            for (int i = 1; i < m_Points.Count;​ i++)
 +            {
 +                list.Add((m_Points[i - 1] + m_Points[i]) * 0.5);
 +            }
 +        }
 +
 +        //​Привязка ​ УЗЕЛ
 +        public void GetNodePoint(ObjectsDisjointerArgs e, IList<​Vector3D>​ list)
 +        {
 +            //Do nothing
 +        }
 +
 +        //​Привязка ​ КВАДРАНТ
 +        public void GetQuadrantPoint(ObjectsDisjointerArgs e, IList<​Vector3D>​ list)
 +        {
 +            //Do nothing
 +        }
 +
 +        //​Привязка ​ ТОЧКА ВСТАВКИ
 +        public void GetInsertionPoint(ObjectsDisjointerArgs e, IList<​Vector3D>​ list)
 +        {
 +            //Do nothing
 +        }
 +
 +        //​Привязка ​ БЛИЖАЙШАЯ ТОЧКА, КАСАТЕЛЬНАЯ и т.п.
 +        public void GetSegments(ObjectsDisjointerArgs e, IList<​ArcSegment>​ arcList, IList<​LineSegment>​ lineList)
 +        {
 +            for (int i = 1; i < m_Points.Count;​ i++)
 +            {
 +                lineList.Add( new LineSegment() { StartPoint = m_Points[i - 1], EndPoint = m_Points[i] } );
 +            }
 +        }
 +    }
 +</​code>​
 +Также изменяем Model.cs. Список содержащий экземпляры класса Points заменяем на [[developers:​references:​topomatic.foundationclasses.undo.basetransactablelist_1|BaseTransactableList<​T>​]]
 +<code csharp>
 +class Model : StateControllerObject,​ IStgSerializable
 +    {
 +        private bool m_ReadOnly = false;
 +
 +        //​Список объектов точек с поддержкой истории изменений
 +        private BaseTransactableList<​Points>​ m_Points;
 +
 +        public Model()
 +        {
 +            m_Points = new BaseTransactableList<​Points>​(this);​
 +        }
 +
 +        //​Метод для добавления списка точек в модель
 +        public Points Add()
 +        {
 +            var p = new Points(this);​
 +            m_Points.Add(p);​
 +            return p;
 +        }
 +
 +        //​Метод для получения списка точек из модели
 +        public Points this[int index]
 +        {
 +            get
 +            {
 +                return m_Points[index];​
 +            }
 +        }
 +
 +        //​Метод для удаления списка точек из модели
 +        public bool Remove(Points points)
 +        {
 +            return m_Points.Remove(points);​
 +        }
 +
 +        //​Количество списков в модели
 +        public int Count
 +        {
 +            get
 +            {
 +                return m_Points.Count;​
 +            }
 +        }
 +
 +        //флаг только для чтения
 +        public override bool ReadOnly
 +        {
 +            get
 +            {
 +                return m_ReadOnly;
 +            }
 +
 +            set
 +            {
 +                m_ReadOnly = value;
 +            }
 +        }
 +
 +        //​Загрузка из узла
 +        public void LoadFromStg(StgNode node)
 +        {
 +            m_Points.InnerList.Clear();​
 +            //При загрузке массива указывается тип составляющих массив значений
 +            var array = node.GetArray("​Points",​ StgType.Node);​
 +            for (int i = 0; i < array.Count;​ i++)
 +            {
 +                var p = new Points(this);​
 +                p.LoadFromStg(array.GetNode(i));​
 +                m_Points.InnerList.Add(p);​
 +            }
 +        }
 +
 +        //​Сохранение в узел
 +        public void SaveToStg(StgNode node)
 +        {
 +            //​Сохраняем значения в узел
 +            //​Сохраняем массив с указанием типа значений
 +            var array = node.AddArray("​Points",​ StgType.Node);​
 +            for (int i = 0; i < m_Points.Count;​ i++)
 +            {
 +                m_Points[i].SaveToStg(array.AddNode());​
 +            }
 +        }
 +    }
 +</​code>​
 +
 +Также вносим изменения в Module.cs в функцию редактирования модели. Теперь перед изменением значений мы вызываем метод [[developers:​references:​topomatic.foundationclasses.iupdatable.beginupdate|BeginUpdate]] а после метод [[developers:​references:​topomatic.foundationclasses.iupdatable.beginupdate|EndUpdate]]. Кроме того теперь нет необходимости выставлять свойство [[developers:​references:​topomatic.foundationclasses.statecontrollerobject.modified|Modified]] вручную,​ при изменении истории значение свойства определяется автоматически.
 +
 +<code csharp>
 +        [cmd("​edit_pointsmodel"​)]
 +        public void EditModel()
 +        {
 +            var cadView = CadView;
 +            if (cadView != null)
 +            {
 +                var model_layer = ModelLayer.GetModelLayer(cadView);​
 +                if (model_layer != null)
 +                {
 +                    //​список выбранных точек
 +                    var positions = new List<​Vector2D>​();​
 +                    //​делегат для динамической отрисовки
 +                    DrawCursorEvent dynamic_draw = delegate (CadPen pen, Vector3D vertex)
 +                    {
 +                        //если есть точки в списке,​ нужно их нарисовать и нарисовать линию
 +                        //от последней выбранной точки, до текущего положения курсора
 +                        if (positions.Count > 0)
 +                        {
 +                            pen.Color = Color.Lime;
 +                            pen.BeginDraw();​
 +                            try
 +                            {
 +                                for (int i = 1; i < positions.Count;​ i++)
 +                                {
 +                                    pen.DrawLine(positions[i - 1], positions[i]);​
 +                                }
 +                                pen.DrawLine(positions[positions.Count - 1], vertex.Pos);​
 +                            }
 +                            finally
 +                            {
 +                                pen.EndDraw();​
 +                            }
 +                        }
 +                    };
 +                    //​подписываемся на событие отрисовки
 +                    cadView.DynamicDraw += dynamic_draw;​
 +                    try
 +                    {
 +                        Vector3D pos;
 +                        //​просим пользователя указать несколько точек
 +                        while (CadCursors.GetPoint(cadView,​ out pos, "​Укажите точку"​))
 +                        {
 +                            positions.Add(pos.Pos);​
 +                        }
 +                        //если точки заданы,​ то изменяем нашу модель
 +                        if (positions.Count > 0)
 +                        {
 +                            //​получаем её со слоя
 +                            var model = model_layer.Model;​
 +                            //​начинаем групповое изменение модели
 +                            model.BeginUpdate();​
 +                            try
 +                            {
 +                                //​добавляем новые точки
 +                                var points = model.Add();​
 +                                for (int i = 0; i < positions.Count;​ i++)
 +                                {
 +                                    points.AddPoint(positions[i]);​
 +                                }
 +                            }
 +                            finally
 +                            {
 +                                //​заканчиваем групповое изменение модели
 +                                model.EndUpdate();​
 +                            }
 +                            //​обновляем видовой экран
 +                            cadView.Unlock();​
 +                            cadView.Invalidate();​
 +                        }
 +                    }
 +                    finally
 +                    {
 +                        //​отписываемся от события отрисовки
 +                        cadView.DynamicDraw -= dynamic_draw;​
 +                    }
 +                }
 +            }
 +        }
 +</​code>​
 +
 +В результате мы получим возможность использовать историю изменений и отменять изменения в нашей модели с помощью этого механизма.
 +
 +<​note>​[[developers:​tutorial:​tutorialcode|Исходный код]] примера расположен в проекте **"​tutorial10"​**.</​note>​
  
developers/tutorial/undoredo.1553371836.txt.gz · Последние изменения: 2021/07/22 14:28 (внешнее изменение)