locked
Usercontrol dependency property to viewmodel property RRS feed

  • Question

  • Hi

    I've seen various posts a little similar to this, but I still haven't been able to solve my problem:

    I have an application that utilizes MVVM and MEF.

     I have a usercontrol, "AccountsControl", with two dependency properties:

    public static readonly DependencyProperty MaxAccountsProperty = 
                DependencyProperty.Register("MaxAccounts", typeof(int?), typeof(AccountControl), new PropertyMetadata(null));
    
            public int? MaxAccounts
            {
                get { return (int?)GetValue(MaxAccountsProperty); }
                set { SetValue(MaxAccountsProperty, value); }
            }
    
    public static readonly DependencyProperty PersonIDProperty = 
                DependencyProperty.Register("PersonID", typeof(int?), typeof(AccountControl), new PropertyMetadata(null));
    
            public int? PersonID
            {
                get { return (int?)GetValue(PersonIDProperty); }
                set { SetValue(PersonIDProperty, value); }
            }

    This control has a viewmodel which is set in the constructor:

    #region Private Properties
    private PluginCatalogService _catalogService = PluginCatalogService.Instance;
    #endregion
    
    public AccountControl()
    {
         InitializeComponent();
    
         if (!ViewModelBase.IsInDesignModeStatic)
              this.DataContext = _catalogService.FindPlugin(ViewModelTypes.AccountControlViewModel, PluginType.ViewModel);
    }

    The AccountControlViewModel has the corresponding properties:

            private int? _personID;
            public int? PersonID
            {
                get { return _personID; }
                private set
                {
                    if (!ReferenceEquals(_personID, value))
                    {
                        _personID= value;
                        RaisePropertyChanged("PersonID");
                    }
                }
            }
    
            private int? _maxAccounts;
            public int? MaxAccounts
            {
                get { return _maxAccounts; }
                private set
                {
                    if (!ReferenceEquals(_maxAccounts, value))
                    {
                        _maxAccounts= value;
                        RaisePropertyChanged("MaxAccounts");
                    }
                }
            }


    The AccountsControl is used within another control, "PersonControl", which has it's own viewmodel etc, and I want to be able to set the values in the xaml like the below:

    <local:AccountControl MaxAccounts="2" PersonID="{Binding Path=Person.ID }"/>

    But obviously this will just set the dependency properties within the AccountControl.  I'm not sure what the xaml should be within the AccountControl to propagate these changes onto the AccountControl viewmodel.

    Any help is much appreciated.

    Thanks

    Friday, November 30, 2012 10:39 AM

