【问题标题】:Edit Record. Pass values of bound TextBoxes to View Model编辑记录。将绑定文本框的值传递给视图模型
【发布时间】:2020-04-13 00:22:14
【问题描述】:

我有一个绑定到 ViewModel 的 DataGrid。当我从 DataGrid 中选择一条记录时,TextBoxes(用户名和角色)正在显示来自所选记录的数据。 我想编辑选定的记录,但我想在更新列表之前检查数据,因此是“OneWay”绑定模式。

我无法将文本框的值传递给视图模型。我可以通过按钮获取一个文本框的值并将该值传递给我的 ICommand

 <Button Grid.Row="5" Grid.Column="1"  Content="Edit" Margin="5 5" 
                    Command="{Binding EditUserCmd, Source={StaticResource viewModelUsers}}"  CommandParameter="{Binding Text, ElementName=txtUsername}

有没有办法通过在视图模型中创建一个包含选定用户的属性来将所有文本框传递给视图模型?或者以某种方式将 texboxes 的值传递给视图模型??

谢谢。

我的视图模型

 public class UsersViewModel
    {
        public ObservableCollection<UsersModel> Users { get; set; }

        private ICommand addUserCommand;
        private ICommand removeUserCommand;
        private ICommand editUserCommand;
        public ICommand AddUserCmd => addUserCommand ?? (addUserCommand = new AddUserCommand(this));
        public ICommand RemoveUserCmd => removeUserCommand ?? (removeUserCommand = new DeleteUserCommand(this));
        public ICommand EditUserCmd => editUserCommand ?? (editUserCommand = new EditUserCommand(this));

        private UsersModel selectedUser = new UsersModel();
        public UsersModel SelectedUser
        {
            get { return this.selectedUser; }
            set
            {
                this.selectedUser = value;
            }
        }

        public UsersViewModel()
        {
            // fetch data from db.            
            DataAccess da = new DataAccess();
            Users = new ObservableCollection<UsersModel>(da.GetRegisteredUsers());
        }
    }

型号


    public class UsersModel
    {

        public int Id { get; set; }

        public string Username { get; set; }

        public string Surname {get; set;}

    }

编辑命令

  internal class EditUserCommand : ICommand
    {
        public event EventHandler CanExecuteChanged;
        public UsersViewModel UsersViewModel { get; set; }
        public EditUserCommand(UsersViewModel usersViewModel)
        {
            this.UsersViewModel = usersViewModel;
        }

        public bool CanExecute(object parameter)
        {
           // UsersModel user = (UsersModel)parameter;
           // if (user != null)
                //return !string.IsNullOrEmpty(user.Id.ToString());

            return true;
        }

        public void Execute(object parameter)
        {
           // UsersModel user = (UsersModel)parameter;
           // if (user != null)
               // this.UsersViewModel.Users

        }
    }

xaml

...
 <Window.Resources>[enter image description here][1]
        <m:UsersModel x:Key="users"></m:UsersModel>
        <vm:UsersViewModel x:Key="viewModelUsers"/>
 </Windows.Resources>
...

       <DataGrid  x:Name="gridUsers"
            Grid.Row="0" 
            DataContext="{Binding Source={StaticResource viewModelUsers}}" CanUserAddRows="False"
            ItemsSource="{Binding Users}">
        </DataGrid>

        <Grid Margin="10" Grid.Row="1"   DataContext="{Binding ElementName=gridUsers, Path=SelectedItem}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="100" />
                <ColumnDefinition Width="200" />

            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>


            <Label Grid.Row="0">UserName:</Label>
            <TextBox x:Name="txtUsername" Grid.Row="0" Grid.Column="1" Margin="0,0,0,10" Text="{Binding Path=Username, Mode=OneWay}"/>

            <Label Grid.Row="1">Role:</Label>
            <TextBox x:Name="txtRole" Grid.Row="1" Grid.Column="1" Margin="0,0,0,10" Text="{Binding Path=Role, Mode=OneWay}"/>



            <StackPanel Grid.Row="5" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Center">

                <Button Grid.Row="5" Grid.Column="1"  Content="Edit" Margin="5 5" 
                    Command="{Binding EditUserCmd, Source={StaticResource viewModelUsers}}"  CommandParameter="{Binding Text, ElementName=txtUsername}">

            </StackPanel>

        </Grid>


