Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/266.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在DataGrid最后一行的最后一列中按Tab键应将焦点设置为新行的第一列_C#_Wpf_Wpfdatagrid - Fatal编程技术网

C# 在DataGrid最后一行的最后一列中按Tab键应将焦点设置为新行的第一列

C# 在DataGrid最后一行的最后一列中按Tab键应将焦点设置为新行的第一列,c#,wpf,wpfdatagrid,C#,Wpf,Wpfdatagrid,我有一个DataGrid,它编辑IEditableObject对象的ObservableCollection。DATAGRID设置为 CuuleRADDROWS=“true”< /C> >以便出现添加新记录的空白行。除了一个明显的例外,一切都很完美 所有包含数据的行的默认选项卡行为是,当从当前行的最后一列中移出选项卡时,移动到下一行的第一列,这正是我想要的行为。但是,如果下一行是新行,即将包含下一条新记录的行,则我不会得到这种行为。选项卡将焦点移到DataGrid中第一行的第一列,而不是移到新行

我有一个
DataGrid
,它编辑
IEditableObject
对象的
ObservableCollection
。DATAGRID设置为<代码> CuuleRADDROWS=“true”< /C> >以便出现添加新记录的空白行。除了一个明显的例外,一切都很完美

所有包含数据的行的默认选项卡行为是,当从当前行的最后一列中移出选项卡时,移动到下一行的第一列,这正是我想要的行为。但是,如果下一行是新行,即将包含下一条新记录的行,则我不会得到这种行为。选项卡将焦点移到DataGrid中第一行的第一列,而不是移到新行的第一列

我当前尝试将行为更改为我想要的行为,如下所示:

private void ItemsDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
    if (ItemsDataGrid.SelectedIndex == ItemsDataGrid.Items.Count - 2)
    {
        DataGridRow row = ItemsDataGrid
            .ItemContainerGenerator.ContainerFromItem(CollectionView.NewItemPlaceholder) as DataGridRow;

        if (row.Focusable)
            row.Focus();

        DataGridCell cell = ItemsDataGrid.GetCell(row, 0);
        if (cell != null)
        {
            DataGridCellInfo dataGridCellInfo = new DataGridCellInfo(cell);
            if (cell.Focusable)
                cell.Focus();
        }
    }
}
它不会将焦点设置到我想要的位置,即使实际调用了
cell.SetFocus()

我目前的工作原理是:
row.Focusable
返回
false
,可能是因为该行尚未“完全”存在(我已经知道此时它还不包含数据),所以所需的单元格无法获得焦点,因为该行无法获得焦点

有什么想法吗


下面是我能找到的最接近MCVE的东西。WPF相当冗长。请注意,我使用的是我的
INotifyPropertyChanged
实现

main window.XAML

<Window
    x:Class="WpfApp2.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:local="clr-namespace:WpfApp2"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">

    <Grid>
        <TabControl>
            <TabItem Header="List">
                <DataGrid                     
                    Name="ItemsDataGrid"
                    AutoGenerateColumns="False"
                    CanUserAddRows="True"
                    ItemsSource="{Binding EditableFilterableItems}"
                    KeyboardNavigation.TabNavigation="Cycle"
                    RowEditEnding="ItemsDataGrid_RowEditEnding"
                    RowHeaderWidth="20"
                    SelectedItem="{Binding SelectedItem}"
                    SelectionUnit="FullRow">

                    <DataGrid.Resources>
                        <!--  http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/  -->
                        <local:BindingProxy x:Key="proxy" Data="{Binding}" />
                    </DataGrid.Resources>

                    <DataGrid.Columns>
                        <DataGridTextColumn
                            x:Name="QuantityColumn"
                            Width="1*"
                            Binding="{Binding Quantity}"
                            Header="Quantity" />
                        <DataGridComboBoxColumn
                            x:Name="AssetColumn"
                            Width="3*"
                            DisplayMemberPath="Description"
                            Header="Item"
                            ItemsSource="{Binding Data.ItemDescriptions, Source={StaticResource proxy}}"
                            SelectedValueBinding="{Binding ItemDescriptionID}"
                            SelectedValuePath="ItemDescriptionID" />
                        <DataGridTextColumn
                            x:Name="NotesColumn"
                            Width="7*"
                            Binding="{Binding Notes}"
                            Header="Notes" />
                    </DataGrid.Columns>
                </DataGrid>
            </TabItem>
        </TabControl>
    </Grid>

</Window>

MainWindow.xaml.CS

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApp2
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        MainWindowViewModel _viewModel;
        public MainWindow()
        {
            _viewModel = new MainWindowViewModel();
            DataContext = _viewModel;
            InitializeComponent();
        }


        private void ItemsDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
        {   
            if (ItemsDataGrid.SelectedIndex == ItemsDataGrid.Items.Count - 2)
            {
                DataGridRow row = ItemsDataGrid
                    .ItemContainerGenerator.ContainerFromItem(CollectionView.NewItemPlaceholder) as DataGridRow;

                var rowIndex = row.GetIndex();

                if (row.Focusable)
                    row.Focus();

                DataGridCell cell = ItemsDataGrid.GetCell(row, 0);
                if (cell != null)
                {
                    DataGridCellInfo dataGridCellInfo = new DataGridCellInfo(cell);
                    if (cell.Focusable)
                        cell.Focus();
                }
            }
        }
    }
}
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
using PropertyChanged;

