【问题标题】:WPF Dependency Property Two way binding not working after DataContext is set [duplicate]设置DataContext后WPF依赖属性两种方式绑定不起作用[重复]
【发布时间】: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 绑定。

标签: c# wpf data-binding wpf-controls


【解决方案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的博客

【讨论】:

    猜你喜欢
    • 2017-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-28
    • 1970-01-01
    • 1970-01-01
    • 2017-03-13
    相关资源
    最近更新 更多