【问题讨论】:

    标签: c# wpf mvvm


    【解决方案1】:

    您的 ViewModel 不应该知道文本框 - 只需添加两个新属性 ([PropertyName]EditValue) 并绑定到它们,然后在您的命令中检查它们,如果正确则将它们复制到模型中,如果不正确则恢复它们 - 这是使用视图模型而不是直接绑定到模型的重点

    【讨论】:

    • 这是有道理的。您是说将属性绑定到文本框吗?如果是这种情况,我怎样才能让数据网格的选定值显示在文本框中? ' '
    【解决方案2】:

    您知道您可以直接编辑DataGrid 单元格吗?你甚至可以使用data validation。这样无效的数据单元格会得到一个红色边框,除非验证通过,否则数据不会被提交。

    另一种选择是让UsersModel 实现INotifyDataErrorInfo 并直接验证属性。然后将DataGrid.SelectedItem绑定到视图模型,并将编辑TextBox元素绑定到这个属性。这样您就实现了实时更新并摆脱了编辑命令:

    UsersViewModel.cs

    public class UsersViewModel
    {
      public ObservableCollection<UsersModel> Users { get; set; }
    
      private UsersModel selectedUser;
      public UsersModel SelectedUser
      {
        get => this.selectedUser; 
        set => this.selectedUser = value;
      }
    
      public UsersViewModel()
      {
        // fetch data from db.            
        DataAccess da = new DataAccess();
        Users = new ObservableCollection<UsersModel>(da.GetRegisteredUsers());
      }
    }
    

    UsersModel.cs

    public class UsersModel : INotifyDataErrorInfo
    {
      private int id;
      public int Id
      {
        get => this.id; 
        set { if (this.id != value && IsIdValid(value)) this.id = value; }
      }
    
      private string userName;
      public string UserName
      {
        get => this.userName; 
        set { if (this.userName != value && IsUserNameValid(value) && ) this.userName = value; }
      }
    
      private string surname;
      public string Surname
      {
        get => this.surname; 
        set { if (this.surname != value && IsSurnameValid(value) && ) this.surname = value; }
      }
    
      // Validates the Id property, updating the errors collection as needed.
      public bool IsIdValid(int value)
      {
        RemoveError(nameof(this.Id), ID_ERROR);
        if (value < 0)
        {
          AddError(nameof(this.Id), ID_ERROR, false);                 
          return false;
        }
        return true;
      }
    
      public bool IsUserNameValid(string value)
      {
        RemoveError(nameof(this.UserName), USER_NAME_ERROR);
        if (string.IsNullOrWhiteSpace(value))
        {
          AddError(nameof(this.UserName), USER_NAME_ERROR, false);
          return false;
        }            
        return true;
      }
    
      public bool IsSurnameValid(string value)
      {
        RemoveError(nameof(this.Surname), SURNAME_ERROR);
        if (string.IsNullOrWhiteSpace(value))
        {
          AddError(nameof(this.Surname), SURNAME_ERROR, false);
          return false;
        }            
        return true;
      }
    
      private Dictionary<String, List<String>> errors =
            new Dictionary<string, List<string>>();
      private const string ID_ERROR = "Value cannot be less than 0.";
      private const string USER_NAME_ERROR = "Value cannot be empty.";
      private const string SURNAME_ERROR = "Value cannot be empty.";
    
      // Adds the specified error to the errors collection if it is not 
      // already present, inserting it in the first position if isWarning is 
      // false. Raises the ErrorsChanged event if the collection changes. 
      public void AddError(string propertyName, string error, bool isWarning)
      {
        if (!errors.ContainsKey(propertyName))
          errors[propertyName] = new List<string>();
    
        if (!errors[propertyName].Contains(error))
        {
          if (isWarning) errors[propertyName].Add(error);
            else errors[propertyName].Insert(0, error);
    
          RaiseErrorsChanged(propertyName);
        }
      }
    
      // Removes the specified error from the errors collection if it is
      // present. Raises the ErrorsChanged event if the collection changes.
      public void RemoveError(string propertyName, string error)
      {
        if (errors.ContainsKey(propertyName) &&
          errors[propertyName].Contains(error))
        {
          errors[propertyName].Remove(error);
          if (errors[propertyName].Count == 0) errors.Remove(propertyName);
            RaiseErrorsChanged(propertyName);
        }
      }
    
      public void RaiseErrorsChanged(string propertyName)
      {
        if (ErrorsChanged != null)
          ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
      }
    
      #region INotifyDataErrorInfo Members
    
      public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
    
      public System.Collections.IEnumerable GetErrors(string propertyName)
      {
        if (String.IsNullOrEmpty(propertyName) || 
          !errors.ContainsKey(propertyName)) return null;
        return errors[propertyName];
      }
    
      public bool HasErrors
      {
        get => errors.Count > 0; 
      }
    
      #endregion
    }
    

    查看
    TextBox.Text绑定必须设置为TwoWay(这是该属性的默认Binding.Mode值)

    <DataGrid x:Name="gridUsers"
              DataContext="{Binding Source={StaticResource viewModelUsers}}" 
              CanUserAddRows="False"
              ItemsSource="{Binding Users}"
              SelectedItem="{Binding SelectedUser"} />
    
    <Grid DataContext="{Binding Source={StaticResource viewModelUsers}, Path=SelectedUser}">
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="100" />
        <ColumnDefinition Width="200" />
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
      </Grid.RowDefinitions>
    
      <Label Grid.Row="0">UserName:</Label>
      <TextBox x:Name="txtUsername" Grid.Row="0" Grid.Column="1" 
               Text="{Binding Username, NotifyOnValidationError=True"/>
    
      <Label Grid.Row="1">Role:</Label>
      <TextBox x:Name="txtRole" Grid.Row="1" Grid.Column="1" 
               Text="{Binding Role, NotifyOnValidationError=True}"/>
    </Grid>
    

    【讨论】:

    • 感谢您的示例。我不知道您可以编辑单元格并以这种方式进行验证。您的示例工作正常,但是如果属性为空,它将恢复为以前的值。我被问到的是,能够编辑文本框而不反映数据网格上的变化,基本上使用“OneWay”,但我不知道如何获取文本框的值。想要拥有“selectedUser”的副本并检查那里的数据。一次,它被检查,更新原始的“selectedUser”以查看网格中的更改。也许在“CanExecute”方法中检查它?
    • 什么意思"如果属性为空..."?新值还是旧(当前)值? UserNameSurname 的验证方法目前只是使用 string.IsNullOrWhiteSpace() 检查空字符串。如果您希望允许空字符串,则必须调整验证约束,例如通过替换 string.IsNullOrWhiteSpace().
    • 整体行为正是你所期望的(如果我没弄错的话): 1. 编辑DataGridSelectedItem 2. 不要立即提交编辑后的值 3.验证编辑/新值 4. 如果无效则拒绝,如果有效则提交/显示。这就是数据验证的目的。您编辑副本的方式也可以,但不是那么干净。您还可以使用值转换器来接受或拒绝更改。但是验证属性本身是最干净的。它为您提供了显示错误的内置机制,例如通过在输入控件周围绘制红色边框。
    • 如果您仍然想坚持最初的方法(为什么要这样做?),然后将第二个 SelectedUserEditCopy 属性添加到您的视图模型并将 TextBox 绑定到它。当SelectedUser 属性更改时,创建它的副本并将其分配给SelectedUserEditCopy 以编辑和验证此属性。但是验证副本而不是原件没有任何意义。
    • 获取用户输入(例如TextBox)到视图模型的最简单的 MVVM 方法是使用双向绑定。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多