====== Отмена и возврат изменений модели ====== В программном комплексе [[http://topomatic.ru|Топоматик Робур]] предусмотрена история изменений произошедших во время работы программы с возможностью отмены этих изменений при необходимости. Классы, используемые для реализации моделей с поддержкой истории изменений расположены в пространстве имён [[developers:references:topomatic.foundationclasses.undo|Topomatic.FoundationClasses.Undo]]. Для реализации поддержки истории изменений в модели необходимо: * Все изменяемые поля должны быть обвёрнуты в классы [[developers:references:topomatic.foundationclasses.undo.basetransactablefield_1|BaseTransactableField]]. * Изменяемые списки должны быть реализованы через [[developers:references:topomatic.foundationclasses.undo.basetransactablelist_1|BaseTransactableList]] * Изменяемые словари должны быть реализованы через [[developers:references:topomatic.foundationclasses.undo.transactabledictionary_2|TransactableDictionary]] Если необходимо отслеживать изменение значения полей или содержимого списков, то вместо [[developers:references:topomatic.foundationclasses.undo.basetransactablefield_1|BaseTransactableField]] и [[developers:references:topomatic.foundationclasses.undo.basetransactablelist_1|BaseTransactableList]] нужно использовать [[developers:references:topomatic.foundationclasses.undo.transactablefield_1|TransactableField]] и [[developers:references:topomatic.foundationclasses.undo.transactablelist_1|TransactableList]]. Эти классы поддерживают события позволяющие следить за изменением своего содержимого. Содержащие поля данных, списки и словари объекты должны поддерживать интерфейс [[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]] или изменив содержимое контейнеров [[developers:references:topomatic.foundationclasses.undo.basetransactablelist_1|BaseTransactableList]] и [[developers:references:topomatic.foundationclasses.undo.transactabledictionary_2|TransactableDictionary]] * Вызвать у родительского объекта, в котором происходят изменения, метод [[developers:references:topomatic.foundationclasses.iupdatable.endupdate|EndUpdate]] после завершения изменений. При изменении только одного поля [[developers:references:topomatic.foundationclasses.undo.basetransactablefield_1|BaseTransactableField]] или одного элемента контейнеров [[developers:references:topomatic.foundationclasses.undo.basetransactablelist_1|BaseTransactableList]] и [[developers:references:topomatic.foundationclasses.undo.transactabledictionary_2|TransactableDictionary]] допустимо не вызывать методы [[developers:references:topomatic.foundationclasses.iupdatable.beginupdate|BeginUpdate]] и [[developers:references:topomatic.foundationclasses.iupdatable.endupdate|EndUpdate]]. В этом случае они будут вызваны автоматически перед началом и в конце изменения соответственно. Для работы истории изменений, необходимо чтобы свойству [[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]]. Если необходимо отслеживать изменение объекта, при изменении каких либо дочерних элементов, то необходимо наследоваться от [[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]] Класс [[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]], а все дочерние элементы получают его автоматически. Часто возникает ситуация, когда в модель необходимо внести какие либо изменения, а потом либо отменить внесенные изменения без записи в истории либо применить их. В таких случаях перед началом изменений нужно вызвать метод **BeginTransaction** а в конце в либо метод **Commit** чтобы применить изменения, либо метод **Rollback** для сброса изменений Создайте новую [[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]]. //Класс для хранения точек в нашей модели //поддерживаем интерфейсы IStgSerializable для сохранения и IOwned для определения владельца списка точек //Также поддерживаем интерфейс IObjectDisjoiner для привязок class Points : UpdatableObject, IStgSerializable, IOwned, IObjectDisjoiner { //Поддержка цепочки родителей private Model m_Owner; //Список структур с поддержкой истории изменений private TransactableList m_Points; public Points(Model owner) { m_Owner = owner; m_Points = new TransactableList(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 list) { for (int i = 0; i < m_Points.Count; i++) { list.Add(m_Points[i]); } } //Привязка ЦЕНТРАЛЬНАЯ ТОЧКА public void GetCenterPoint(ObjectsDisjointerArgs e, IList list) { //Do nothing } //Привязка СЕРЕДИНА ОТРЕЗКА public void GetMiddlePoint(ObjectsDisjointerArgs e, IList 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 list) { //Do nothing } //Привязка КВАДРАНТ public void GetQuadrantPoint(ObjectsDisjointerArgs e, IList list) { //Do nothing } //Привязка ТОЧКА ВСТАВКИ public void GetInsertionPoint(ObjectsDisjointerArgs e, IList list) { //Do nothing } //Привязка БЛИЖАЙШАЯ ТОЧКА, КАСАТЕЛЬНАЯ и т.п. public void GetSegments(ObjectsDisjointerArgs e, IList arcList, IList lineList) { for (int i = 1; i < m_Points.Count; i++) { lineList.Add( new LineSegment() { StartPoint = m_Points[i - 1], EndPoint = m_Points[i] } ); } } } Также изменяем Model.cs. Список содержащий экземпляры класса Points заменяем на [[developers:references:topomatic.foundationclasses.undo.basetransactablelist_1|BaseTransactableList]] class Model : StateControllerObject, IStgSerializable { private bool m_ReadOnly = false; //Список объектов точек с поддержкой истории изменений private BaseTransactableList m_Points; public Model() { m_Points = new BaseTransactableList(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()); } } } Также вносим изменения в Module.cs в функцию редактирования модели. Теперь перед изменением значений мы вызываем метод [[developers:references:topomatic.foundationclasses.iupdatable.beginupdate|BeginUpdate]] а после метод [[developers:references:topomatic.foundationclasses.iupdatable.beginupdate|EndUpdate]]. Кроме того теперь нет необходимости выставлять свойство [[developers:references:topomatic.foundationclasses.statecontrollerobject.modified|Modified]] вручную, при изменении истории значение свойства определяется автоматически. [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(); //делегат для динамической отрисовки 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; } } } } В результате мы получим возможность использовать историю изменений и отменять изменения в нашей модели с помощью этого механизма. Исходный код примера, вы можете скачать используя эту ссылку {{ :developers:tutorial:undoredo:tutorial10.zip |Архив с кодом примера}}