WPF DataGrid如何结合静态文本与绑定对象实现动态新增行?
WPF DataGrid 实现点击添加行+自动显示提示行方案
核心思路
放弃CompositeCollection方案,直接通过标记占位对象的方式实现:在OperationClass中新增IsPlaceholder属性区分提示行和真实数据行,通过操作ObservableCollection完成点击添加、自动补全提示行的逻辑,同时用样式控制提示行的外观和交互。
步骤1:修改OperationClass实体
添加IsPlaceholder标记属性,并实现INotifyPropertyChanged以便监听属性变化:
using System.ComponentModel; using System.Runtime.CompilerServices; public class OperationClass : INotifyPropertyChanged { private string _name; private DateTime? _start; private DateTime? _end; // 标记是否为提示占位行 public bool IsPlaceholder { get; set; } public string Name { get => _name; set { _name = value; OnPropertyChanged(); } } public DateTime? Start { get => _start; set { _start = value; OnPropertyChanged(); } } public DateTime? End { get => _end; set { _end = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
步骤2:ViewModel逻辑实现
处理行点击事件、属性变化监听、自动添加提示行的逻辑,同时实现一个简单的RelayCommand用于绑定点击命令:
先实现RelayCommand(命令绑定工具)
using System; using System.Windows.Input; public class RelayCommand<T> : ICommand { private readonly Action<T> _execute; private readonly Predicate<T> _canExecute; public RelayCommand(Action<T> execute, Predicate<T> canExecute = null) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } public bool CanExecute(object parameter) => _canExecute?.Invoke((T)parameter) ?? true; public void Execute(object parameter) => _execute((T)parameter); public event EventHandler CanExecuteChanged { add => CommandManager.RequerySuggested += value; remove => CommandManager.RequerySuggested -= value; } }
主ViewModel逻辑
using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Input; public class MainViewModel : INotifyPropertyChanged { private ObservableCollection<OperationClass> _operationClasses; public ObservableCollection<OperationClass> OperationClasses { get => _operationClasses; set { _operationClasses = value; OnPropertyChanged(); } } // 行点击命令 public ICommand RowClickCommand { get; } public MainViewModel() { OperationClasses = new ObservableCollection<OperationClass>(); AddPlaceholderRow(); // 初始化添加提示行 RowClickCommand = new RelayCommand<OperationClass>(OnRowClicked); // 监听集合变化,绑定/解绑属性监听 OperationClasses.CollectionChanged += OperationClasses_CollectionChanged; } private void OperationClasses_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) { foreach (OperationClass item in e.NewItems) { if (!item.IsPlaceholder) { item.PropertyChanged += OperationClass_PropertyChanged; } } } if (e.OldItems != null) { foreach (OperationClass item in e.OldItems) { item.PropertyChanged -= OperationClass_PropertyChanged; } } } // 监听数据行的属性变化,完成输入后自动添加提示行 private void OperationClass_PropertyChanged(object sender, PropertyChangedEventArgs e) { var item = sender as OperationClass; if (item == null || item.IsPlaceholder) return; // 检查三个属性是否都已填写完整 if (!string.IsNullOrEmpty(item.Name) && item.Start.HasValue && item.End.HasValue) { // 延迟执行避免UI卡顿 Application.Current.Dispatcher.BeginInvoke(() => { if (!OperationClasses.Any(x => x.IsPlaceholder)) { AddPlaceholderRow(); } }); } } // 处理提示行点击事件:替换为可编辑空行 private void OnRowClicked(OperationClass item) { if (item?.IsPlaceholder != true) return; var index = OperationClasses.IndexOf(item); OperationClasses.RemoveAt(index); var newEditableItem = new OperationClass(); OperationClasses.Insert(index, newEditableItem); // 自动聚焦到新行的Name单元格 Application.Current.Dispatcher.BeginInvoke(() => { var dataGrid = Application.Current.MainWindow.FindName("OperationDataGrid") as DataGrid; if (dataGrid != null) { dataGrid.SelectedItem = newEditableItem; dataGrid.ScrollIntoView(newEditableItem); dataGrid.BeginEdit(); } }); } // 添加提示行 private void AddPlaceholderRow() { OperationClasses.Add(new OperationClass { Name = "new Operation Class", IsPlaceholder = true }); } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
步骤3:XAML界面实现
绑定数据源、设置行/单元格样式、绑定点击命令:
<Window x:Class="WpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApp" Title="Operation Class Manager" Height="450" Width="800"> <Window.DataContext> <local:MainViewModel /> </Window.DataContext> <Grid> <DataGrid x:Name="OperationDataGrid" ItemsSource="{Binding OperationClasses}" AutoGenerateColumns="False" RowStyle="{StaticResource OperationRowStyle}" CellStyle="{StaticResource OperationCellStyle}"> <!-- 绑定行点击命令 --> <DataGrid.InputBindings> <MouseBinding MouseAction="LeftClick" Command="{Binding RowClickCommand}" CommandParameter="{Binding}" /> </DataGrid.InputBindings> <!-- 定义列 --> <DataGrid.Columns> <DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="*" /> <DataGridDateTimeColumn Header="Start" Binding="{Binding Start}" Width="Auto" /> <DataGridDateTimeColumn Header="End" Binding="{Binding End}" Width="Auto" /> </DataGrid.Columns> </DataGrid> </Grid> <!-- 定义行和单元格样式 --> <Window.Resources> <Style x:Key="OperationRowStyle" TargetType="DataGridRow"> <Style.Triggers> <DataTrigger Binding="{Binding IsPlaceholder}" Value="True"> <Setter Property="Foreground" Value="Gray" /> <Setter Property="Cursor" Value="Hand" /> </DataTrigger> </Style.Triggers> </Style> <Style x:Key="OperationCellStyle" TargetType="DataGridCell"> <Style.Triggers> <DataTrigger Binding="{Binding IsPlaceholder}" Value="True"> <Setter Property="IsEnabled" Value="False" /> </DataTrigger> </Style.Triggers> </Style> </Window.Resources> </Window>
方案说明
- 提示行标记:通过
IsPlaceholder属性区分提示行和数据行,样式上设置提示行为灰色、手型光标、不可编辑 - 点击逻辑:点击提示行时,移除占位对象并插入空的可编辑对象,同时自动聚焦到新行
- 自动补全提示行:监听数据行的属性变化,当三个必填属性都填写完成后,自动添加新的提示行
- 为什么不用CompositeCollection:CompositeCollection的绑定逻辑复杂,尤其是处理集合动态变化和交互事件时,直接操作
ObservableCollection更简洁可控,也更容易维护
内容的提问来源于stack exchange,提问作者UGURENG
相关产品推荐
相关产品推荐