namespace WpfApp2
{
    [AddINotifyPropertyChangedInterface]
    public class MainWindowViewModel
    {
        public MainWindowViewModel()
        {
            Items = new ObservableCollection<Item>(
                new List<Item>
                {
                    new Item {ItemDescriptionID=1, Quantity=1, Notes="Little Red Wagon"},
                    new Item {ItemDescriptionID=2, Quantity=1, Notes="I Want a Pony"},
                }
            );
            FilterableItems = CollectionViewSource.GetDefaultView(Items);
            EditableFilterableItems = FilterableItems as IEditableCollectionView;
        }
        public ObservableCollection<Item> Items { get; set; }
        public ICollectionView FilterableItems { get; set; }
        public IEditableCollectionView EditableFilterableItems { get; set; }

        public Item SelectedItem { get; set; }

        public List<ItemDescription> ItemDescriptions => new List<ItemDescription>
        {
            new ItemDescription { ItemDescriptionID = 1, Description="Wagon" },
            new ItemDescription { ItemDescriptionID = 2, Description="Pony" },
            new ItemDescription { ItemDescriptionID = 3, Description="Train" },
            new ItemDescription { ItemDescriptionID = 4, Description="Dump Truck" },
        };
    }
}
public class Item : EditableObject<Item>
{
    public int Quantity { get; set; }
    public int ItemDescriptionID { get; set; }
    public string Notes { get; set; }
}

public class ItemDescription
{
    public int ItemDescriptionID { get; set; }
    public string Description { get; set; }
}
using System.Windows;

namespace WpfApp2
{
    /// <summary>
    /// http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
    /// </summary>
    public class BindingProxy : Freezable
    {
        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }

        public object Data
        {
            get { return GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
    }
}
using System;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace WpfApp2
{
    public static class DataGridHelper
    {
        public static T GetVisualChild<T>(Visual parent) where T : Visual
        {
            T child = default(T);
            int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < numVisuals; i++)
            {
                Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
                child = v as T;
                if (child == null)
                {
                    child = GetVisualChild<T>(v);
                }
                if (child != null)
                {
                    break;
                }
            }
            return child;
        }
        public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int column)
        {
            if (row != null)
            {
                DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);

                if (presenter == null)
                {
                    grid.ScrollIntoView(row, grid.Columns[column]);
                    presenter = GetVisualChild<DataGridCellsPresenter>(row);
                }

                DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
                return cell;
            }
            return null;
        }
        public static DataGridCell GetCell(this DataGrid grid, int row, int column)
        {
            DataGridRow rowContainer = grid.GetRow(row);
            return grid.GetCell(rowContainer, column);
        }
    }
}
using System;
using System.ComponentModel;

namespace WpfApp2
{
    public abstract class EditableObject<T> : IEditableObject
    {
        private T Cache { get; set; }

        private object CurrentModel
        {
            get { return this; }
        }

        public RelayCommand CancelEditCommand
        {
            get { return new RelayCommand(CancelEdit); }
        }

        #region IEditableObject Members
        public void BeginEdit()
        {
            Cache = Activator.CreateInstance<T>();

            //Set Properties of Cache
            foreach (var info in CurrentModel.GetType().GetProperties())
            {
                if (!info.CanRead || !info.CanWrite) continue;
                var oldValue = info.GetValue(CurrentModel, null);
                Cache.GetType().GetProperty(info.Name).SetValue(Cache, oldValue, null);
            }
        }

        public virtual void EndEdit()
        {
            Cache = default(T);
        }


        public void CancelEdit()
        {
            foreach (var info in CurrentModel.GetType().GetProperties())
            {
                if (!info.CanRead || !info.CanWrite) continue;
                var oldValue = info.GetValue(Cache, null);
                CurrentModel.GetType().GetProperty(info.Name).SetValue(CurrentModel, oldValue, null);
            }
        }
        #endregion
    }
}
using System;
using System.Windows.Input;

namespace WpfApp2
{
    /// <summary>
    /// A command whose sole purpose is to relay its functionality to other objects by invoking delegates. 
    /// The default return value for the CanExecute method is 'true'.
    /// <see cref="RaiseCanExecuteChanged"/> needs to be called whenever
    /// <see cref="CanExecute"/> is expected to return a different value.
    /// </summary>
    public class RelayCommand : ICommand
    {
        #region Private members
        /// <summary>
        /// Creates a new command that can always execute.
        /// </summary>
        private readonly Action execute;

        /// <summary>
        /// True if command is executing, false otherwise
        /// </summary>
        private readonly Func<bool> canExecute;
        #endregion

        /// <summary>
        /// Initializes a new instance of <see cref="RelayCommand"/> that can always execute.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        public RelayCommand(Action execute) : this(execute, canExecute: null) { }

        /// <summary>
        /// Initializes a new instance of <see cref="RelayCommand"/>.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        /// <param name="canExecute">The execution status logic.</param>
        public RelayCommand(Action execute, Func<bool> canExecute)
        {
            this.execute = execute ?? throw new ArgumentNullException("execute");
            this.canExecute = canExecute;
        }

        /// <summary>
        /// Raised when RaiseCanExecuteChanged is called.
        /// </summary>
        public event EventHandler CanExecuteChanged;

