C# 保存时更新数据网格的WPF可编辑主详细信息

C# 保存时更新数据网格的WPF可编辑主详细信息,c#,wpf,xaml,mvvm,datagrid,C#,Wpf,Xaml,Mvvm,Datagrid,我对WPF非常陌生,所以我想我应该从一些简单的东西开始:一个允许用户管理用户的窗口。该窗口包含一个DataGrid,以及几个用于添加或编辑用户的输入控件。当用户在网格中选择记录时,数据将绑定到输入控件。然后,用户可以进行所需的更改并单击“保存”按钮以保留更改 然而,一旦用户更改了其中一个输入控件,在单击“保存”按钮之前,DataGrid中的相应数据也会更新。我希望DataGrid仅在用户单击“保存”后更新 以下是视图的XAML: <Window x:Class="LearnWPF.View

我对WPF非常陌生,所以我想我应该从一些简单的东西开始:一个允许用户管理用户的窗口。该窗口包含一个
DataGrid
,以及几个用于添加或编辑用户的输入控件。当用户在网格中选择记录时,数据将绑定到输入控件。然后,用户可以进行所需的更改并单击“保存”按钮以保留更改

然而,一旦用户更改了其中一个输入控件,在单击“保存”按钮之前,
DataGrid
中的相应数据也会更新。我希望
DataGrid
仅在用户单击“保存”后更新

以下是视图的XAML:

<Window x:Class="LearnWPF.Views.AdminUser"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vms="clr-namespace:LearnWPF.ViewModels"
        Title="User Administration" Height="400" Width="450" 
        ResizeMode="NoResize">
    <Window.DataContext>
        <vms:UserViewModel />
    </Window.DataContext>

    <StackPanel>
        <GroupBox x:Name="grpDetails" Header="User Details" DataContext="{Binding CurrentUser, Mode=OneWay}">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>

                <Label Grid.Column="0" Grid.Row="0">First Name:</Label>
                <TextBox Grid.Column="1" Grid.Row="0" Style="{StaticResource TextBox}" Text="{Binding FirstName}"></TextBox>

                <Label Grid.Column="0" Grid.Row="1">Surname:</Label>
                <TextBox Grid.Column="1" Grid.Row="1" Style="{StaticResource TextBox}" Text="{Binding LastName}"></TextBox>

                <Label Grid.Column="0" Grid.Row="2">Username:</Label>
                <TextBox Grid.Column="1" Grid.Row="2" Style="{StaticResource TextBox}" Text="{Binding Username}"></TextBox>

                <Label Grid.Column="0" Grid.Row="3">Password:</Label>
                <PasswordBox Grid.Column="1" Grid.Row="3" Style="{StaticResource PasswordBox}"></PasswordBox>

                <Label Grid.Column="0" Grid.Row="4">Confirm Password:</Label>
                <PasswordBox Grid.Column="1" Grid.Row="4" Style="{StaticResource PasswordBox}"></PasswordBox>
            </Grid>
        </GroupBox>

        <StackPanel Orientation="Horizontal">
            <Button Style="{StaticResource Button}" Command="{Binding SaveCommand}" CommandParameter="{Binding CurrentUser}">Save</Button>
            <Button Style="{StaticResource Button}">Cancel</Button>
        </StackPanel>

        <DataGrid x:Name="grdUsers" AutoGenerateColumns="False" CanUserAddRows="False" CanUserResizeRows="False"
                  Style="{StaticResource DataGrid}" ItemsSource="{Binding Users}" SelectedItem="{Binding CurrentUser, Mode=OneWayToSource}">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Full Name" IsReadOnly="True" Binding="{Binding FullName}" Width="2*"></DataGridTextColumn>
                <DataGridTextColumn Header="Username" IsReadOnly="True" Binding="{Binding Username}" Width="*"></DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>
    </StackPanel>
</Window>
ViewModel(
IRemoteStore
提供对底层记录存储的访问):

很明显,我希望使用纯MVVM模式实现这一点。我已尝试将
数据网格中的绑定设置为
单向
,但这会导致更改不会反映在网格中(尽管会添加新条目)

我还研究了SO问题,它建议在ViewModel上使用“selected”属性。如上所述,我的原始实现已经有了这样一个属性(称为“CurrentUser”),但是在当前的绑定配置中,网格仍然会随着用户的更改而更新


任何帮助都将不胜感激,因为我已经在这个问题上碰头好几个小时了。如果我遗漏了什么,请评论&我将更新帖子。谢谢。

谢谢你提供了这么多的代码,我更容易理解你的问题

首先,我将在下面解释您当前的“用户输入->数据网格”流程:

