只写模式下的 WPF 数据绑定控件

Posted

技术标签:

【中文标题】只写模式下的 WPF 数据绑定控件【英文标题】:WPF Databind control in write-only mode 【发布时间】:2016-02-27 03:43:41 【问题描述】:

我正在尝试使用 WPF 绑定,所以我创建了一个简单的项目。 代码如下:

public class Person : INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;
    public int Age 
        get  return age; 
        set 
            age = value;
            FirePropertyChanged("Age");
        
    

    public string Name
    
        get  return name; 
        set
        
            name = value;
            FirePropertyChanged("Name");
        
    

    private void FirePropertyChanged(string v)
    
        if(PropertyChanged !=null)
            PropertyChanged(this, new PropertyChangedEventArgs(v));
    
    private int age;
    private string name;

我的视图模型包含 Person 的 ObservableCollection,以及用于跟踪所选 Person 的单个 Person。

我已将列表框的 ItemsSource 绑定到 ObservableCollection,并将 SelectedItem 绑定到单个 Person,称为 CurrentPerson。此外,我已将 TextBox 绑定到 CurrentPerson.Name。

代码工作正常,但每当我更改 TextBox 的内容时 - 我的列表框也会更改。而且无论 listbox\selecteditem 上的“OneWay、TwoWay、OneWayToSource”绑定模式是什么组合,我都无法阻止列表框从 CurrentPerson 更新。

如何防止这种行为?我想仅使用 VM 中的 ICommand 接口从 CurrentPerson 更新列表框。

【问题讨论】:

【参考方案1】:

Person 对象只有一个副本,它同时用于ListBox.ItemsSourceTextBox.Text,因此从一个位置更新该对象自然也会反映另一个位置的更改。

两个简单的解决方案是

TextBox.Text 上的BindingMode 更改为Explicit,因此它不会更新Person 对象,直到您告诉它

TextBox.Text 使用单独的字符串属性,并在命令执行时将其复制到您的SelectedPerson.Name

我个人更喜欢第二个选项,因为我不喜欢不能准确反映 UI 组件背后的数据对象的绑定,它允许用户更改 SelectedItem 而无需重置 TextBox 值.


对于第二个选项的示例,您的 ViewModel 可能如下所示:

public class MyViewModel()

    ObservableCollection<Person> People  get; set; 
    Person SelectedPerson  get; set; 
    string NewPersonName  get; set; 
    ICommand UpdatePersonName  get; 

UpdatePersonName 命令的执行位置

SelectedPerson.Name = NewPersonName;

并且CanExecute 只会在以下情况下返回 true

SelectedPerson != null 
&& !NewPersonName.IsNullOrWhiteSpace() 
&& NewPersonName != SelectedPerson.Name

【讨论】:

第二种解决方案对我来说听起来很糟糕。为什么要污染包含具有编辑相关属性的人员列表的 ViewModel?几乎违反了 ViewModel 的单一职责原则,并且是代码异味 @Tseng Hrm 也许我应该澄清一下。我更新了我的答案。对我来说,ViewModel 应该为 View 建模,所以如果 View 有一个数据列表框、一个用于选定项目新名称的文本框和一个按钮,那么 ViewModel 应该包含所有这些数据。 这仍然是一个混合问题。你最终会在同一个 ViewModel 中混合浏览、显示和编辑。上面的MyViewModel 应该更名为BrowserPeopleViewModel(因为它实际上是这样做的),并且取决于您是处于查看模式还是编辑模式,在列表框旁边显示PersonViewModel 或“EditPersonViewModel”。毕竟,模型可能是一个复杂的,由多个编辑视图分隔(更改送货地址、付款信息、婚姻状态)。将它们全部放入一个 ViewModel 将导致您复制数十个属性并创建难以维护的代码 构建 WPF 应用程序的方法不止一种。你所描述的是一种方式,而我所描述的是另一种方式。这完全取决于你想如何构建你的应用程序:)【参考方案2】:

我不确定我是否正确地回答了这个问题。 所以,我们有一个 Person 类

public class Person : INotifyPropertyChanged
    
        public event PropertyChangedEventHandler PropertyChanged;
        public int Age
        
            get  return age; 
            set
            
                age = value;
                FirePropertyChanged("Age");
            
        

        public string Name
        
            get  return name; 
            set
            
                name = value;
                FirePropertyChanged("Name");
            
        

        private void FirePropertyChanged(string v)
        
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(v));
        
        private int age;
        private string name;
    

我们有一个视图模型

public class ViewModel : INotifyPropertyChanged
    
        public ObservableCollection<Person> List  get; set; 
        Person currentPerson;
        public Person CurrentPerson 
            get  return currentPerson; 
            set  currentPerson = value;
            FirePropertyChanged("CurrentPerson");
            
        

        private void FirePropertyChanged(string v)
        
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(v));
        
        public event PropertyChangedEventHandler PropertyChanged;

    

xaml 是

<ListBox ItemsSource="Binding List" SelectedItem="Binding CurrentPerson">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBox Text="Binding Name" Width="100" />
        </DataTemplate>
    </ListBox.ItemTemplate>       
</ListBox>

我将视图模型绑定到视图通过

    ViewModel vm = new ViewModel();
    vm.List = new ObservableCollection<Person>();
    foreach (var i in Enumerable.Range(1,10))
    
        vm.List.Add(new Person()  Name = "Test" + i.ToString(), Age= i );
    
    vm.CurrentPerson = null;
    this.DataContext = vm;

每当我更改文本框中的值时,它都会正确更新名称。我试图为列表更改添加一个处理程序,但它并没有被触发。

vm.List.CollectionChanged += List_CollectionChanged;
void List_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        
            MessageBox.Show(e.Action.ToString());
        

如果它与您的问题陈述不同,您能否发表评论?

【讨论】:

我认为 OP 的问题是他们在 ListBox 旁边有一个单独的 &lt;TextBox Text="Binding CurrentPerson.Name" /&gt;,他们希望能够在不更新 ListBox 数据的情况下更新该 TextBox.Text。【参考方案3】:

如果您想控制保存/更新的时间和内容,您显然需要一个 ViewModel 来编辑您的 Person 模型。

在您的列表框中选择一个人时,您必须将人的 id(避免传递对象本身)传递给绑定到要编辑的属性的PersonEditViewModel,将人员数据加载到PersonEditViewModel然后编辑。一旦您点击“保存”按钮,它应该提交更改并更新数据库或您用于持久性的任何内容。

使用事件/消息来回传递值/事件,或使用导航方法(如 Prism 中的INavigationAware 界面)。

【讨论】:

以上是关于只写模式下的 WPF 数据绑定控件的主要内容,如果未能解决你的问题,请参考以下文章

WPF 绑定

WPF——ComboBox控件怎么绑定数据

wpf 控件属性通过数据绑定到某个集合的某一个数据上。

WPF 动态列(DataGridTemplateColumn) 绑定数据 (自定义控件)对象绑定

wpf 自定义控件的自定义属性的数据绑定问题

WPF数据绑定问题:子控件宽度是父容器宽度的1/3,如何做?