        /// <summary>
        /// Determines whether this <see cref="RelayCommand"/> can execute in its current state.
        /// </summary>
        /// <param name="parameter">
        /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
        /// </param>
        /// <returns>True if this command can be executed; otherwise, false.</returns>
        public bool CanExecute(object parameter) => canExecute == null ? true : canExecute();

        /// <summary>
        /// Executes the <see cref="RelayCommand"/> on the current command target.
        /// </summary>
        /// <param name="parameter">
        /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
        /// </param>
        public void Execute(object parameter)
        {
            execute();
        }

        /// <summary>
        /// Method used to raise the <see cref="CanExecuteChanged"/> event
        /// to indicate that the return value of the <see cref="CanExecute"/>
        /// method has changed.
        /// </summary>
        public void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    }
}
使用System.Windows;
使用System.Windows.Controls;
使用System.Windows.Data;
命名空间WpfApp2
{
/// 
///MainWindow.xaml的交互逻辑
/// 
公共部分类主窗口:窗口
{
MainWindowViewModel\u viewModel;
公共主窗口()
{
_viewModel=新的主窗口viewModel();
DataContext=_viewModel;
初始化组件();
}
私有void ItemsDataGrid_RowEditEnding(对象发送方,DataGridRowEditEndingEventArgs e)
{   
if(ItemsDataGrid.SelectedIndex==ItemsDataGrid.Items.Count-2)
{
DataGridRow行=ItemsDataGrid
.ItemContainerGenerator.ContainerFromItem(CollectionView.NewItemPlaceholder)作为DataGridRow;
var rowIndex=row.GetIndex();
if(行可聚焦)
row.Focus();
DataGridCell cell=ItemsDataGrid.GetCell(行,0);
如果(单元格!=null)
{
DataGridCellInfo DataGridCellInfo=新的DataGridCellInfo(单元格);
if(单元格可聚焦)
cell.Focus();
}
}
}
}
}
MainWindowViewModel.CS

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApp2
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        MainWindowViewModel _viewModel;
        public MainWindow()
        {
            _viewModel = new MainWindowViewModel();
            DataContext = _viewModel;
            InitializeComponent();
        }


        private void ItemsDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
        {   
            if (ItemsDataGrid.SelectedIndex == ItemsDataGrid.Items.Count - 2)
            {
                DataGridRow row = ItemsDataGrid
                    .ItemContainerGenerator.ContainerFromItem(CollectionView.NewItemPlaceholder) as DataGridRow;

                var rowIndex = row.GetIndex();

                if (row.Focusable)
                    row.Focus();

                DataGridCell cell = ItemsDataGrid.GetCell(row, 0);
                if (cell != null)
                {
                    DataGridCellInfo dataGridCellInfo = new DataGridCellInfo(cell);
                    if (cell.Focusable)
                        cell.Focus();
                }
            }
        }
    }
}
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
using PropertyChanged;

namespace WpfApp2
{
    [AddINotifyPropertyChangedInterface]
    public class MainWindowViewModel
    {
        public MainWindowViewModel()
        {
            Items = new ObservableCollection<Item>(
                new List<Item>
                {
                    new Item {ItemDescriptionID=1, Quantity=1, Notes="Little Red Wagon"},
                    new Item {ItemDescriptionID=2, Quantity=1, Notes="I Want a Pony"},
                }
            );
            FilterableItems = CollectionViewSource.GetDefaultView(Items);
            EditableFilterableItems = FilterableItems as IEditableCollectionView;
        }
        public ObservableCollection<Item> Items { get; set; }
        public ICollectionView FilterableItems { get; set; }
        public IEditableCollectionView EditableFilterableItems { get; set; }

        public Item SelectedItem { get; set; }

        public List<ItemDescription> ItemDescriptions => new List<ItemDescription>
        {
            new ItemDescription { ItemDescriptionID = 1, Description="Wagon" },
            new ItemDescription { ItemDescriptionID = 2, Description="Pony" },
            new ItemDescription { ItemDescriptionID = 3, Description="Train" },
            new ItemDescription { ItemDescriptionID = 4, Description="Dump Truck" },
        };
    }
}
public class Item : EditableObject<Item>
{
    public int Quantity { get; set; }
    public int ItemDescriptionID { get; set; }
    public string Notes { get; set; }
}

public class ItemDescription
{
    public int ItemDescriptionID { get; set; }
    public string Description { get; set; }
}
using System.Windows;

namespace WpfApp2
{
    /// <summary>
    /// http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
    /// </summary>
    public class BindingProxy : Freezable
    {
        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }

        public object Data
        {
            get { return GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
    }
}
using System;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace WpfApp2
{
    public static class DataGridHelper
    {
        public static T GetVisualChild<T>(Visual parent) where T : Visual
        {
            T child = default(T);
            int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < numVisuals; i++)
            {
                Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
                child = v as T;
                if (child == null)
                {
                    child = GetVisualChild<T>(v);
                }
                if (child != null)
                {
                    break;
                }
            }
            return child;
        }
        public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int column)
        {
            if (row != null)
            {
                DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);

                if (presenter == null)
                {
                    grid.ScrollIntoView(row, grid.Columns[column]);
                    presenter = GetVisualChild<DataGridCellsPresenter>(row);
                }

                DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
                return cell;
            }
            return null;
        }
        public static DataGridCell GetCell(this DataGrid grid, int row, int column)
        {
            DataGridRow rowContainer = grid.GetRow(row);
            return grid.GetCell(rowContainer, column);
        }
    }
}
using System;
using System.ComponentModel;