例如,当您键入
Username:
TextBox
中的文本时,您键入的文本最终会在某个时候返回到
文本框中。在我们的例子中,text
属性值是当前的
UserModel.Username
属性,因为它们是绑定的,而他是属性值:

Text="{Binding UserName}"></TextBox>
当引发
PropertyChanged
时,它会通知所有
UserModel.Username
订阅者的更改,在我们的例子中,
DataGrid.Columns
中的一个是订阅者

<DataGridTextColumn Header="Username" IsReadOnly="True" Binding="{Binding Username}" Width="*"></DataGridTextColumn>
视图模型

...
public UserModel TemporarySelectedUser { get; private set; }
...
TemporarySelectedUser = new UserModel(); // once in the constructor.
...
private UserModel _currentUser;
public UserModel CurrentUser
{
    get { return _currentUser; }
    set
    {
        _currentUser = value;

        if (value != null)
            value.CopyTo(TemporarySelectedUser);

        RaisePropertyChanged("CurrentUser");
    }
}
...
private ICommand _saveCommand;
public ICommand SaveCommand
{
    get
    {
        return _saveCommand ??
                (_saveCommand = new Command<UserModel>(SaveExecute));
    }
}

public void SaveExecute(UserModel updatedUser)
{
    UserModel oldUser = Users[
        Users.IndexOf(
            Users.First(value => value.Id == updatedUser.Id))
        ];
    // updatedUser is always TemporarySelectedUser.
    updatedUser.CopyTo(oldUser);
}
...
public void CopyTo(UserModel target)
{
    if (target == null)
        throw new ArgumentNullException();

    target.FirstName = this.FirstName;
    target.LastName = this.LastName;
    target.Username = this.Username;
    target.Id = this.Id;
}
用户输入--文本输入-->临时用户--单击保存-->更新用户和UI

MVVM方法似乎是视图优先,许多“视图优先”方法的指导原则之一是,对于每个视图,您都应该创建相应的视图模型。因此,在视图抽象后重命名ViewModel更“准确”,例如将
UserViewModel
重命名为
AdminUserViewModel

此外,您还可以将
SaveCommand
重命名为
Command
,因为它回答的是整个命令模式解决方案,而不是特殊的“saving”情况

我建议您使用其中一个MVVM框架(我推荐使用MVVMLight)作为MVVM研究的最佳实践,有很多


希望能有所帮助。

感谢您提供的详细答案,很高兴知道为什么会发生某些事情&而不仅仅是如何解决它。。。尤其是当你还在学习的时候。我添加了“临时”属性&它现在正按照我想要的方式工作。我曾考虑过使用MVVM框架,但由于我仍在忙于学习,所以在了解WPF的基本工作原理之前,我不想抽象出任何东西。一旦我对自己的知识感到满意,我将继续使用这样一个框架。再次感谢!。
private string _username;
public string Username
{
    get { return _username; }
    set
    {
        _username = value;
        RaisePropertyChanged("Username"); // notification
    }
}
<DataGridTextColumn Header="Username" IsReadOnly="True" Binding="{Binding Username}" Width="*"></DataGridTextColumn>
<GroupBox x:Name="GroupBoxDetails" Header="User Details" DataContext="{Binding Path=TemporarySelectedUser, Mode=TwoWay, UpdateSourceTrigger=LostFocus}">
...
<Button Content="Save"
                    Command="{Binding Path=SaveCommand}"
                    CommandParameter="{Binding Path=TemporarySelectedUser}"/> // CommandParameter is optional if you'll use SaveCommand with no input members.
...
public UserModel TemporarySelectedUser { get; private set; }
...
TemporarySelectedUser = new UserModel(); // once in the constructor.
...
private UserModel _currentUser;
public UserModel CurrentUser
{
    get { return _currentUser; }
    set
    {
        _currentUser = value;

        if (value != null)
            value.CopyTo(TemporarySelectedUser);

        RaisePropertyChanged("CurrentUser");
    }
}
...
private ICommand _saveCommand;
public ICommand SaveCommand
{
    get
    {
        return _saveCommand ??
                (_saveCommand = new Command<UserModel>(SaveExecute));
    }
}

public void SaveExecute(UserModel updatedUser)
{
    UserModel oldUser = Users[
        Users.IndexOf(
            Users.First(value => value.Id == updatedUser.Id))
        ];
    // updatedUser is always TemporarySelectedUser.
    updatedUser.CopyTo(oldUser);
}
...
public void CopyTo(UserModel target)
{
    if (target == null)
        throw new ArgumentNullException();

    target.FirstName = this.FirstName;
    target.LastName = this.LastName;
    target.Username = this.Username;
    target.Id = this.Id;
}