-1

I have a ComboBox whose ItemSource collection can be changed from other part of application and this ComboboBox can be used to multiple places of app. Thus, to centralize this I have created a user control that contains only combobox and set it datasource to viewmodel and has exposed a dependency property for binding. But the dependency property doesn't updated bound property on combobox item selection.

Here are my extracted sample codes

//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;
            }
        }
    }

Finally in the window

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

MyModelIdInWindow property doesn't get updated even though in user control MyModelId DP value gets changed

Bhuban Shrestha
  • 1,304
  • 11
  • 27
  • Neither the UserControl nor one of its child elements should have their DataContext set. There should not be a private view model object that is unknown to the rest of your application. Instead, use RelativeSource Bindings in the control's XAML. – Clemens Jul 02 '21 at 05:53

2 Answers2

0

You need to implement the INotifyCollectionChanged interface isntead the INotifyPropertyChanged. <br

If you want the grid to update when individual object properties change, than each contained object must implement the INotifyPropertyChanged interface. The INotifyCollectionChanged is an interface that the collection should implement, and are for notifications of addition and removal events

Adrian Efford
  • 473
  • 3
  • 8
0

Solution is not to set DataContext for UserControl instead

public CommonControl()
{
    InitializeComponent();
    (this.Content as FrameworkElement).DataContext = new MyModelViewModel();
    //this.DataContext = new MyModelViewModel();
}

Thanks to @jerrynixon blog about Two-way binding inside a XAML User Control

Bhuban Shrestha
  • 1,304
  • 11
  • 27