namespace WpfApp2
{
    public abstract class EditableObject<T> : IEditableObject
    {
        private T Cache { get; set; }

        private object CurrentModel
        {
            get { return this; }
        }

        public RelayCommand CancelEditCommand
        {
            get { return new RelayCommand(CancelEdit); }
        }

        #region IEditableObject Members
        public void BeginEdit()
        {
            Cache = Activator.CreateInstance<T>();

            //Set Properties of Cache
            foreach (var info in CurrentModel.GetType().GetProperties())
            {
                if (!info.CanRead || !info.CanWrite) continue;
                var oldValue = info.GetValue(CurrentModel, null);
                Cache.GetType().GetProperty(info.Name).SetValue(Cache, oldValue, null);
            }
        }

        public virtual void EndEdit()
        {
            Cache = default(T);
        }


        public void CancelEdit()
        {
            foreach (var info in CurrentModel.GetType().GetProperties())
            {
                if (!info.CanRead || !info.CanWrite) continue;
                var oldValue = info.GetValue(Cache, null);
                CurrentModel.GetType().GetProperty(info.Name).SetValue(CurrentModel, oldValue, null);
            }
        }
        #endregion
    }
}
using System;
using System.Windows.Input;

namespace WpfApp2
{
    /// <summary>
    /// A command whose sole purpose is to relay its functionality to other objects by invoking delegates. 
    /// The default return value for the CanExecute method is 'true'.
    /// <see cref="RaiseCanExecuteChanged"/> needs to be called whenever
    /// <see cref="CanExecute"/> is expected to return a different value.
    /// </summary>
    public class RelayCommand : ICommand
    {
        #region Private members
        /// <summary>
        /// Creates a new command that can always execute.
        /// </summary>
        private readonly Action execute;

        /// <summary>
        /// True if command is executing, false otherwise
        /// </summary>
        private readonly Func<bool> canExecute;
        #endregion

        /// <summary>
        /// Initializes a new instance of <see cref="RelayCommand"/> that can always execute.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        public RelayCommand(Action execute) : this(execute, canExecute: null) { }

        /// <summary>
        /// Initializes a new instance of <see cref="RelayCommand"/>.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        /// <param name="canExecute">The execution status logic.</param>
        public RelayCommand(Action execute, Func<bool> canExecute)
        {
            this.execute = execute ?? throw new ArgumentNullException("execute");
            this.canExecute = canExecute;
        }

        /// <summary>
        /// Raised when RaiseCanExecuteChanged is called.
        /// </summary>
        public event EventHandler CanExecuteChanged;

        /// <summary>
        /// Determines whether this <see cref="RelayCommand"/> can execute in its current state.
        /// </summary>
        /// <param name="parameter">
        /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
        /// </param>
        /// <returns>True if this command can be executed; otherwise, false.</returns>
        public bool CanExecute(object parameter) => canExecute == null ? true : canExecute();

        /// <summary>
        /// Executes the <see cref="RelayCommand"/> on the current command target.
        /// </summary>
        /// <param name="parameter">
        /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
        /// </param>
        public void Execute(object parameter)
        {
            execute();
        }

        /// <summary>
        /// Method used to raise the <see cref="CanExecuteChanged"/> event
        /// to indicate that the return value of the <see cref="CanExecute"/>
        /// method has changed.
        /// </summary>
        public void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    }
}
使用System.Collections.Generic;
使用System.Collections.ObjectModel;
使用系统组件模型;
使用System.Windows.Data;
使用改变的财产;
命名空间WpfApp2
{
[AddInnotifyPropertyChangedInterface]
公共类MainWindowViewModel
{
公共主窗口视图模型()
{
Items=新的ObservableCollection(
新名单
{
新项目{ItemDescriptionID=1,数量=1,Notes=“Little Red Cargo”},
新项目{ItemDescriptionID=2,Quantity=1,Notes=“我想要一匹小马”},
}
);
FilterableItems=CollectionViewSource.GetDefaultView(项目);
EditableFilterableItems=作为IEditableCollectionView的FilterableItems;
}
公共ObservableCollection项{get;set;}
public ICollectionView FilterableItems{get;set;}
公共IEditableCollectionView可编辑过滤项{get;set;}
公共项SelectedItem{get;set;}
公共列表项描述=>新列表
{
新的ItemDescription{ItemDescriptionID=1,Description=“货车”},
新的ItemDescription{ItemDescriptionID=2,Description=“Pony”},
新的ItemDescription{ItemDescriptionID=3,Description=“Train”},
新ItemDescription{ItemDescriptionID=4,Description=“自卸车”},
};
}
}
Item.CS,itemsdescription.CS

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApp2
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        MainWindowViewModel _viewModel;
        public MainWindow()
        {
            _viewModel = new MainWindowViewModel();
            DataContext = _viewModel;
            InitializeComponent();
        }


        private void ItemsDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
        {   
            if (ItemsDataGrid.SelectedIndex == ItemsDataGrid.Items.Count - 2)
            {
                DataGridRow row = ItemsDataGrid
                    .ItemContainerGenerator.ContainerFromItem(CollectionView.NewItemPlaceholder) as DataGridRow;

                var rowIndex = row.GetIndex();

                if (row.Focusable)
                    row.Focus();

                DataGridCell cell = ItemsDataGrid.GetCell(row, 0);
                if (cell != null)
                {
                    DataGridCellInfo dataGridCellInfo = new DataGridCellInfo(cell);
                    if (cell.Focusable)
                        cell.Focus();
                }
            }
        }
    }
}
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
using PropertyChanged;

