WPF DataGrid - 如何自动设置新添加行的编辑模式?

3
我有一个程序,到目前为止遵循MVVM原则/规则,没有任何代码。我有一个DataGrid,用户可以添加、编辑或删除代表学生的行。用户可以通过单击“+”按钮向列表中添加一行,但是为了编辑行,用户必须先单击他刚刚添加的行,这不太用户友好。
我一直在尝试将新添加的行设置为编辑模式,但是我所有的尝试要么失败了,要么工作正常,但对程序的其余部分产生了一些干扰性副作用。我在网上查找了解决方案,但找到的解决方案要么过于复杂,要么也有不良副作用。
我创建了一个类似的程序,代码量较少,以便更容易地展示我的程序和DataGrid的结构:

模型

public class Student
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string PhoneNumber { get; set; }
    public string Address { get; set; }
    public string Email { get; set; }
}

视图模型

public class StudentsViewModel : INotifyPropertyChanged
{
    public StudentsViewModel()
    {
        Students = new ObservableCollection<Student>();
    }

    private ObservableCollection<Student> students;
    public ObservableCollection<Student> Students
    {
        get { return students; }
        set
        {
            students = value;
            NotifyPropertyChanged(nameof(Students));
        }
    }

    private Student selectedStudent;
    public Student SelectedStudent
    {
        get { return selectedStudent; }
        set
        {
            selectedStudent = value;
            NotifyPropertyChanged(nameof(SelectedStudent));
        }
    }

    private ICommand addRow;
    public ICommand AddRow
    {
        get
        {
            if(addRow == null)
            {
                addRow = new RelayCommand(
                    parameter => AddStudent(new Student()),
                    parameter => true
                );
            }
            return addRow;
        }
    }

    private ICommand removeCmd;
    public ICommand RemoveCmd
    {
        get
        {
            removeCmd = new RelayCommand(
                parameter => RemoveStudent(parameter as Student),
                parameter => parameter != null
            );
            return removeCmd;
        }
    }

    private void AddStudent(Student studentToAdd)
    {
        Students.Add(studentToAdd);
    }

    private void RemoveStudent(Student studentToRemove)
    {
        if (Students.Contains(studentToRemove))
        {
            Students.Remove(studentToRemove);
        }
    }

    #region INotify

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void NotifyPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion
}

视图

<Window x:Class="DataGridExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DataGridExample"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="600"
        Width="1000">
    <Window.Resources>
        <local:StudentsViewModel x:Key="StudentsVm"/>
    </Window.Resources>
    <Grid DataContext="{Binding Source={StaticResource StudentsVm}}">
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
        <DockPanel LastChildFill="False"
                   Background="#FF2C58EC">
            <Button Command="{Binding AddRow}"
                    Height="25"
                    Margin="5">
                <Button.Template>
                    <ControlTemplate>
                        <Image Source="/Images/AddItem.png"/>
                    </ControlTemplate>
                </Button.Template>
            </Button>
            <Button Command="{Binding RemoveCmd}"
                    CommandParameter="{Binding ElementName=StudentDataGrid, Path=SelectedItem}"
                    Height="25"
                    Margin="5">
                <Button.Template>
                    <ControlTemplate>
                        <Image Source="/Images/DeleteItem.png"/>
                    </ControlTemplate>
                </Button.Template>
            </Button>
        </DockPanel>
        <DataGrid ItemsSource="{Binding Students}"
                  SelectedItem="{Binding Source={StaticResource StudentsVm}, Path=SelectedStudent, Mode=TwoWay}"
                  x:Name="StudentDataGrid"
                  ColumnWidth="*"
                  CanUserAddRows="False"
                  CanUserResizeRows="False"
                  CanUserResizeColumns="False"
                  CanUserSortColumns="False"
                  CanUserDeleteRows="False"
                  AutoGenerateColumns="False"
                  Grid.Row="1">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding FirstName, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
                                    Header="First Name">
                </DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding LastName, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
                                    Header="Last Name">
                </DataGridTextColumn>
                <DataGridTemplateColumn Header="Date of Birth">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding DateOfBirth, StringFormat={}{0:dd.MM.yyyy}, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <DatePicker SelectedDate="{Binding DateOfBirth, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
                                        DisplayDate="{Binding DateOfBirth, Mode=OneWay, UpdateSourceTrigger=LostFocus}">
                            </DatePicker>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>
                </DataGridTemplateColumn>
                <DataGridTextColumn Binding="{Binding PhoneNumber, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
                                    Header="Phone Number">
                </DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding Address, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
                                    Header="Address">
                </DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding Email, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
                                    Header="Email">
                </DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

我希望解决方案能够兼容MVVM,但现在只要它不引起其他问题且不需要大量框架或文件编码,我就会满意。

我希望结果看起来像这个示例,但这个也可以接受,只要在单击“+”按钮后无需鼠标移动即可。


你的添加按钮应该绑定到ViewModel而不是绑定到ViewModel的控件,这是第一件事情,当我看到UI元素上有名称时,我总是感到不舒服。我猜你不想显示Model(学生)的更改,因为没有INPC?你看过这个SO答案吗? - XAMlMAX
你是指添加按钮的命令参数吗?就我所见,结果是一样的,这是一种不好的做法吗?我在WPF方面的经验不到一年,所以请原谅我问这个问题,如果您绑定它们,给UI元素命名有什么不好的地方呢?此外,在我的模型中,实际上并不需要INPC(至少在这个示例中),因为它只绑定到一个项源 - 我的ObservableCollection,该集合更新对UI的更改。 - Swapper
@XAMIMAX,你提供的答案与我想要的完全不同,我不需要单击编辑,尤其是那种具有干扰性副作用的编辑方式(请阅读该答案的评论)。我想要的是在添加行后立即选择并进行编辑,而无需单击它。 - Swapper
这个答案的想法是为了给你一个指示,告诉你如何实现这个功能。你需要使用 DataTrigger 而不是点击事件,然后将其绑定到你的学生类上的属性,该属性将指示一个新记录(可能是 ID 为 0)。这样说通了吗? - XAMlMAX
@XAMIMAX,我尝试了你建议的方法,并在学生类中添加了ID属性以进行测试,但它没有起作用,或者至少仅将IsEditing设置为True是不够的。 - Swapper
显示剩余2条评论
1个回答

1
我找到了一种实现你想要的方式,但它似乎不是很健壮,所以请小心使用。首先,你应该连接到你的 DataGridLoadingRow 事件。

XAML

<DataGrid ...
          LoadingRow="StudentDataGrid_LoadingRow"
          ...>

代码后台

private async void StudentDataGrid_LoadingRow(object sender, DataGridRowEventArgs e) {
    // Force asynchrony to run callback after UI has finished loading.
    await Task.Yield();
    // Mark the new row as selected
    StudentDataGrid.SelectedItem = e.Row.Item;
    StudentDataGrid.CurrentCell = new DataGridCellInfo(e.Row.Item, StudentDataGrid.Columns[0]);
    // Enter edit mode
    StudentDataGrid.BeginEdit();
}

通过将方法设置为async并通过await Task.Yield()调用强制异步执行,您可以让UI在通过BeginEdit()调用开始编辑之前完成行的加载。
我写这篇文章更多是作为一个实验,我不知道我是否会推荐这种方法,但我希望它能在有人找到更好的解决方案之前提供帮助。

1
感谢您的答复,对我来说似乎没有任何问题。目前会使用这个方法,除非我找到更好的答案。如果我找不到更好的答案,那么我将接受这个作为解决方案。 - Swapper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接