All replies

  • I am not sure that I fully understand your question but to get the property in the view model set by the usercontrol you will need to use Mode=TwoWay in the binding

    PersonID="{Binding Path=Person.ID, Mode=TwoWay}"

    (I assume it is a typo but shouldn't you bind to Person.PersonID and not Person.ID, since the property in the view model is called PersonID?)


    Paul Linton

    Sunday, December 2, 2012 3:03 AM
  • Hi Paul

    Thanks for your reply.  Sorry for causing any confusion, it's mainly the AccountControl that my question relates to.  The AccountControl has the dependency property, PersonID, but the AccountControlViewModel also has the property, PersonID.  I'm not sure how to get the value from the dependency property to the viewmodel property, bearing in mind I'm using MVVM and MEF.  Does that make sense?

    Monday, December 3, 2012 9:02 AM
  • There is no problem. Since your UserControl is bound to its viewmodel (via its DataContext property) you can, in the Xaml of the object using your UserControl, write <mycode:MyUserControl PersonID={Binding PersonID}/>

    the first PersonID is the property of your UC, the second one will be searched  in current DataContext, so in the VM of the UC.


    Olivier Dahan Dot.Blog : www.e-naxos.com/blog

    Monday, December 3, 2012 1:01 PM
  • Thanks Olivier.  I think there still might be some confusion, or I haven't understood your reply, but how does the value from the UC property get to the UC viewmodel?  I'm basically trying to propogate the value from the UC property to the UC viewmodel property.

    For example, with regards to another property I have, when I use the AccountControl in another object I want to do <mine:AccountControl MaxAccounts="2"/>.  I need this value in the AccountControl viewmodel.

    Monday, December 3, 2012 1:51 PM
  • Hi,

    The best way to achieve this is by passing the Parent Context( say VMA) to the Child UserControl (in your case AccountControl) and from there to its ViewModel (say VMB). Now you can play with the Parent DataContext inside the Child UserControl or View Model.

    To achieve this you need a create a dependency property UCB. Like below  -

    public object ParentDataContext
            {
                get { return (object)GetValue(ParentDataContextProperty); }
                set { SetValue(ParentDataContextProperty, value); }
            }
         
            public static readonly DependencyProperty ParentDataContextProperty =
                DependencyProperty.Register("ParentDataContext", typeof(object), typeof(UCB), new PropertyMetadata(null, ParentDataContextChangeCallback));
    
            static void ParentDataContextChangeCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                VMB vmb = ((UCB)d).DataContext as VMB;
                vmb.setParentContext(e.NewValue);
            }

    And then pass the parent context as below -

     <Grid x:Name="LayoutRoot" Background="Blue">
        <StackPanel x:Name="sp_parent" Orientation="Vertical">
          <StackPanel x:Name="sp_child1" Orientation="Horizontal">
            <TextBlock Text="Parent Context(Salary) :" Height="50"/>
            <TextBlock Text="{Binding SalaryOfChild}"/>
          </StackPanel>
          <StackPanel x:Name="sp_child3" Orientation="Horizontal">
            <my:UCB ParentDataContext="{Binding ElementName=LayoutRoot, Path=DataContext}" />
          </StackPanel>
        </StackPanel>
      </Grid>

    So your UCB Control will be as below -

    public partial class UCB : UserControl
        {
            public object ParentDataContext
            {
                get { return (object)GetValue(ParentDataContextProperty); }
                set { SetValue(ParentDataContextProperty, value); }
            }
         
            public static readonly DependencyProperty ParentDataContextProperty =
                DependencyProperty.Register("ParentDataContext", typeof(object), typeof(UCB), new PropertyMetadata(null, ParentDataContextChangeCallback));
    
            static void ParentDataContextChangeCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                VMB vmb = ((UCB)d).DataContext as VMB;
                vmb.setParentContext(e.NewValue);
            }
            public UCB()
            {
                InitializeComponent();
                this.Loaded += new RoutedEventHandler(UCB_Loaded);
            }
    
            void UCB_Loaded(object sender, RoutedEventArgs e)
            {
                this.DataContext = new VMB();
            }
        }


    Dhananjay(Tech Lead). Please mark the reply as answers if it helps.

    Monday, December 3, 2012 3:35 PM
  • Thanks for your detailed reply, but I'm not sure where the parent datacontext comes in.  My issue is basically with one control, AccountControl.  The issue is basically, how do I get the value from the AccountControl dependency property, MaxAccounts, to the AccountControlViewModel property, MaxAccounts.

    I can see how I could use a callback like in your reply to reference the viewmodel and set the value manually, but I'm using MVVM and MEF.  Wouldn't referencing the ViewModel like that 'break' the rule of MVVM?

    Thanks.

    Tuesday, December 4, 2012 8:36 AM
  • Ok I think I almost have a solution.  The MaxAccounts value is being received in the ViewModel, but the ParentID isn't and I'm not sure why.

    So my ViewModel is like this:

    public class AccountControlViewModel : ViewModelBase { ... #region Public Properties private int? _maxAccounts; public int? MaxAccounts { get { return _maxAccounts; } set { if (!ReferenceEquals(_maxAccounts, value)) { _maxAccounts = value; RaisePropertyChanged("MaxAccounts"); } } }

    private int _parentID; public int ParentID { get { return _parentID; } set { if (!ReferenceEquals(_parentID, value)) { _parentID= value; RaisePropertyChanged("ParentID"); } } }

    #endregion

    ... }

    In the codebehind of my AccountControl, I have set the binding within the constructor which appears to propagate the value from MaxAccounts DP to the ViewModel property, MaxAccounts.  But the ParentID isn't working

    public partial class AccountControl : UserControl { #region Private Properties private PluginCatalogService _catalogService = PluginCatalogService.Instance; #endregion public static readonly DependencyProperty MaxAccountsProperty = DependencyProperty.Register("MaxAccounts", typeof(int?), typeof(AccountControl), new PropertyMetadata(null)); public int? MaxAccounts { get { return (int?)GetValue(MaxAccountsProperty); } set { SetValue(MaxAccountsProperty, value); } }

            public static readonly DependencyProperty ParentIDProperty = 
                DependencyProperty.Register("ParentID", typeof(int), typeof(AccountControl), new PropertyMetadata(-1));
    
            public int ParentID
            {
                get { return (int)GetValue(ParentIDProperty); }
                set { SetValue(ParentIDProperty, value); }
            }

    public AccountControl() { InitializeComponent(); if (!ViewModelBase.IsInDesignModeStatic) this.DataContext = _catalogService.FindPlugin(ViewModelTypes.AccountControlViewModel, PluginType.ViewModel); this.SetBinding(MaxAccountsProperty, new Binding("MaxAccounts") { Mode = BindingMode.TwoWay, Source = DataContext });

    this.SetBinding(ParentIDProperty, new Binding("ParentID") { Mode = BindingMode.TwoWay, Source = DataContext }); } }

    And I am setting it in the ParentControl like this:

    <local:AccountControl MaxAccounts="2" ParentID="{Binding ElementName=ParentControl, Path=DataContext.CurrentPerson.ID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

    I created a callback on the ParentIDProperty to see if when the CurrentPerson changes, the property receives the new value and it does.  It just isn't being received in the AccountControl ViewModel.

    Any thoughts?


    • Edited by dpadam Tuesday, December 4, 2012 3:29 PM
    Tuesday, December 4, 2012 12:50 PM

  • <local:AccountControl MaxAccounts="2" ParentID="{Binding ElementName=ParentControl, Path=DataContext.CurrentPerson.ID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

    Path=DataContext.CurrentPerson.ID

    Where is CurrentPerson?

    Shouldn't it be Path=DataContext.PersonID

    when you are debugging inside the output window you don't get a Warning saying CurrentPerson.ID property not found on AccountControlViewModel?

    Tuesday, December 4, 2012 4:56 PM
  • <local:AccountControl MaxAccounts="2" ParentID="{Binding ElementName=ParentControl, Path=DataContext.CurrentPerson.ID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

    The AccountControl is being used like the above in a parent control.  CurrentPerson is an entity property inside the ParentControl ViewModel.  My aim is that when the CurrentPerson changes (and thus the CurrentPerson.ID) the ParentID property in the AccountControl will change.  I then need this new ParentID value to propagate to the AccountControl ViewModel.

    I checked that the AccountControl property, ParentID, is being changed when CurrentPerson changes by creating a OnChangedCallBack event inside the AccountControl.  The value does get changed, but the value isn't being received in the AccountControl ViewModel.

    Strangely, if I hardcode the ParentID in the xaml (ParentID="1"), the value is picked up in the AccountControl VM.  But obviously I need it to be data bound.

    Any help is much appreciated.

    Tuesday, December 4, 2012 10:58 PM
  • Try removing the UpdateSourceTrigger so just:

    <local:AccountControl MaxAccounts="2" ParentID="{Binding ElementName=ParentControl, Path=DataContext.CurrentPerson.ID, Mode=TwoWay}" />

    after that I'm stomped :-\

    Wednesday, December 5, 2012 4:19 PM
  • Unfortunately that didn't have any affect, but I appreciate your input.
    Wednesday, December 5, 2012 4:31 PM
  • Unfortunately that didn't have any affect, but I appreciate your input. Thanks.
    Wednesday, December 5, 2012 4:32 PM