namespace WpfApp2
{
    [AddINotifyPropertyChangedInterface]
    public class MainWindowViewModel
    {
        public MainWindowViewModel()
        {
            Items = new ObservableCollection<Item>(
                new List<Item>
                {
                    new Item {ItemDescriptionID=1, Quantity=1, Notes="Little Red Wagon"},
                    new Item {ItemDescriptionID=2, Quantity=1, Notes="I Want a Pony"},
                }
            );
            FilterableItems = CollectionViewSource.GetDefaultView(Items);
            EditableFilterableItems = FilterableItems as IEditableCollectionView;
        }
        public ObservableCollection<Item> Items { get; set; }
        public ICollectionView FilterableItems { get; set; }
        public IEditableCollectionView EditableFilterableItems { get; set; }

        public Item SelectedItem { get; set; }

        public List<ItemDescription> ItemDescriptions => new List<ItemDescription>
        {
            new ItemDescription { ItemDescriptionID = 1, Description="Wagon" },
            new ItemDescription { ItemDescriptionID = 2, Description="Pony" },
            new ItemDescription { ItemDescriptionID = 3, Description="Train" },
            new ItemDescription { ItemDescriptionID = 4, Description="Dump Truck" },
        };
    }
}
public class Item : EditableObject<Item>
{
    public int Quantity { get; set; }
    public int ItemDescriptionID { get; set; }
    public string Notes { get; set; }
}

public class ItemDescription
{
    public int ItemDescriptionID { get; set; }
    public string Description { get; set; }
}
using System.Windows;

namespace WpfApp2
{
    /// <summary>
    /// http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
    /// </summary>
    public class BindingProxy : Freezable
    {
        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }

        public object Data
        {
            get { return GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
    }
}
using System;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace WpfApp2
{
    public static class DataGridHelper
    {
        public static T GetVisualChild<T>(Visual parent) where T : Visual
        {
            T child = default(T);
            int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < numVisuals; i++)
            {
                Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
                child = v as T;
                if (child == null)
                {
                    child = GetVisualChild<T>(v);
                }
                if (child != null)
                {
                    break;
                }
            }
            return child;
        }
        public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int column)
        {
            if (row != null)
            {
                DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);

                if (presenter == null)
                {
                    grid.ScrollIntoView(row, grid.Columns[column]);
                    presenter = GetVisualChild<DataGridCellsPresenter>(row);
                }

                DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
                return cell;
            }
            return null;
        }
        public static DataGridCell GetCell(this DataGrid grid, int row, int column)
        {
            DataGridRow rowContainer = grid.GetRow(row);
            return grid.GetCell(rowContainer, column);
        }
    }
}
using System;
using System.ComponentModel;

namespace WpfApp2
{
    public abstract class EditableObject<T> : IEditableObject
    {
        private T Cache { get; set; }

        private object CurrentModel
        {
            get { return this; }
        }

        public RelayCommand CancelEditCommand
        {
            get { return new RelayCommand(CancelEdit); }
        }

        #region IEditableObject Members
        public void BeginEdit()
        {
            Cache = Activator.CreateInstance<T>();

            //Set Properties of Cache
            foreach (var info in CurrentModel.GetType().GetProperties())
            {
                if (!info.CanRead || !info.CanWrite) continue;
                var oldValue = info.GetValue(CurrentModel, null);
                Cache.GetType().GetProperty(info.Name).SetValue(Cache, oldValue, null);
            }
        }

        public virtual void EndEdit()
        {
            Cache = default(T);
        }


        public void CancelEdit()
        {
            foreach (var info in CurrentModel.GetType().GetProperties())
            {
                if (!info.CanRead || !info.CanWrite) continue;
                var oldValue = info.GetValue(Cache, null);
                CurrentModel.GetType().GetProperty(info.Name).SetValue(CurrentModel, oldValue, null);
            }
        }
        #endregion
    }
}
using System;
using System.Windows.Input;

namespace WpfApp2
{
    /// <summary>
    /// A command whose sole purpose is to relay its functionality to other objects by invoking delegates. 
    /// The default return value for the CanExecute method is 'true'.
    /// <see cref="RaiseCanExecuteChanged"/> needs to be called whenever
    /// <see cref="CanExecute"/> is expected to return a different value.
    /// </summary>
    public class RelayCommand : ICommand
    {
        #region Private members
        /// <summary>
        /// Creates a new command that can always execute.
        /// </summary>
        private readonly Action execute;

        /// <summary>
        /// True if command is executing, false otherwise
        /// </summary>
        private readonly Func<bool> canExecute;
        #endregion

        /// <summary>
        /// Initializes a new instance of <see cref="RelayCommand"/> that can always execute.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        public RelayCommand(Action execute) : this(execute, canExecute: null) { }

