From f1ed454474f80e0aa745d5e179a12c14d2f1734e Mon Sep 17 00:00:00 2001 From: "CORP\\ploskin.alexander" Date: Mon, 1 Nov 2021 13:20:29 +0300 Subject: [PATCH 1/4] Add ability to undo operations --- CS/ViewModel/EFCore/LocalData/Issues/User.cs | 22 +++++-- .../EFCore/LocalData/MainViewModel.cs | 58 +++++++++++++++++-- CS/ViewModel/EFCore/LocalData/MainWindow.xaml | 4 +- 3 files changed, 74 insertions(+), 10 deletions(-) diff --git a/CS/ViewModel/EFCore/LocalData/Issues/User.cs b/CS/ViewModel/EFCore/LocalData/Issues/User.cs index e3e8021..c07e1e9 100644 --- a/CS/ViewModel/EFCore/LocalData/Issues/User.cs +++ b/CS/ViewModel/EFCore/LocalData/Issues/User.cs @@ -1,10 +1,22 @@ +using DevExpress.Mvvm; using System.Collections.Generic; namespace EFCoreIssues.Issues { - public class User { - public int Id { get; set; } - public string FirstName { get; set; } - public string LastName { get; set; } - public virtual ICollection Issues { get; set; } + public class User : BindableBase { + public int Id { get => GetValue(); set => SetValue(value); } + public string FirstName { get => GetValue(); set => SetValue(value); } + public string LastName { get => GetValue(); set => SetValue(value); } + public virtual ICollection Issues { get => GetValue>(); set => SetValue(value); } + + public User Clone() { + return new User() { FirstName = FirstName, Id = Id, Issues = Issues, LastName = LastName }; + } + + public void CopyTo(User anotherUser) { + anotherUser.FirstName = FirstName; + anotherUser.Id = Id; + anotherUser.Issues = Issues; + anotherUser.LastName = LastName; + } } } diff --git a/CS/ViewModel/EFCore/LocalData/MainViewModel.cs b/CS/ViewModel/EFCore/LocalData/MainViewModel.cs index 0844a1f..5f52b21 100644 --- a/CS/ViewModel/EFCore/LocalData/MainViewModel.cs +++ b/CS/ViewModel/EFCore/LocalData/MainViewModel.cs @@ -1,32 +1,82 @@ using DevExpress.Mvvm; +using EFCoreIssues.Issues; +using System; +using System.Collections.ObjectModel; using System.Linq; namespace EFCoreIssues { public class MainViewModel : ViewModelBase { EFCoreIssues.Issues.IssuesContext _Context; - System.Collections.Generic.IList _ItemsSource; + ObservableCollection _ItemsSource; - public System.Collections.Generic.IList ItemsSource + public ObservableCollection ItemsSource { get { if(_ItemsSource == null && !IsInDesignMode) { _Context = new EFCoreIssues.Issues.IssuesContext(); - _ItemsSource = _Context.Users.ToList(); + _ItemsSource = new ObservableCollection(_Context.Users); } return _ItemsSource; } } + + void RemoveItem(User item) { + ItemsSource.Remove(item); + _Context.Users.Remove(item); + _Context.SaveChanges(); + } + + void InsertItem(int position, User item) { + ItemsSource.Insert(position, item); + _Context.Users.Add(item); + _Context.SaveChanges(); + } + + void ApplyEditingCache(User item) { + editingCache.CopyTo(item); + _Context.SaveChanges(); + } + + Action undoAction; + User editingCache; + + + [DevExpress.Mvvm.DataAnnotations.Command] + public void Undo() { + undoAction?.Invoke(); + undoAction = null; + } + + public bool CanUndo() { + return undoAction != null; + } + + [DevExpress.Mvvm.DataAnnotations.Command] + public void StartRowEdit(DevExpress.Mvvm.Xpf.RowEditStartedArgs args) { + if(args.Item != null) { + editingCache = ((User)args.Item).Clone(); + } + } + [DevExpress.Mvvm.DataAnnotations.Command] public void ValidateRow(DevExpress.Mvvm.Xpf.RowValidationArgs args) { var item = (EFCoreIssues.Issues.User)args.Item; - if(args.IsNewItem) + + undoAction = args.IsNewItem ? new Action(() => RemoveItem(item)) : new Action(() => ApplyEditingCache(item)); + + if(args.IsNewItem) { _Context.Users.Add(item); + } _Context.SaveChanges(); } + [DevExpress.Mvvm.DataAnnotations.Command] public void ValidateRowDeletion(DevExpress.Mvvm.Xpf.ValidateRowDeletionArgs args) { var item = (EFCoreIssues.Issues.User)args.Items.Single(); + + undoAction = new Action(() => InsertItem(args.SourceIndexes.Single(), item.Clone())); + _Context.Users.Remove(item); _Context.SaveChanges(); } diff --git a/CS/ViewModel/EFCore/LocalData/MainWindow.xaml b/CS/ViewModel/EFCore/LocalData/MainWindow.xaml index 560de16..534fa65 100644 --- a/CS/ViewModel/EFCore/LocalData/MainWindow.xaml +++ b/CS/ViewModel/EFCore/LocalData/MainWindow.xaml @@ -5,16 +5,18 @@ + - + + From 5dafefcc48b19bb0d2cf5f8672a7e577eb0868a3 Mon Sep 17 00:00:00 2001 From: DevExpressExampleBot Date: Mon, 1 Nov 2021 13:25:13 +0300 Subject: [PATCH 2/4] README auto update [skip ci] --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index e40afa9..9238d77 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -![](https://img.shields.io/endpoint?url=https://codecentral.devexpress.com/api/v1/VersionRange/265491908/21.2.3%2B) [![](https://img.shields.io/badge/Open_in_DevExpress_Support_Center-FF7200?style=flat-square&logo=DevExpress&logoColor=white)](https://supportcenter.devexpress.com/ticket/details/T899930) [![](https://img.shields.io/badge/📖_How_to_use_DevExpress_Examples-e9f6fc?style=flat-square)](https://docs.devexpress.com/GeneralInformation/403183) From 9a5672a68bfbdaf4846d84a1c5abe9053ce3e17f Mon Sep 17 00:00:00 2001 From: "CORP\\ploskin.alexander" Date: Tue, 2 Nov 2021 11:59:43 +0300 Subject: [PATCH 3/4] Revert "Add ability to undo operations" This reverts commit f1ed454474f80e0aa745d5e179a12c14d2f1734e. --- CS/ViewModel/EFCore/LocalData/Issues/User.cs | 22 ++----- .../EFCore/LocalData/MainViewModel.cs | 58 ++----------------- CS/ViewModel/EFCore/LocalData/MainWindow.xaml | 4 +- 3 files changed, 10 insertions(+), 74 deletions(-) diff --git a/CS/ViewModel/EFCore/LocalData/Issues/User.cs b/CS/ViewModel/EFCore/LocalData/Issues/User.cs index c07e1e9..e3e8021 100644 --- a/CS/ViewModel/EFCore/LocalData/Issues/User.cs +++ b/CS/ViewModel/EFCore/LocalData/Issues/User.cs @@ -1,22 +1,10 @@ -using DevExpress.Mvvm; using System.Collections.Generic; namespace EFCoreIssues.Issues { - public class User : BindableBase { - public int Id { get => GetValue(); set => SetValue(value); } - public string FirstName { get => GetValue(); set => SetValue(value); } - public string LastName { get => GetValue(); set => SetValue(value); } - public virtual ICollection Issues { get => GetValue>(); set => SetValue(value); } - - public User Clone() { - return new User() { FirstName = FirstName, Id = Id, Issues = Issues, LastName = LastName }; - } - - public void CopyTo(User anotherUser) { - anotherUser.FirstName = FirstName; - anotherUser.Id = Id; - anotherUser.Issues = Issues; - anotherUser.LastName = LastName; - } + public class User { + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public virtual ICollection Issues { get; set; } } } diff --git a/CS/ViewModel/EFCore/LocalData/MainViewModel.cs b/CS/ViewModel/EFCore/LocalData/MainViewModel.cs index 5f52b21..0844a1f 100644 --- a/CS/ViewModel/EFCore/LocalData/MainViewModel.cs +++ b/CS/ViewModel/EFCore/LocalData/MainViewModel.cs @@ -1,82 +1,32 @@ using DevExpress.Mvvm; -using EFCoreIssues.Issues; -using System; -using System.Collections.ObjectModel; using System.Linq; namespace EFCoreIssues { public class MainViewModel : ViewModelBase { EFCoreIssues.Issues.IssuesContext _Context; - ObservableCollection _ItemsSource; + System.Collections.Generic.IList _ItemsSource; - public ObservableCollection ItemsSource + public System.Collections.Generic.IList ItemsSource { get { if(_ItemsSource == null && !IsInDesignMode) { _Context = new EFCoreIssues.Issues.IssuesContext(); - _ItemsSource = new ObservableCollection(_Context.Users); + _ItemsSource = _Context.Users.ToList(); } return _ItemsSource; } } - - void RemoveItem(User item) { - ItemsSource.Remove(item); - _Context.Users.Remove(item); - _Context.SaveChanges(); - } - - void InsertItem(int position, User item) { - ItemsSource.Insert(position, item); - _Context.Users.Add(item); - _Context.SaveChanges(); - } - - void ApplyEditingCache(User item) { - editingCache.CopyTo(item); - _Context.SaveChanges(); - } - - Action undoAction; - User editingCache; - - - [DevExpress.Mvvm.DataAnnotations.Command] - public void Undo() { - undoAction?.Invoke(); - undoAction = null; - } - - public bool CanUndo() { - return undoAction != null; - } - - [DevExpress.Mvvm.DataAnnotations.Command] - public void StartRowEdit(DevExpress.Mvvm.Xpf.RowEditStartedArgs args) { - if(args.Item != null) { - editingCache = ((User)args.Item).Clone(); - } - } - [DevExpress.Mvvm.DataAnnotations.Command] public void ValidateRow(DevExpress.Mvvm.Xpf.RowValidationArgs args) { var item = (EFCoreIssues.Issues.User)args.Item; - - undoAction = args.IsNewItem ? new Action(() => RemoveItem(item)) : new Action(() => ApplyEditingCache(item)); - - if(args.IsNewItem) { + if(args.IsNewItem) _Context.Users.Add(item); - } _Context.SaveChanges(); } - [DevExpress.Mvvm.DataAnnotations.Command] public void ValidateRowDeletion(DevExpress.Mvvm.Xpf.ValidateRowDeletionArgs args) { var item = (EFCoreIssues.Issues.User)args.Items.Single(); - - undoAction = new Action(() => InsertItem(args.SourceIndexes.Single(), item.Clone())); - _Context.Users.Remove(item); _Context.SaveChanges(); } diff --git a/CS/ViewModel/EFCore/LocalData/MainWindow.xaml b/CS/ViewModel/EFCore/LocalData/MainWindow.xaml index 534fa65..560de16 100644 --- a/CS/ViewModel/EFCore/LocalData/MainWindow.xaml +++ b/CS/ViewModel/EFCore/LocalData/MainWindow.xaml @@ -5,18 +5,16 @@ - - + - From d3736abae8a5180ec2f029bbbebcc25e317850d6 Mon Sep 17 00:00:00 2001 From: "CORP\\ploskin.alexander" Date: Tue, 2 Nov 2021 12:48:19 +0300 Subject: [PATCH 4/4] Implement logic in behavior --- .../IDataItemCopyOperationsSupporter.cs | 12 ++ .../EFCore/LocalData/LocalData.csproj | 11 +- .../EFCore/LocalData/MainViewModel.cs | 17 ++- CS/ViewModel/EFCore/LocalData/MainWindow.xaml | 69 ++++++---- CS/ViewModel/EFCore/LocalData/UndoBehavior.cs | 128 ++++++++++++++++++ CS/ViewModel/EFCore/LocalData/UndoManager.cs | 11 ++ .../LocalData/UserCopyOperationsSupporter.cs | 32 +++++ 7 files changed, 243 insertions(+), 37 deletions(-) create mode 100644 CS/ViewModel/EFCore/LocalData/IDataItemCopyOperationsSupporter.cs create mode 100644 CS/ViewModel/EFCore/LocalData/UndoBehavior.cs create mode 100644 CS/ViewModel/EFCore/LocalData/UndoManager.cs create mode 100644 CS/ViewModel/EFCore/LocalData/UserCopyOperationsSupporter.cs diff --git a/CS/ViewModel/EFCore/LocalData/IDataItemCopyOperationsSupporter.cs b/CS/ViewModel/EFCore/LocalData/IDataItemCopyOperationsSupporter.cs new file mode 100644 index 0000000..e0cbf34 --- /dev/null +++ b/CS/ViewModel/EFCore/LocalData/IDataItemCopyOperationsSupporter.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EFCoreIssues { + public interface IDataItemCopyOperationsSupporter { + object Clone(object item); + void CopyTo(object source, object target); + } +} diff --git a/CS/ViewModel/EFCore/LocalData/LocalData.csproj b/CS/ViewModel/EFCore/LocalData/LocalData.csproj index cf7b5b4..c7f6a6b 100644 --- a/CS/ViewModel/EFCore/LocalData/LocalData.csproj +++ b/CS/ViewModel/EFCore/LocalData/LocalData.csproj @@ -1,4 +1,4 @@ - + @@ -13,7 +13,7 @@ 512 {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 4 - true + true true true @@ -166,11 +166,14 @@ MSBuild:Compile Designer + + MSBuild:Compile Designer - + + App.xaml Code @@ -213,4 +216,4 @@ - + \ No newline at end of file diff --git a/CS/ViewModel/EFCore/LocalData/MainViewModel.cs b/CS/ViewModel/EFCore/LocalData/MainViewModel.cs index 0844a1f..c2e7051 100644 --- a/CS/ViewModel/EFCore/LocalData/MainViewModel.cs +++ b/CS/ViewModel/EFCore/LocalData/MainViewModel.cs @@ -1,18 +1,29 @@ using DevExpress.Mvvm; +using System.Collections.ObjectModel; using System.Linq; namespace EFCoreIssues { public class MainViewModel : ViewModelBase { EFCoreIssues.Issues.IssuesContext _Context; - System.Collections.Generic.IList _ItemsSource; + ObservableCollection _ItemsSource; - public System.Collections.Generic.IList ItemsSource + UserCopyOperationsSupporter _CopyOperationsSupporter; + public UserCopyOperationsSupporter CopyOperationsSupporter { + get { + if(_CopyOperationsSupporter == null) { + _CopyOperationsSupporter = new UserCopyOperationsSupporter(); + } + return _CopyOperationsSupporter; + } + } + + public ObservableCollection ItemsSource { get { if(_ItemsSource == null && !IsInDesignMode) { _Context = new EFCoreIssues.Issues.IssuesContext(); - _ItemsSource = _Context.Users.ToList(); + _ItemsSource = new ObservableCollection(_Context.Users); } return _ItemsSource; } diff --git a/CS/ViewModel/EFCore/LocalData/MainWindow.xaml b/CS/ViewModel/EFCore/LocalData/MainWindow.xaml index 560de16..ef3e943 100644 --- a/CS/ViewModel/EFCore/LocalData/MainWindow.xaml +++ b/CS/ViewModel/EFCore/LocalData/MainWindow.xaml @@ -1,31 +1,40 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CS/ViewModel/EFCore/LocalData/UndoBehavior.cs b/CS/ViewModel/EFCore/LocalData/UndoBehavior.cs new file mode 100644 index 0000000..3f56d06 --- /dev/null +++ b/CS/ViewModel/EFCore/LocalData/UndoBehavior.cs @@ -0,0 +1,128 @@ +using DevExpress.Mvvm; +using DevExpress.Mvvm.UI.Interactivity; +using DevExpress.Mvvm.Xpf; +using DevExpress.Xpf.Grid; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; + +namespace EFCoreIssues { + public class UndoBehavior : Behavior { + public static readonly DependencyProperty CopyOperationsSupporterProperty = + DependencyProperty.Register(nameof(CopyOperationsSupporter), typeof(IDataItemCopyOperationsSupporter), typeof(UndoBehavior), new PropertyMetadata(null)); + + public IDataItemCopyOperationsSupporter CopyOperationsSupporter { + get { return (IDataItemCopyOperationsSupporter)GetValue(CopyOperationsSupporterProperty); } + set { SetValue(CopyOperationsSupporterProperty, value); } + } + + public static readonly DependencyProperty ValidateRowCommandProperty = + DependencyProperty.Register(nameof(ValidateRowCommand), typeof(ICommand), typeof(UndoBehavior), new PropertyMetadata(null)); + + public ICommand ValidateRowCommand { + get { return (ICommand)GetValue(ValidateRowCommandProperty); } + set { SetValue(ValidateRowCommandProperty, value); } + } + + public static readonly DependencyProperty ValidateRowDeletionCommandProperty = + DependencyProperty.Register(nameof(ValidateRowDeletionCommand), typeof(ICommand), typeof(UndoBehavior), new PropertyMetadata(null)); + + public ICommand ValidateRowDeletionCommand { + get { return (ICommand)GetValue(ValidateRowDeletionCommandProperty); } + set { SetValue(ValidateRowDeletionCommandProperty, value); } + } + + public ICommand UndoCommand { get; private set; } + + public UndoBehavior() { + UndoCommand = new DelegateCommand(Undo, CanUndo); + } + + IList source; + + protected override void OnAttached() { + base.OnAttached(); + AssociatedObject.ValidateRow += OnRowAddedOrEdited; + AssociatedObject.ValidateRowDeletion += OnRowDeleted; + AssociatedObject.RowEditStarted += OnEditingStarted; + AssociatedObject.DataSourceRefresh += OnRefresh; + AssociatedObject.InitNewRow += OnNewRowStarted; + source = (IList)AssociatedObject.DataControl.ItemsSource; + } + + private void OnRefresh(object sender, DataSourceRefreshEventArgs e) { + undoAction = null; + editingCache = null; + } + + protected override void OnDetaching() { + AssociatedObject.ValidateRow -= OnRowAddedOrEdited; + AssociatedObject.ValidateRowDeletion -= OnRowDeleted; + AssociatedObject.RowEditStarted -= OnEditingStarted; + AssociatedObject.DataSourceRefresh -= OnRefresh; + AssociatedObject.InitNewRow -= OnNewRowStarted; + source = null; + base.OnDetaching(); + } + + private void OnNewRowStarted(object sender, InitNewRowEventArgs e) { + isNewItemRowEditing = true; + } + + bool isNewItemRowEditing; + + object editingCache; + + private void OnEditingStarted(object sender, RowEditStartedEventArgs e) { + if(e.RowHandle != DataControlBase.NewItemRowHandle) { + editingCache = CopyOperationsSupporter.Clone(e.Row); + } + } + + private void OnRowDeleted(object sender, GridValidateRowDeletionEventArgs e) { + undoAction = new Action(() => { + InsertItem(e.RowHandles.Single(), CopyOperationsSupporter.Clone(e.Rows.Single())); //Somehow index is changing after a new adding to source + }); + } + + private void OnRowAddedOrEdited(object sender, GridRowValidationEventArgs e) { + var item = e.Row; + undoAction = e.IsNewItem ? new Action(() => RemoveItem(item)) : new Action(() => ApplyEditingCache(item)); + isNewItemRowEditing = false; + } + + void ApplyEditingCache(object item) { + CopyOperationsSupporter.CopyTo(editingCache, item); + AssociatedObject.DataControl.RefreshRow(source.IndexOf(item)); //TODO: find a way how to get row handle by element or list index + ValidateRowCommand.Execute(new RowValidationArgs(editingCache, source.IndexOf(item), false, new CancellationToken(), false)); + } + + Action undoAction; + + void Undo() { + undoAction?.Invoke(); + undoAction = null; + } + + bool CanUndo() { + return undoAction != null && !AssociatedObject.IsEditing && !isNewItemRowEditing && !AssociatedObject.IsDataSourceRefreshing; + } + + void RemoveItem(object item) { + source.Remove(item); + ValidateRowDeletionCommand.Execute(new ValidateRowDeletionArgs(new object[] { item }, new int[] { source.IndexOf(item) })); + } + + void InsertItem(int position, object item) { + source.Insert(position, item); + ValidateRowCommand.Execute(new RowValidationArgs(item, source.IndexOf(item), true, new CancellationToken(), false)); + } + + } +} diff --git a/CS/ViewModel/EFCore/LocalData/UndoManager.cs b/CS/ViewModel/EFCore/LocalData/UndoManager.cs new file mode 100644 index 0000000..ae533cb --- /dev/null +++ b/CS/ViewModel/EFCore/LocalData/UndoManager.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EFCoreIssues { + public class UndoManager { + + } +} diff --git a/CS/ViewModel/EFCore/LocalData/UserCopyOperationsSupporter.cs b/CS/ViewModel/EFCore/LocalData/UserCopyOperationsSupporter.cs new file mode 100644 index 0000000..289d39c --- /dev/null +++ b/CS/ViewModel/EFCore/LocalData/UserCopyOperationsSupporter.cs @@ -0,0 +1,32 @@ +using EFCoreIssues.Issues; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EFCoreIssues { + public class UserCopyOperationsSupporter : IDataItemCopyOperationsSupporter { + public object Clone(object item) { + var userItem = GetUser(item); + return new User() { FirstName = userItem.FirstName, Id = userItem.Id, Issues = userItem.Issues, LastName = userItem.LastName }; + } + + public void CopyTo(object source, object target) { + var userSource = GetUser(source); + var userTarget = GetUser(target); + userTarget.FirstName = userSource.FirstName; + userTarget.Id = userSource.Id; + userTarget.Issues = userSource.Issues; + userTarget.LastName = userSource.LastName; + } + + User GetUser(object item) { + var result = item as User; + if(result == null) { + throw new InvalidOperationException(); + } + return result; + } + } +}