views:

41

answers:

2

I am databinding a view to a viewmodel and am having trouble initializing a combobox to a default value. A simplification of the class I'm using in the binding is

public class LanguageDetails
{
  public string Code { get; set; }
  public string Name { get; set; }
  public string EnglishName { get; set; }

  public string DisplayName
  {
    get
    {
      if (this.Name == this.EnglishName)
      {
        return this.Name;
      }
      return String.Format("{0} ({1})", this.Name, this.EnglishName);
    }
  }
}

The combobox is declared in the view's XAML as

<ComboBox x:Name="LanguageSelector" Grid.Row="0" Grid.Column="1" 
          SelectedItem="{Binding SelectedLanguage,Mode=TwoWay}" 
          ItemsSource="{Binding AvailableLanguages}">
  <ComboBox.ItemTemplate>
    <DataTemplate>
      <TextBlock Text="{Binding DisplayName}"/>
    </DataTemplate>
  </ComboBox.ItemTemplate>
</ComboBox>

and the viewmodel contains this code

private List<LanguageDetails> _availableLanguages;
private LanguageDetails _selectedLanguage;

public LoginViewModel()
{
  _availableLanguages = LanguageManager.GetLanguageDetailsForSet(BaseApp.AppLanguageSetID);
  _selectedLanguage = _availableLanguages.SingleOrDefault(l => l.Code == "en");
}

public LanguageDetails SelectedLanguage
{
  get { return _selectedLanguage; }
  set
  {
    _selectedLanguage = value;
    OnPropertyChanged("SelectedLanguage");
  }
}

public List<LanguageDetails> AvailableLanguages
{
  get { return _availableLanguages; }
  set
  {
    _availableLanguages = value;
    OnPropertyChanged("AvailableLanguages");
  }
}

At the end of the constructor both _availableLanguages and _selectedLanguage variables are set as expected, the combobox's pulldown list contains all items in _availableLanguages but the selected value is not displayed in the combobox. Selecting an item from the pulldown correctly displays it and sets the SelectedLanguage property in the viewmodel. A breakpoint in the setter reveals that _selectedLanguage still contains what it was initialized to until it is overwritten with value.

I suspect that there is some little thing I'm missing, but after trying various things and much googling I'm still stumped. I could achieve the desired result in other ways but really want to get a handle on the proper use of databinding.

+1  A: 

You need to change the order of you bindings in XAML so that your ItemsSource binds before the SelectedItem.

<ComboBox x:Name="LanguageSelector" Width="100"  
        ItemsSource="{Binding AvailableLanguages}"
        SelectedItem="{Binding SelectedLanguage,Mode=TwoWay}">
        <ComboBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding DisplayName}"/>
            </DataTemplate>
        </ComboBox.ItemTemplate>
    </ComboBox>

If you set a breakpoint on the 'get' of both the SeletedLanguage and AvailibleLanguage, you will notice that the SelectedLanguage gets hit before your AvailibleLanguage. Since that's happening, it's unable to set the SelectedLanguage because the ItemsSource is not yet populated. Changing the order of the bindings in your XAML will make the AvailibleLanguages get hit first, then the SelectedLanguage. This should solve your problem.

JSprang
Thanks. I discovered this via breakpoints as you describe before I actually saw your answer. I have to say that it blows my mind that attribute order in XAML is significant; I would never have thought this would be the case.
Steve Crane
A: 

Hi,

1) When you assign the SelectedLanguage, use the public property SelectedLanguage instead of the private _selectedLanguage, so that the setter gets executed,

2) You need to move the assignment of the selectedlanguage to the moment that the view has been loaded. You can do it by implementing the Loaded event handler on the View. If you want to be "mvvm compliant" then you should use a Blend behavior that will map UI loaded event to a viewmodel command implementation in which you would set the selected language.

Przemek
I had tried the first option; it didn't work. I want to avoid extra code as in your second suggestion. I believe this should be possible with just simple bindings and the solution provided by my accepted answer bears this out.
Steve Crane
Just the first option is not enough. The SelectedLanguages property on the ViewModel must be set after the view has been fully constructed. Why "-1"? Just trying to help.
Przemek
I appreciate the attempt to help but the tooltips on the vote up/down buttons say "This answer is useful" for up and "This answer is not useful" for down. While your solution may achieve the result it adds complexity and doesn't get to the root cause, i.e ordering of XAML attributes. Therefore I didn't find it useful so I marked it down.
Steve Crane
What "ordering of XAML attributes" are you talking about? The order of attributes on the ComboBox control has nothing to do with the root of the problem at hand. You can switch the order without impact on the solution. However, when you implement both of my suggestions, the problem IS solved. Actually, even befor answering I had created a sample project using your snippets and my solution worked 100 % properly, ie. the combobox WAS populated by default after the view was created!
Przemek
And, what is more important the proper language is bound. The root of the problem here is that you were trying to bind the combobox before it was populated by the view. That's what I am addressing with point 2. Namely, it is necessary to postpone the binding to the moment that the combobox IS populated. There is no other way to accomplish the goal here.
Przemek
Yes the postponing must be achieved and the accepted answer does it in a simpler way just by reversing the order of the bindings. If the ItemsSource is bound before the SelectedItem then the binding of the SelectedItem only happens once the list has been populated and there is no problem.
Steve Crane