        /// <summary>
        /// Initializes a new instance of <see cref="RelayCommand"/>.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        /// <param name="canExecute">The execution status logic.</param>
        public RelayCommand(Action execute, Func<bool> canExecute)
        {
            this.execute = execute ?? throw new ArgumentNullException("execute");
            this.canExecute = canExecute;
        }

        /// <summary>
        /// Raised when RaiseCanExecuteChanged is called.
        /// </summary>
        public event EventHandler CanExecuteChanged;

        /// <summary>
        /// Determines whether this <see cref="RelayCommand"/> can execute in its current state.
        /// </summary>
        /// <param name="parameter">
        /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
        /// </param>
        /// <returns>True if this command can be executed; otherwise, false.</returns>
        public bool CanExecute(object parameter) => canExecute == null ? true : canExecute();

        /// <summary>
        /// Executes the <see cref="RelayCommand"/> on the current command target.
        /// </summary>
        /// <param name="parameter">
        /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
        /// </param>
        public void Execute(object parameter)
        {
            execute();
        }

        /// <summary>
        /// Method used to raise the <see cref="CanExecuteChanged"/> event
        /// to indicate that the return value of the <see cref="CanExecute"/>
        /// method has changed.
        /// </summary>
        public void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    }
}
公共类项:EditableObject
{
公共整数数量{get;set;}
public int itemsdescriptionid{get;set;}
公共字符串注释{get;set;}
}
公共类项目描述
{
public int itemsdescriptionid{get;set;}
公共字符串说明{get;set;}
}
BindingProxy.CS

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApp2
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        MainWindowViewModel _viewModel;
        public MainWindow()
        {
            _viewModel = new MainWindowViewModel();
            DataContext = _viewModel;
            InitializeComponent();
        }


        private void ItemsDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
        {   
            if (ItemsDataGrid.SelectedIndex == ItemsDataGrid.Items.Count - 2)
            {
                DataGridRow row = ItemsDataGrid
                    .ItemContainerGenerator.ContainerFromItem(CollectionView.NewItemPlaceholder) as DataGridRow;

                var rowIndex = row.GetIndex();

                if (row.Focusable)
                    row.Focus();

                DataGridCell cell = ItemsDataGrid.GetCell(row, 0);
                if (cell != null)
                {
                    DataGridCellInfo dataGridCellInfo = new DataGridCellInfo(cell);
                    if (cell.Focusable)
                        cell.Focus();
                }
            }
        }
    }
}
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
using PropertyChanged;

namespace WpfApp2
{
    [AddINotifyPropertyChangedInterface]
    public class MainWindowViewModel
    {
        public MainWindowViewModel()
        {
            Items = new ObservableCollection<Item>(
                new List<Item>
                {
                    new Item {ItemDescriptionID=1, Quantity=1, Notes="Little Red Wagon"},
                    new Item {ItemDescriptionID=2, Quantity=1, Notes="I Want a Pony"},
                }
            );
            FilterableItems = CollectionViewSource.GetDefaultView(Items);
            EditableFilterableItems = FilterableItems as IEditableCollectionView;
        }
        public ObservableCollection<Item> Items { get; set; }
        public ICollectionView FilterableItems { get; set; }
        public IEditableCollectionView EditableFilterableItems { get; set; }

        public Item SelectedItem { get; set; }

        public List<ItemDescription> ItemDescriptions => new List<ItemDescription>
        {
            new ItemDescription { ItemDescriptionID = 1, Description="Wagon" },
            new ItemDescription { ItemDescriptionID = 2, Description="Pony" },
            new ItemDescription { ItemDescriptionID = 3, Description="Train" },
            new ItemDescription { ItemDescriptionID = 4, Description="Dump Truck" },
        };
    }
}
public class Item : EditableObject<Item>
{
    public int Quantity { get; set; }
    public int ItemDescriptionID { get; set; }
    public string Notes { get; set; }
}

public class ItemDescription
{
    public int ItemDescriptionID { get; set; }
    public string Description { get; set; }
}
using System.Windows;

namespace WpfApp2
{
    /// <summary>
    /// http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
    /// </summary>
    public class BindingProxy : Freezable
    {
        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }

        public object Data
        {
            get { return GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
    }
}
using System;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace WpfApp2
{
    public static class DataGridHelper
    {
        public static T GetVisualChild<T>(Visual parent) where T : Visual
        {
            T child = default(T);
            int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < numVisuals; i++)
            {
                Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
                child = v as T;
                if (child == null)
                {
                    child = GetVisualChild<T>(v);
                }
                if (child != null)
                {
                    break;
                }
            }
            return child;
        }
        public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int column)
        {
            if (row != null)
            {
                DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);

                if (presenter == null)
                {
                    grid.ScrollIntoView(row, grid.Columns[column]);
                    presenter = GetVisualChild<DataGridCellsPresenter>(row);
                }

                DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
                return cell;
            }
            return null;
        }
        public static DataGridCell GetCell(this DataGrid grid, int row, int column)
        {
            DataGridRow rowContainer = grid.GetRow(row);
            return grid.GetCell(rowContainer, column);
        }
    }
}
using System;
using System.ComponentModel;

namespace WpfApp2
{
    public abstract class EditableObject<T> : IEditableObject
    {
        private T Cache { get; set; }

        private object CurrentModel
        {
            get { return this; }
        }

        public RelayCommand CancelEditCommand
        {
            get { return new RelayCommand(CancelEdit); }
        }

