views:

162

answers:

3

Hi,

EDIT: The issue underneath is fixed, GO TO EDIT2 in this post.

I have an Organisation entity and a Region entity. An object of type Organisation can have one or more Region objects connected to it, thus I have a foreign key in my Region entity to the Organisation Entity. The Organisation and Region objects are pulled from my database using WCF RIA and entity framework. I want to put the Organisation objects in one ComboBox and the Region objects in another ComboBox, and when selecting an organsation having the ComboBox for Region objects automatically only showing regions that are connected to the selected organisation. Should be pretty basic, but the way I've designed it right now it doesnt work at all.

So, any hint to how I can achive this? A simple simple codeexample is much appreciated! (I'm using SL4,WCF RIA MVVM)

EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2:

Using Venomo's ElemntBinding answer this is working great for me now when I want to add a new object to my collection, and I'm just pulling the avaible countries and connected regions and then type a site in a textbox...So I get my combination of Organisation, region and site in my database :)

Now, I've got a new problem when I want to EDIT a site in my collection. In EDIT mode, I want the two dropdowns to be preselected and disabled (BusinessRule is that I can edit the sitename, not which organisation og region it's connected to). So by setting the SelectedIndex property on Organisation combobox I get my organisation selected, but when doing the same on the Regions combobox it fails with an Object Reference error.

+2  A: 

You can achieve this with some clever ElementBindings.

Basic example:

Let's say we have a simple class like this:

public class Country
{
    public string Name { get; set; }

    public IEnumerable<string> Regions { get; set; }
}

Then, we'll have two ComboBoxes: one for choosing a country and another for choosing a region in that country. The second one should update itself when the value of the first one changes.

Okay, first we have to tell Silverlight how to display a Country. For complex scenarios we could use a DataTemplate for that, but for now, we will only need the DisplayMemberPath property of the ComboBox class.

<ComboBox x:Name="cbCountries" DisplayMemberPath="Name"/>

So, we create a simple collection of these objects in the code behind:

var countries = new List<Country>()
{
    new Country
    {
        Name = "USA",
        Regions = new List<string>
        {
            "Texas", "New York", "Florida", ...
        },
    },
    new Country
    {
        Name = "UK",
        Regions = new List<string>
        {
            "Scotland", "Wales", "England", ...
        },
    },
    ...
};

I know that those are not all of the regions in the example countries, but this is a Silverlight example and not a geographical lesson.

Now, we have to set the ItemsSource of the ComboBox to this collection.

cbCountries.ItemsSource = countries;

Both of these can be in the constructor in the code-behind.
Okay, now back to XAML!

We'll need another ComboBox and a way for telling it to get its items from the other collection dynamically.
Binding its ItemsSource to the other ComboBox's selected item is just the most obvious way to achieve that.

<ComboBox x:Name="cbRegion" ItemsSource="{Binding ElementName=cbCountries, Path=SelectedItem.Regions}"/>

This should do the trick quite simply.

If you use MVVM:

You can bind to the ItemsSource of the first ComboBox from the ViewModel. The rest remains the same.
To tell what the selected values are to the ViewModel, use Two-way bindings on the SelectedItem property of both ComboBoxes and bind that to whatever property you have for it in the ViewModel.

If your collection can change dynamically:

If the list of the countries (or whatever it is that you want to use this for) can change during runtime, it is best if you implement INotifyPropertyChanged for the Country class and for the regions, use ObservableCollection<>.
If it doesn't need to change during runtime, no need to bother with these.

Venemo
Venemo, thanx a lot - I'm almost there and I will mark your post as answer. BUT, van you look at my latest post in this thread? I have one more little piece to solve.
Mcad001
@Mcad001 - You're welcome. It seems to me that you're missing the DisplayMemberPath attribute from your second ComboBox.
Venemo
@Mcad001 - by the way, StackOverflow is no regular forum. If you still have trouble, don't post an answer (as it is not a "real" answer) - instead, feel free to edit your question as needed.
Venemo
Point taken about the forum.You were correct regarding the DisplayMemberPath - Thanx!
Mcad001
@Mcad001 - I'm glad I was able to help.
Venemo
Venemo, I've edited my original question, please have a look and see if you're able to help me with my current problem:)
Mcad001
+2  A: 

