My Master's Thesis Problems and solutions encountered…

31Oct/100

Cascading Comboboxes in MVVM

Cascading comboboxes refers to having a combobox being populated depending on the selected item of another combobox. In my example, I have what is called a Group Questionnaire, which can contain any number of Questionnaires.

The main purpose with this post is to show how to deal with the Silverlight bug that entails that the binding of the SelectedItem to the second combobox is broken the second time the first combobox is updated, if it is bound to an ObservableCollection.

The actual populating of the Cascading comboboxes is not at all different than when a combobox is populated normally, but I will show it very quickly anyway.

The first thing you need to do is create a new class that will inherit the normal ComboBox:

Step 1: Create the following class in your ViewModel, which inherits the ComboBox.


  public class XComboBox : ComboBox
    {
        private BindingExpression bE;
        public XComboBox()
        {
            this.SelectionChanged += new SelectionChangedEventHandler(XComboBox_SelectionChanged);
        }

        void XComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (bE==null)
            {
               bE = this.GetBindingExpression(ComboBox.SelectedValueProperty);
            }
            else
            {
                if (this.GetBindingExpression(ComboBox.SelectedValueProperty) == null)
                {
                   this.SetBinding(ComboBox.SelectedValueProperty, bE.ParentBinding);
                }
            }
        }

    }

Step 2: In your View, where you want to add the cascading comboboxes, add a reference to the ViewModel, so you can access the XCombobox. Notice that the reference is called NewCombo:


xmlns:NewCombo="clr-namespace:ViewModel;assembly=ViewModel"

Step 3: Add the combobox in the View. Notice the NewCombo:XComboBox:

<!-- FIRST COMBOBOX -->
<NewCombo:XComboBox  x:Name="AllParentQstionnaires"
        ItemsSource="{Binding GroupQuestionnaireCB, Mode=OneWay}"
       SelectedValue="{Binding SelectedGroupQuestionnaireCB, Mode=TwoWay}"
       DisplayMemberPath="ParentQuestionnaireName"
       SelectedValuePath="ParentQuestionnaireID"
                                  />

<!-- SECOND COMBOBOX -->
   <NewCombo:XComboBox  x:Name="AllQuestionnaires"
       ItemsSource="{Binding QuestionnaireCB, Mode=OneWay}"
       SelectedValue="{Binding SelectedQuestionnaireCB, Mode=TwoWay}"
       DisplayMemberPath="QuestionnaireName"
       SelectedValuePath="QuestionnaireID"
                            />

And that's basically it! The rest works as usual, and you will want to create the properties of the ComboBoxes in the ViewModel.

Notice that the two comboboxes are bound to the GroupQuestionnaireCB and the QuestionnaireCB.

Step 4: Create all the properties for the comboboxes, and populate the first in the constructor, and the second combobox in the setter of the first combobox.


using ViewModel;
using ViewModel.QMServiceReference; 

namespace ViewModel
{
    public class Client_Questionnaire3_ViewModel : INotifyPropertyChanged
    {

        QMServiceReference.Service1Client WebService; 

        #region combobox properties

        //First combobox
        private ObservableCollection<ParentQuestionnaire> groupQuestionnaireCB;
        public ObservableCollection<ParentQuestionnaire> GroupQuestionnaireCB
        {
            get
            {
                return groupQuestionnaireCB;
            }
            set
            {

                groupQuestionnaireCB = value;
                RaisePropertyChanged("GroupQuestionnaireCB");

            }

        }

        private int selectedGroupQuestionnaireCB;
        public int SelectedGroupQuestionnaireCB
        {
            get
            {

                return selectedGroupQuestionnaireCB;
            }
            set
            {
                selectedGroupQuestionnaireCB = value;
                //Vi populate combobox nr. 2 with the SelectedValue from combobox 1. 

                RaisePropertyChanged("SelectedGroupQuestionnaireCB");
                //Populate next combobox
                WebService.GetAllQuestionnairesCompleted += new EventHandler<GetAllQuestionnairesCompletedEventArgs>(WebService_GetAllQuestionnairesCompleted);
                WebService.GetAllQuestionnairesAsync(2, SelectedGroupQuestionnaireCB);
            }
        }

        //Second combobox

        private ObservableCollection<AllQuestionnaires> questionnaireCB;
        public ObservableCollection<AllQuestionnaires> QuestionnaireCB
        {
            get
            {
                return questionnaireCB;
            }
            set
            {
                questionnaireCB = value;
                RaisePropertyChanged("QuestionnaireCB");
            }
        }

        private int selectedQuestionnaireCB;
        public int SelectedQuestionnaireCB
        {
            get
            {
                return selectedQuestionnaireCB;
            }
            set
            {
                selectedQuestionnaireCB = value;
                RaisePropertyChanged("SelectedQuestionnaireCB");

            }

        }

        #endregion

        //Contructor

        public Client_Questionnaire3_ViewModel()
        {

                WebService = new Service1Client();
                 //calls webservice to populate the first combobox
                WebService.GetParentQuestionnaireCompleted += new EventHandler<GetParentQuestionnaireCompletedEventArgs>(WebService_GetParentQuestionnaireCompleted);
                WebService.GetParentQuestionnaireAsync(2); 

        }

        void WebService_GetParentQuestionnaireCompleted(object sender, GetParentQuestionnaireCompletedEventArgs e)

     //populates the first combobox
       {
            GroupQuestionnaireCB = e.Result;
            //selects the first value as deafult
            if (GroupQuestionnaireCB.Count > 0)
            {
             SelectedGroupQuestionnaireCB = GroupQuestionnaireCB[0].ParentQuestionnaireID;
            }
        }

        void WebService_GetAllQuestionnairesCompleted(object sender, GetAllQuestionnairesCompletedEventArgs e){
        //populates the second combobox
            QuestionnaireCB = e.Result;
        //selects the first value as default
            if (QuestionnaireCB.Count > 0)
            {
                SelectedQuestionnaireCB = QuestionnaireCB[0].QuestionnaireID;
            }

        }

//Eventhandler
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string propertyname)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyname));
            }
        }

  }

}

And that's it!

Well, I have not shown the whole Model aspect, but it it's completely like described in my previous post.

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment



1 + = nine

No trackbacks yet.