        #region IEditableObject Members
        public void BeginEdit()
        {
            Cache = Activator.CreateInstance<T>();

            //Set Properties of Cache
            foreach (var info in CurrentModel.GetType().GetProperties())
            {
                if (!info.CanRead || !info.CanWrite) continue;
                var oldValue = info.GetValue(CurrentModel, null);
                Cache.GetType().GetProperty(info.Name).SetValue(Cache, oldValue, null);
            }
        }

        public virtual void EndEdit()
        {
            Cache = default(T);
        }


        public void CancelEdit()
        {
            foreach (var info in CurrentModel.GetType().GetProperties())
            {
                if (!info.CanRead || !info.CanWrite) continue;
                var oldValue = info.GetValue(Cache, null);
                CurrentModel.GetType().GetProperty(info.Name).SetValue(CurrentModel, oldValue, null);
            }
        }
        #endregion
    }
}
using System;
using System.Windows.Input;

namespace WpfApp2
{
    /// <summary>
    /// A command whose sole purpose is to relay its functionality to other objects by invoking delegates. 
    /// The default return value for the CanExecute method is 'true'.
    /// <see cref="RaiseCanExecuteChanged"/> needs to be called whenever
    /// <see cref="CanExecute"/> is expected to return a different value.
    /// </summary>
    public class RelayCommand : ICommand
    {
        #region Private members
        /// <summary>
        /// Creates a new command that can always execute.
        /// </summary>
        private readonly Action execute;

        /// <summary>
        /// True if command is executing, false otherwise
        /// </summary>
        private readonly Func<bool> canExecute;
        #endregion

        /// <summary>
        /// Initializes a new instance of <see cref="RelayCommand"/> that can always execute.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        public RelayCommand(Action execute) : this(execute, canExecute: null) { }

        /// <summary>
        /// Initializes a new instance of <see cref="RelayCommand"/>.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        /// <param name="canExecute">The execution status logic.</param>
        public RelayCommand(Action execute, Func<bool> canExecute)
        {
            this.execute = execute ?? throw new ArgumentNullException("execute");
            this.canExecute = canExecute;
        }

        /// <summary>
        /// Raised when RaiseCanExecuteChanged is called.
        /// </summary>
        public event EventHandler CanExecuteChanged;

        /// <summary>
        /// Determines whether this <see cref="RelayCommand"/> can execute in its current state.
        /// </summary>
        /// <param name="parameter">
        /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
        /// </param>
        /// <returns>True if this command can be executed; otherwise, false.</returns>
        public bool CanExecute(object parameter) => canExecute == null ? true : canExecute();

        /// <summary>
        /// Executes the <see cref="RelayCommand"/> on the current command target.
        /// </summary>
        /// <param name="parameter">
        /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
        /// </param>
        public void Execute(object parameter)
        {
            execute();
        }

        /// <summary>
        /// Method used to raise the <see cref="CanExecuteChanged"/> event
        /// to indicate that the return value of the <see cref="CanExecute"/>
        /// method has changed.
        /// </summary>
        public void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    }
}
使用System.Windows;
命名空间WpfApp2
{
/// 
/// http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
/// 
公共类BindingProxy:Freezable
{
受保护的重写Freezable CreateInstanceCore()
{
返回新的BindingProxy();
}
公共对象数据
{
获取{返回GetValue(DataProperty);}
set{SetValue(DataProperty,value);}
}
//使用DependencyProperty作为数据的后备存储。这将启用动画、样式、绑定等。。。
公共静态只读DependencyProperty DataProperty=
Register(“数据”、typeof(对象)、typeof(BindingProxy)、新UIPropertyMetadata(null));
}
}
DataGridHelper.CS

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApp2
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        MainWindowViewModel _viewModel;
        public MainWindow()
        {
            _viewModel = new MainWindowViewModel();
            DataContext = _viewModel;
            InitializeComponent();
        }


        private void ItemsDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
        {   
            if (ItemsDataGrid.SelectedIndex == ItemsDataGrid.Items.Count - 2)
            {
                DataGridRow row = ItemsDataGrid
                    .ItemContainerGenerator.ContainerFromItem(CollectionView.NewItemPlaceholder) as DataGridRow;

                var rowIndex = row.GetIndex();

                if (row.Focusable)
                    row.Focus();

                DataGridCell cell = ItemsDataGrid.GetCell(row, 0);
                if (cell != null)
                {
                    DataGridCellInfo dataGridCellInfo = new DataGridCellInfo(cell);
                    if (cell.Focusable)
                        cell.Focus();
                }
            }
        }
    }
}
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
using PropertyChanged;

namespace WpfApp2
{
    [AddINotifyPropertyChangedInterface]
    public class MainWindowViewModel
    {
        public MainWindowViewModel()
        {
            Items = new ObservableCollection<Item>(
                new List<Item>
                {
                    new Item {ItemDescriptionID=1, Quantity=1, Notes="Little Red Wagon"},
                    new Item {ItemDescriptionID=2, Quantity=1, Notes="I Want a Pony"},
                }
            );
            FilterableItems = CollectionViewSource.GetDefaultView(Items);
            EditableFilterableItems = FilterableItems as IEditableCollectionView;
        }
        public ObservableCollection<Item> Items { get; set; }
        public ICollectionView FilterableItems { get; set; }
        public IEditableCollectionView EditableFilterableItems { get; set; }

