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;
使用