You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>

方案说明

  1. 提示行标记:通过IsPlaceholder属性区分提示行和数据行,样式上设置提示行为灰色、手型光标、不可编辑
  2. 点击逻辑:点击提示行时,移除占位对象并插入空的可编辑对象,同时自动聚焦到新行
  3. 自动补全提示行:监听数据行的属性变化,当三个必填属性都填写完成后,自动添加新的提示行
  4. 为什么不用CompositeCollection:CompositeCollection的绑定逻辑复杂,尤其是处理集合动态变化和交互事件时,直接操作ObservableCollection更简洁可控,也更容易维护

内容的提问来源于stack exchange,提问作者UGURENG

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 21:20:16