        public Item SelectedItem { get; set; }

        public List<ItemDescription> ItemDescriptions => new List<ItemDescription>
        {
            new ItemDescription { ItemDescriptionID = 1, Description="Wagon" },
            new ItemDescription { ItemDescriptionID = 2, Description="Pony" },
            new ItemDescription { ItemDescriptionID = 3, Description="Train" },
            new ItemDescription { ItemDescriptionID = 4, Description="Dump Truck" },
        };
    }
}
public class Item : EditableObject<Item>
{
    public int Quantity { get; set; }
    public int ItemDescriptionID { get; set; }
    public string Notes { get; set; }
}

public class ItemDescription
{
    public int ItemDescriptionID { get; set; }
    public string Description { get; set; }
}
using System.Windows;

namespace WpfApp2
{
    /// <summary>
    /// http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
    /// </summary>
    public class BindingProxy : Freezable
    {
        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }

        public object Data
        {
            get { return GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
    }
}
using System;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace WpfApp2
{
    public static class DataGridHelper
    {
        public static T GetVisualChild<T>(Visual parent) where T : Visual
        {
            T child = default(T);
            int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < numVisuals; i++)
            {
                Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
                child = v as T;
                if (child == null)
                {
                    child = GetVisualChild<T>(v);
                }
                if (child != null)
                {
                    break;
                }
            }
            return child;
        }
        public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int column)
        {
            if (row != null)
            {
                DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);

                if (presenter == null)
                {
                    grid.ScrollIntoView(row, grid.Columns[column]);
                    presenter = GetVisualChild<DataGridCellsPresenter>(row);
                }

                DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
                return cell;
            }
            return null;
        }
        public static DataGridCell GetCell(this DataGrid grid, int row, int column)
        {
            DataGridRow rowContainer = grid.GetRow(row);
            return grid.GetCell(rowContainer, column);
        }
    }
}
using System;
using System.ComponentModel;

namespace WpfApp2
{
    public abstract class EditableObject<T> : IEditableObject
    {
        private T Cache { get; set; }

        private object CurrentModel
        {
            get { return this; }
        }

        public RelayCommand CancelEditCommand
        {
            get { return new RelayCommand(CancelEdit); }
        }

        #region IEditableObject Members
        public void BeginEdit()
        {
            Cache = Activator.CreateInstance<T>();

            //Set Properties of Cache
            foreach (var info in CurrentModel.GetType().GetProperties())
            {
                if (!info.CanRead || !info.CanWrite) continue;
                var oldValue = info.GetValue(CurrentModel, null);
                Cache.GetType().GetProperty(info.Name).SetValue(Cache, oldValue, null);
            }
        }

        public virtual void EndEdit()
        {
            Cache = default(T);
        }


        public void CancelEdit()
        {
            foreach (var info in CurrentModel.GetType().GetProperties())
            {
                if (!info.CanRead || !info.CanWrite) continue;
                var oldValue = info.GetValue(Cache, null);
                CurrentModel.GetType().GetProperty(info.Name).SetValue(CurrentModel, oldValue, null);
            }
        }
        #endregion
    }
}
using System;
using System.Windows.Input;

namespace WpfApp2
{
    /// <summary>
    /// A command whose sole purpose is to relay its functionality to other objects by invoking delegates. 
    /// The default return value for the CanExecute method is 'true'.
    /// <see cref="RaiseCanExecuteChanged"/> needs to be called whenever
    /// <see cref="CanExecute"/> is expected to return a different value.
    /// </summary>
    public class RelayCommand : ICommand
    {
        #region Private members
        /// <summary>
        /// Creates a new command that can always execute.
        /// </summary>
        private readonly Action execute;

        /// <summary>
        /// True if command is executing, false otherwise
        /// </summary>
        private readonly Func<bool> canExecute;
        #endregion

        /// <summary>
        /// Initializes a new instance of <see cref="RelayCommand"/> that can always execute.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        public RelayCommand(Action execute) : this(execute, canExecute: null) { }

        /// <summary>
        /// Initializes a new instance of <see cref="RelayCommand"/>.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        /// <param name="canExecute">The execution status logic.</param>
        public RelayCommand(Action execute, Func<bool> canExecute)
        {
            this.execute = execute ?? throw new ArgumentNullException("execute");
            this.canExecute = canExecute;
        }

        /// <summary>
        /// Raised when RaiseCanExecuteChanged is called.
        /// </summary>
        public event EventHandler CanExecuteChanged;

        /// <summary>
        /// Determines whether this <see cref="RelayCommand"/> can execute in its current state.
        /// </summary>
        /// <param name="parameter">
        /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
        /// </param>
        /// <returns>True if this command can be executed; otherwise, false.</returns>
        public bool CanExecute(object parameter) => canExecute == null ? true : canExecute();

        /// <summary>
        /// Executes the <see cref="RelayCommand"/> on the current command target.
        /// </summary>
        /// <param name="parameter">
        /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
        /// </param>
        public void Execute(object parameter)
        {
            execute();
        }

        /// <summary>
        /// Method used to raise the <see cref="CanExecuteChanged"/> event
        /// to indicate that the return value of the <see cref="CanExecute"/>
        /// method has changed.
        /// </summary>
        public void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    }
}
使用系统;
使用System.Windows.Controls;
使用System.Windows.Controls.Primitives;
使用