设置DataContext后WPF依赖属性两种方式绑定不起作用[重复]

Posted

技术标签:

【中文标题】设置DataContext后WPF依赖属性两种方式绑定不起作用[重复]【英文标题】:WPF Dependency Property Two way binding not working after DataContext is set [duplicate] 【发布时间】:2021-09-14 02:10:34 【问题描述】:

我有一个 ComboBox,它的 ItemSource 集合可以从应用程序的其他部分更改,这个 ComboboBox 可以用于应用程序的多个位置。因此,为了集中这一点,我创建了一个仅包含组合框的用户控件,并将其数据源设置为 viewmodel,并公开了一个用于绑定的依赖属性。但是依赖属性不会更新组合框项目选择的绑定属性。

这是我提取的示例代码

//My base Model for Combobox data    
public class MyModel

   public int Id  get; set; 
   public string Text  get; set; 


//Base class for View Models to notify property change
public class BaseViewModel : INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged = (sender, e) =>;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    



//View model for user control that contains combobox
public class MyModelViewModel : BaseViewModel

    private ObservableCollection<MyModel> _mymodelCollection;
    public ObservableCollection<MyModel> MyModelCollection
    
        get  return _mymodelCollection; 
        set
        
            _mymodelCollection = value;OnPropertyChanged();
        
    

    public MyModelViewModel()
    
        MyModelCollection = new ObservableCollection<MyModel>
        
            new MyModel
            
                Id = 1, Text = "Project 1"
            ,
            new MyModel
            
                Id = 2, Text = "Project 2"
            ,
            new MyModel
            
                Id = 3, Text = "Project 3"
            
        ;
    


//View model for parent window that contains combobox user control
public class TestWindowViewModel : BaseViewModel


    private int _myModelId;

    public int MyModelIdInWindow
    
        get  return _myModelId; 
        set  
            _myModelId = value; OnPropertyChanged();
            //debug code
            Console.WriteLine(value);
        
    



//User Control for reusability
<UserControl x:Class="SimpleWPF.Control.CommonControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:SimpleWPF.Control"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <ComboBox
            x:Name="cmbList"
            Grid.Row="0"
            Grid.Column="0"
            ItemsSource="Binding MyModelCollection, Mode=OneWay"
            DisplayMemberPath="Text"
            SelectedValuePath="Id"
            
            SelectionChanged="cmbList_SelectionChanged"            
            />

    </Grid>
</UserControl>

//User Control Code Behind with exposed MyModelId DP
public partial class CommonControl : UserControl
    
        private bool _isInternalUpdate = false;
        public int MyModelId
        
            get  return (int)GetValue(MyModelIdProperty); 
            set  SetValue(MyModelIdProperty, value); 
        

// Using a DependencyProperty as the backing store for BranchId.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty MyModelIdProperty =
        DependencyProperty.Register("MyModelId", typeof(int), typeof(CommonControl),
            new FrameworkPropertyMetadata(int.MinValue, 
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, MyModelIdPropertyChanged, 
                null, false, UpdateSourceTrigger.PropertyChanged));


        public CommonControl()
        
            InitializeComponent();
            this.DataContext = new MyModelViewModel();
        

        private void cmbList_SelectionChanged(object sender, SelectionChangedEventArgs e)
        
            if(cmbList.SelectedValue != null)
            
                _isInternalUpdate = true;
                MyModelId = (int)cmbList.SelectedValue;
                _isInternalUpdate = false;
            
        

        private static void MyModelIdPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        
            if (d is CommonControl branchComboBox)
            
                branchComboBox.UpdateBranch();
            
        

        private void UpdateBranch()
        
            if (!_isInternalUpdate)
            
                cmbList.SelectedValue = MyModelId;
            
        
    

终于在窗口中

<ctrl:CommonControl MyModelId="Binding MyModelIdInWindow, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged"/>

即使在用户控件中 MyModelId DP 值发生更改,MyModelIdInWindow 属性也不会更新

【问题讨论】:

UserControl 和它的任何一个子元素都不应该设置它们的 DataContext。不应有应用程序的其余部分不知道的私有视图模型对象。而是在控件的 XAML 中使用 RelativeSource 绑定。 【参考方案1】:

您需要实现INotifyCollectionChanged 接口而不是INotifyPropertyChanged

如果您希望在单个对象属性更改时更新网格,则每个包含的对象都必须实现 INotifyPropertyChanged 接口。 INotifyCollectionChanged 是集合应实现的接口,用于通知添加和删除事件

【讨论】:

感谢您的建议,我也应该实施它,但我想这并没有导致实际问题。是吗? @BhubanShrestha 其实我觉得是……【参考方案2】:

解决方法是不要为 UserControl 设置 DataContext

public CommonControl()

    InitializeComponent();
    (this.Content as FrameworkElement).DataContext = new MyModelViewModel();
    //this.DataContext = new MyModelViewModel();

感谢@jerrynixon 关于Two-way binding inside a XAML User Control的博客

【讨论】:

以上是关于设置DataContext后WPF依赖属性两种方式绑定不起作用[重复]的主要内容,如果未能解决你的问题,请参考以下文章

[翻译] WPF 中用户控件 DataContext/Binding 和依赖属性的问题

WPF 依赖属性

WPF 依赖属性

WPF:将静态资源绑定到用户控件中的依赖项属性

WPF怎么把一个TextBlock的Text绑定到一个变量啊

当对象改变我的观点时不在 wpf