James
James

Reputation: 25

Conditional combobox binding with string storage

I'm trying to build a payment info form and ran into some troubles.

  1. I have this "Country" ComboBox that lets the user select country.
  2. I also have another "State" ComboBox that lets the user select the state in the country.
  3. Because every country has different states, the ItemSource depends on the selection of the "Country" ComboBox.
  4. Hierarchy of the XAML: TabControl -> Grid -> ComboBoxes

  5. I also need to store the data in an instance of a Profile class, call this profile 1 (the profiles are stored in a static Observable Collection, call this Profiles), the program will contain multiple profiles.

Question:

I'm having difficulty making this bind to the State ComboBox, as I am a newb to WPF, so any help would be greatly appreciated. Big thanks.

The problem is that I don't know how to make the State ComboBox ItemSource conditional bind to the ComboBoxSource class members while being in sync with the State property of the individual profiles. (It needs to be in sync from reading the values of the strings)

Solution:

<ComboBox Name="Country" ItemsSource="{Binding Source={x:Static loc:ComboBoxItemSource.Countries}}" SelectedItem="{Binding Path=Country, Mode=TwoWay}">
<ComboBox Name="State" ItemsSource="{Binding States, Mode=TwoWay}" SelectedItem="{Binding Path=State, Mode=TwoWay}">

Used this in conjunction with the solution from "stijn", big thanks! For some reason if I used the original attributes of the "State" ComboBox the selected option wouldn't show up right at runtime.

Main idea is to use SelectedItem instead of SelectedValue and SelectedValuePath

Original Code Below:

XAML:

<ComboBox Name="Country" Grid.Row="0" Grid.Column="3" SelectedValuePath="Content" SelectedValue="{Binding Country, Mode=TwoWay}" SelectionChanged="Country_SelectionChanged">
    <ComboBoxItem>USA</ComboBoxItem>
    <ComboBoxItem>Canada</ComboBoxItem>
    <ComboBoxItem>Japan</ComboBoxItem>
</ComboBox>
<ComboBox Name="State" Grid.Row="1" Grid.Column="3" SelectedValuePath="Content" SelectedValue="{Binding State, Mode=TwoWay}" ItemsSource="{Binding Source={x:Static loc:ComboBoxItemSource.USStates}}">

Code-behind:

class Profile
{
    //default values
    private string country = "Canada";

    public string Country
    {
        get { return country; }
        set 
        {
            country = value;
            this.OnPropertyChanged();
        }
    }
    //default values
    private string state = "CA2";

    public string State
    {
        get { return state; }
        set 
        { 
            state = value;
            this.OnPropertyChanged();
        }
    }
}

public class ComboBoxItemSource
{
    public static MTObservableCollection<string> USStates = new MTObservableCollection<string> { "VA", "DC" };
    public static MTObservableCollection<string> CanadaStates = new MTObservableCollection<string> { "CA1", "CA2" };
    public static MTObservableCollection<string> Countries = new MTObservableCollection<string> { "USA", "Canada", "Japan" };       
}

//Some method in the main class:
private void Country_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    Dispatcher.BeginInvoke(new Action(() =>
    {
        if (Profiles.Count > 0)
        {
            //the ComboBox is inside a grid, which is inside a TabControl that displays all the profiles
            Profile item = (Profile)((Grid)((ComboBox)sender).Parent).DataContext;
            ContentPresenter cp = Tabs.Template.FindName("PART_SelectedContentHost", Tabs) as ContentPresenter;
            ComboBox g = Tabs.ContentTemplate.FindName("State", cp) as ComboBox;
            if (g.ItemsSource == null) { return; }
            if (((ComboBox)sender).Text == "USA")
            {
                g.ItemsSource = ComboBoxItemSource.USStates;
            }
            else if (((ComboBox)sender).Text == "Canada")
            {
                g.ItemsSource = ComboBoxItemSource.CanadaStates;
            }
        }
    }));
}

Upvotes: 0

Views: 951

Answers (1)

stijn
stijn

Reputation: 35911

No need to 'conditionally bind' to static variabes and no need for code behind. I suggest you search the internet for 'MVVM' and read some basic tutorials on that.

Possible solution: provide a public IList<string> States property to bind your states combobox to (I don't think you have to use ObservableCollections here) and in the setter of Country set the list to the appropriate states list. Something like:

public string Country
{
    get { return country; }
    set 
    {
        country = value;
        SetStatesForCountry( value );
        this.OnPropertyChanged();
    }
}

private IList<string> states;

public IList<string> States
{
  get{ return states; }
  private set { states = value; this.OnPropertyChanged(); } 
}

private void SetStatesForCountry( string selectedCountry )
{
  IList<string> found;
  if( StatesPerCountry.Map.TryGetValue( selectedCountry, out found ) )
  {
    States = found;
  }
  else
  {
    States = null; //or you could set it to "not applicable" or so
  }
}

Note I used a dictionary for mapping states to countries, no need to have huge if/else clasues:

public class StatesPerCountry
{
   public static IDictionary<string,IList<string>> Map = new
     IDictionary<string,IList<string>>
   {
     { "USA", new List<string>{ "a", "b", "c" } },
     { "CAN", new List<string>{ "a", "b", "c" } },
     { "Japan", new List<string>{ "a", "b", "c" } }
   }
}

Upvotes: 1

Related Questions