Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CS/ViewModel/EFCore/LocalData/IDataItemCopyOperationsSupporter.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
11 changes: 7 additions & 4 deletions CS/ViewModel/EFCore/LocalData/LocalData.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\packages\EntityFramework.6.4.4\build\EntityFramework.props" Condition="Exists('..\..\..\packages\EntityFramework.6.4.4\build\EntityFramework.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
Expand All @@ -13,7 +13,7 @@
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<NuGetPackageImportStamp>
Expand Down Expand Up @@ -166,11 +166,14 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="UndoBehavior.cs" />
<Compile Include="UserCopyOperationsSupporter.cs" />
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="MainViewModel.cs"/>
<Compile Include="IDataItemCopyOperationsSupporter.cs" />
<Compile Include="MainViewModel.cs" />
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
Expand Down Expand Up @@ -213,4 +216,4 @@
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
</Project>
17 changes: 14 additions & 3 deletions CS/ViewModel/EFCore/LocalData/MainViewModel.cs
Original file line number Diff line number Diff line change
@@ -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<EFCoreIssues.Issues.User> _ItemsSource;
ObservableCollection<EFCoreIssues.Issues.User> _ItemsSource;

public System.Collections.Generic.IList<EFCoreIssues.Issues.User> ItemsSource
UserCopyOperationsSupporter _CopyOperationsSupporter;
public UserCopyOperationsSupporter CopyOperationsSupporter {
get {
if(_CopyOperationsSupporter == null) {
_CopyOperationsSupporter = new UserCopyOperationsSupporter();
}
return _CopyOperationsSupporter;
}
}

public ObservableCollection<EFCoreIssues.Issues.User> ItemsSource
{
get
{
if(_ItemsSource == null && !IsInDesignMode) {
_Context = new EFCoreIssues.Issues.IssuesContext();
_ItemsSource = _Context.Users.ToList();
_ItemsSource = new ObservableCollection<Issues.User>(_Context.Users);
}
return _ItemsSource;
}
Expand Down
69 changes: 39 additions & 30 deletions CS/ViewModel/EFCore/LocalData/MainWindow.xaml
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
<Window x:Class="EFCoreIssues.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:EFCoreIssues" xmlns:local_issues="clr-namespace:EFCoreIssues.Issues" xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid" xmlns:dxb="http://schemas.devexpress.com/winfx/2008/xaml/bars" xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800">
<Grid>
<dxb:ToolBarControl>
<dxb:BarButtonItem Content="Refresh (F5)" Command="{Binding View.Commands.RefreshDataSource, ElementName=grid}" Glyph="{dx:DXImage SvgImages/Icon Builder/Actions_Refresh.svg}" BarItemDisplayMode="ContentAndGlyph" />
<dxb:BarButtonItem Content="Edit (F2)" Command="{Binding View.Commands.EditFocusedRow, ElementName=grid}" Glyph="{dx:DXImage SvgImages/Icon Builder/Actions_Edit.svg}" BarItemDisplayMode="ContentAndGlyph" />
<dxb:BarButtonItem Content="New" Command="{Binding View.Commands.AddNewRow, ElementName=grid}" Glyph="{dx:DXImage SvgImages/Icon Builder/Actions_Add.svg}" BarItemDisplayMode="ContentAndGlyph" />
<dxb:BarButtonItem Content="Delete (Del)" Command="{Binding View.Commands.DeleteFocusedRow, ElementName=grid}" Glyph="{dx:DXImage SvgImages/Icon Builder/Actions_Delete.svg}" BarItemDisplayMode="ContentAndGlyph" />
</dxb:ToolBarControl>
<dxg:GridControl x:Name="grid" ItemsSource="{Binding ItemsSource}" RestoreStateKeyFieldName="Id" RestoreStateOnSourceChange="True" Grid.Row="1">
<dxg:GridControl.View>
<dxg:TableView NewItemRowPosition="Top" ShowUpdateRowButtons="OnCellEditorOpen" ValidateRowCommand="{Binding ValidateRowCommand}" ValidateRowDeletionCommand="{Binding ValidateRowDeletionCommand}" DataSourceRefreshCommand="{Binding DataSourceRefreshCommand}" ShowFixedTotalSummary="True" />
</dxg:GridControl.View>
<dxg:GridColumn FieldName="Id" IsSmart="True" ReadOnly="True" />
<dxg:GridColumn FieldName="FirstName" IsSmart="True" />
<dxg:GridColumn FieldName="LastName" IsSmart="True" />
<dxg:GridControl.InputBindings>
<KeyBinding Command="{Binding View.Commands.DeleteFocusedRow, ElementName=grid}" Key="Delete" />
</dxg:GridControl.InputBindings>
<dxg:GridControl.TotalSummary>
<dxg:GridSummaryItem SummaryType="Count" Alignment="Right" />
</dxg:GridControl.TotalSummary>
</dxg:GridControl>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
</Grid>
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Window x:Class="EFCoreIssues.MainWindow" xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:EFCoreIssues" xmlns:local_issues="clr-namespace:EFCoreIssues.Issues" xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid" xmlns:dxb="http://schemas.devexpress.com/winfx/2008/xaml/bars" xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<dxb:ToolBarControl>
<dxb:BarButtonItem Content="Refresh (F5)" Command="{Binding View.Commands.RefreshDataSource, ElementName=grid}" Glyph="{dx:DXImage SvgImages/Icon Builder/Actions_Refresh.svg}" BarItemDisplayMode="ContentAndGlyph" />
<dxb:BarButtonItem Content="Edit (F2)" Command="{Binding View.Commands.EditFocusedRow, ElementName=grid}" Glyph="{dx:DXImage SvgImages/Icon Builder/Actions_Edit.svg}" BarItemDisplayMode="ContentAndGlyph" />
<dxb:BarButtonItem Content="New" Command="{Binding View.Commands.AddNewRow, ElementName=grid}" Glyph="{dx:DXImage SvgImages/Icon Builder/Actions_Add.svg}" BarItemDisplayMode="ContentAndGlyph" />
<dxb:BarButtonItem Content="Delete (Del)" Command="{Binding View.Commands.DeleteFocusedRow, ElementName=grid}" Glyph="{dx:DXImage SvgImages/Icon Builder/Actions_Delete.svg}" BarItemDisplayMode="ContentAndGlyph" />
<dxb:BarButtonItem Content="Undo (Ctrl+Z)" Command="{Binding UndoCommand, ElementName=undoBehavior}" Glyph="{dx:DXImage SvgImages/Icon Builder/Actions_Undo.svg}" BarItemDisplayMode="ContentAndGlyph" />
</dxb:ToolBarControl>
<dxg:GridControl x:Name="grid" ItemsSource="{Binding ItemsSource}" RestoreStateKeyFieldName="Id" RestoreStateOnSourceChange="True" Grid.Row="1">
<dxg:GridControl.View>
<dxg:TableView NewItemRowPosition="Top" ShowUpdateRowButtons="OnCellEditorOpen" ValidateRowCommand="{Binding ValidateRowCommand}" ValidateRowDeletionCommand="{Binding ValidateRowDeletionCommand}" DataSourceRefreshCommand="{Binding DataSourceRefreshCommand}" ShowFixedTotalSummary="True" >
<dxmvvm:Interaction.Behaviors>
<local:UndoBehavior x:Name="undoBehavior"
ValidateRowCommand="{Binding ValidateRowCommand}"
ValidateRowDeletionCommand="{Binding ValidateRowDeletionCommand}"
CopyOperationsSupporter="{Binding CopyOperationsSupporter}" />
</dxmvvm:Interaction.Behaviors>
</dxg:TableView>
</dxg:GridControl.View>
<dxg:GridColumn FieldName="Id" IsSmart="True" ReadOnly="True" />
<dxg:GridColumn FieldName="FirstName" IsSmart="True" />
<dxg:GridColumn FieldName="LastName" IsSmart="True" />
<dxg:GridControl.InputBindings>
<KeyBinding Command="{Binding View.Commands.DeleteFocusedRow, ElementName=grid}" Key="Delete" />
<KeyBinding Command="{Binding UndoCommand, ElementName=undoBehavior}" Key="Z" Modifiers="Ctrl" />
</dxg:GridControl.InputBindings>
<dxg:GridControl.TotalSummary>
<dxg:GridSummaryItem SummaryType="Count" Alignment="Right" />
</dxg:GridControl.TotalSummary>
</dxg:GridControl>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
</Grid>
</Window>
128 changes: 128 additions & 0 deletions CS/ViewModel/EFCore/LocalData/UndoBehavior.cs
Original file line number Diff line number Diff line change
@@ -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<TableView> {
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<RowValidationArgs>), typeof(UndoBehavior), new PropertyMetadata(null));

public ICommand<RowValidationArgs> ValidateRowCommand {
get { return (ICommand<RowValidationArgs>)GetValue(ValidateRowCommandProperty); }
set { SetValue(ValidateRowCommandProperty, value); }
}

public static readonly DependencyProperty ValidateRowDeletionCommandProperty =
DependencyProperty.Register(nameof(ValidateRowDeletionCommand), typeof(ICommand<ValidateRowDeletionArgs>), typeof(UndoBehavior), new PropertyMetadata(null));

public ICommand<ValidateRowDeletionArgs> ValidateRowDeletionCommand {
get { return (ICommand<ValidateRowDeletionArgs>)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));
}

}
}
11 changes: 11 additions & 0 deletions CS/ViewModel/EFCore/LocalData/UndoManager.cs
Original file line number Diff line number Diff line change
@@ -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 {

}
}
32 changes: 32 additions & 0 deletions CS/ViewModel/EFCore/LocalData/UserCopyOperationsSupporter.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<!-- default badges list -->
![](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)
<!-- default badges end -->
Expand Down