View Model:

public ObservableCollection<Organisation> Organisations { get; set; }

private Organisation selectedOrganisation;
public Organisation SelectedOrganisation
{
  get { return this.selectedOrganisation; }
  set
  {
    if (this.selectedOrganisation != value)
    {
       this.selectedOrganisation = value;
       this.NotifyPropertyChanged("SelectedOrganisation");
       this.UpdateRegions();
    }
  }

  private IEnumerable<Region> regions;
  public IEnumerable<Region> Regions
  {
     get { return this.regions; }
     set
     {
        if (this.regions != value)
        {
          this.regions = value;
          this.NotifyPropertyChanged("Regions");
        }
     }
  }

  private void NotifyPropertyChanged(String info)
  {
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(info));
    }
  }

  private void UpdateRegions()
  {
    // If regions are not pre-populated you might need to perform a new call to 
    // the service that retrieves organisations in order to retrieve the associated Regions entities 
    // for the SelectedOrganisation organisation
    this.Regions = this.SelectedOrganisation.Regions;
  }

In your View:

<ComboBox x:Name="Organisations" ItemsSource="{Binding Organisations}" SelectedItem="{Binding SelectedOrganisation, Mode=TwoWay}" />
<ComboBox x:Name="Regions" ItemsSource="{Binding Regions}" />
Anero
Your example lacks a way of telling the selected region to the `ViewModel`, and also the `SelectedItem` for the first combo box should be a two-way binding in order for it to work. Also, I don't see an `Organisations` collection in your `ViewModel`.
Venemo
@Venemo: from the question I assumed tracking of the selected region wasn't necessary, if so it's just a matter of adding a SelectedRegion to the VM and a SelectedItem attribute for the Regions combo as well (this can be easily infered from the Organisations combo box). The same goes to the Organisations collection (the combo box is being already populated so I assume @Mcad001 is already doing this correctly, I added this piece to the VM anyway). You're right regarding the binding way, I edited the answer to include it.
Anero
@Anero - Okay. Your answer is correct now. :)
Venemo
Venemo, thanx a lot - I'm almost there and I will mark your post as answer.BUT, van you look at my latest post in this thread? I have one more little piece to solve.
Mcad001
Anero, I've edited my original question, please have a look and see if you're able to help me with my current problem:)
Mcad001
A: 

Venemo, can you help me out on this one? This is my code right now.. It kind of works, showing my Organisation Description OK, and the region combobox changes based on selection in Organisation combobox. BUT, the region combobox shows Region: 1, Region: 2 etc, not the actual name of the region even though thats what I'm asking for in this line:

ItemsSource="{Binding ElementName=cbOrganisation, Path=SelectedItem.Region}">

<ComboBox x:Name="cbOrganisation"
                  Grid.Row="0"
                  IsEnabled="{Binding IsEnabled}" 
                  Grid.Column="1" 
                  ItemsSource="{Binding OrganisationEntries}"
                  DisplayMemberPath="Description">
</ComboBox>

<ComboBox         Grid.Row="1"
                  IsEnabled="{Binding IsEnabled}" 
                  Grid.Column="1" 
                  ItemsSource="{Binding ElementName=cbOrganisation, Path=SelectedItem.Region}">
</ComboBox>

The OrganisationEntries is of type ServiceModel.DomainServices.Client.EntitySet(Of Organisation) and that set contains EntityCollection(Of Region) (By default from WCF RIA since I'm using foreign keys..)

I tried going directly to the field/property of my Region object. But then the combobox turns up emtpy (Path=SelectedItem.Region.Description)

Mcad001