none
Need help with MVVM, Usercontrols and dependency properties RRS feed

  • Question

  • Hi!

    Please help me do things the correct way!!

    My scenario is as following.

    I have a window, that window has two usercontrol embedded. The first user control A contains a button, the second user control B contains a list. There is a bound property on B that keeps track of which item in the list is selected. The button on A opens a new window C which is a preview window for an image contained in the selected list item. I work with MVVM.

    Simple enough? Well I have some problems understanding the proper way to do the bindings here. The hierarchy is like this:

    UC:A -contains--> UC:B -opens-> Window:C

    I have solved this, but it feels very clumpsy. I solved it by:

    1) I created a dependency property in the code behind on B that contain SelectedItem from VM in A

     public CoreObjectViewModel  SelectedCoreObject {
                get { return (CoreObjectViewModel)GetValue(SelectedCoreObjectProperty); }
                set { SetValue(SelectedCoreObjectProperty, value); }
            }
            
            public static readonly DependencyProperty SelectedCoreObjectProperty =
                DependencyProperty.Register("SelectedCoreObject", typeof(CoreObjectViewModel), typeof(SearchBoxView), new PropertyMetadata(null, OnSelectedCoreObjectChanged));
            public static void OnSelectedCoreObjectChanged(DependencyObject Sender, DependencyPropertyChangedEventArgs e) {
                var sender = Sender as SearchBoxView;
                var dc = (SearchBoxViewModel)sender.DataContext;
                dc.SelectedCoreObject = e.NewValue as CoreObjectViewModel;
                sender.DataContext = dc;        
            }


    2) With this dependency property I can bind the selected item to my control by this code in A

     <m:SearchBoxView SelectedCoreObject="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}}, Path=DataContext.SelectedItem}"

    Now I have the selected item from the list in A into the user control B. BUT the ´B viewmodel still has no idea about this, so if you look at the first code block, in the method OnSelected...Changed() I pull the datacontext from B (meaning the viewmodel), and manually set the value, so that the viewmodel know about this,

    3) Finally I assign the B viewmodel as datacontext for the window C in the command that opens the window.

    private void TogglePreviewWindow() {
                if (previewWindow == null) {
                    previewWindow = new SearchPreviewWindow(this);
                    previewWindow.DataContext = this;
                    previewWindow.Show();
                } else {
                    previewWindow.Close();
                    previewWindow = null;
                }
                RaisePropertyChanged(() => IsPreviewShowing);
            }

    My question to you is, is all this code reqally needed to accomplis this task? I find it very difficult to pass data between usercontrols within usercontrols where they all have their own VM. If they all share the VM then it is easy, just bind the same datacontext so they share the same VM.

    Granted, the previewwindow has no need for it's own VM, but if that had been the case, how do you suggest that I would bound the selected item to the window image control? A usser control can't have two datacontexts, right?

    Also another question, when working with MVVM and dependency properties, what is the correct way to get a dependency property from an UC into the UC's VM? That is where it is useful, and not in the code behind. But I have no other choice but to define the DP in the code behind, if I want to use it in XAML, right? Is it correct to do as I do, to pull the VM from the UC, assign the DP manually, then put back the VM datacontext property?

    As you may see, I am a newbie at this...

    /S


    Henrik

    Wednesday, July 10, 2013 7:39 PM

Answers

  • I'd do something like this (it's a huge abbreviation of course, these are all properties with NotifyPropertyChanged call and a standard RelayCommand) :

    class ParentViewModel
    {
       public ObservableCollection<ItemType> MyItems ...
       public ItemType MySelectedItem ...
    
       RelayCommand PreviewCommand ...
    }

    I'd add an ItemSource DP to the B UC code behind:

    public static readonly DependencyProperty ItemsSourceProperty =
                DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(B), new PropertyMetadata(new PropertyChangedCallback(OnItemsSourcePropertyChanged)));

    and similarly the SelectedItem property. Inside the B UC, I'd TemplateBinding to bind ItemsSource and SelectedItem properties of the ListBox/ListView/DataGrid/whatever floats your boat to the respective properties of their parent B control. ( you could also do everything manually in the code-behind in property changed callbacks gives you more control but also adds some work ).

    In the View of the ParentViewModel you can bind ViewModel properties to the ItemSource and SelectedItem of the B control. Like this:

    <MainView>
    <StackPanel>
    
    <A/>
    <B ItemsSource={Binding MyItemsSource} SelectedItem={Binding MySelectedItem}
    
    </StackPanel>
    </MainView>

    This way you would always automatically get the currently selected item from the listbox in your ParentViewModel - it would be visible to all the control in its View.

    DPs are like any other bindable properties used in WPF. Hence they should be in the controls code-behind (or in helper/behaviour classes if they are attached dependency properties). For instance in the TextBox control, the Text DP is defined in the control's code behind - and you can normally bind to it in your ViewModels - your own dependency properties should work exactly the same way.

    Thursday, July 11, 2013 7:31 AM