Здесь показаны различия между двумя версиями данной страницы.
Предыдущая версия справа и слева Предыдущая версия Следующая версия | Предыдущая версия | ||
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> | ||