CBrown77
CBrown77

Reputation: 557

WPF ListBox binding works at first, but not afterwards

Base Problem => my ListBox won't update when a different ComboBox value is selected

I'm making a WPF application following the MVVM pattern (or at least trying to). I have a list of servers that I fetch based on the currently selected "Application". The list of servers are put into a ListBox, and I want the ListBox to update with the new servers when I changed the "Application" via a dropdown menu.

View snippet

The "Current:" value will change based on the selection made (so that binding at least works). I have a ServerListViewModel class which implements INotifyPropertyChanged. Here's a snippet of it

ServerListViewModel (snippet)

public ServerListViewModel()
{
    _serverListModel = new ServerListModel
    {
        ServerList = ConfigUtility.GetServers()
    };
}

...

public BindingList<string> ServerList
{
    get { return _serverListModel.ServerList; }
    set
    {
        if (ReferenceEquals(_serverListModel.ServerList, value)) return;
        _serverListModel.ServerList = value;
        InvokePropertyChanged("ServerList");
    }
}

The constructor properly works properly and the ListBox updates to reflect the ServerList property. 'ConfigUtility.GetServers' uses a saved value of the active "Application" in a JSON file.

In this class I setup a static property like this so that I could try to access this class from another view model

public static ServerListViewModel Instance { get; } = new ServerListViewModel();

SettingsViewModel.cs

The dropdown menu is on a settings tab, while the server list has its own tab. These tabs have their own view models.

Here's a snippet of this view model:

public ComboBoxItem CurrentApplication
{
    get { return _settingsModel.CurrentApplication; }
    set {
        SetCurrentApplication(value);
    }
}       

private void SetCurrentApplication(ComboBoxItem value)
{
    if (ReferenceEquals(_settingsModel.CurrentApplication, value)) return;
    _savedSettings = MySettings.Load();
    if (value.Content == null)
    {
        _settingsModel.CurrentApplication =
            new ComboBoxItem { Content = _savedSettings.CurrentApplication };
    }
    else
    {
        _settingsModel.CurrentApplication = value;
        _savedSettings.CurrentApplication = (string)value.Content;
        _savedSettings.Save();
        ServerListViewModel.Instance.ServerList = ConfigUtility.GetServers();
    }   
    InvokePropertyChanged("CurrentApplication");
}

I also have a MySettings object which saves values to a JSON file so that I can save the active "Application" b/w sessions.

XAML

<TabControl DockPanel.Dock="Bottom" HorizontalAlignment="Stretch" Height="Auto" TabStripPlacement="Bottom">
    <!-- Server List -->
    <TabItem Name="ServerListTab" Header="Server List">
        <TabItem.DataContext>
            <viewModel:ServerListViewModel />
        </TabItem.DataContext>
        <ListBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
        ItemsSource="{Binding ServerList, UpdateSourceTrigger=PropertyChanged}" IsSynchronizedWithCurrentItem="True"
        SelectedItem="{Binding SelectedServer}">
            <ListBox.ItemContainerStyle>
                <Style TargetType="ListBoxItem">
                    <Setter Property="FontSize" Value="14"></Setter>
                </Style>
            </ListBox.ItemContainerStyle>
        </ListBox>
    </TabItem>
    <!-- Settings -->
    <TabItem Name="SettingsTab" Header="Settings">
        <TabItem.DataContext>
             <viewModel:SettingsViewModel />
        </TabItem.DataContext>
        <StackPanel>
             <TextBlock FontWeight="Bold">Application</TextBlock>
             <WrapPanel>
                  <TextBlock>Current:</TextBlock>
                  <TextBlock Text="{Binding CurrentApplication.Content}"></TextBlock>
             </WrapPanel>
             <TextBlock>Select</TextBlock>
             <ComboBox Name="TheComboBox" SelectedItem="{Binding CurrentApplication}">
                  <ComboBoxItem>NetWebServer</ComboBoxItem>
                  <ComboBoxItem>NetSearchService</ComboBoxItem>
                  <ComboBoxItem>NetInterface</ComboBoxItem>
             </ComboBox>
             <TextBlock FontWeight="Bold">Service</TextBlock>
             <WrapPanel>
                  <TextBlock>Current:</TextBlock>
                  <TextBlock></TextBlock>
             </WrapPanel>
             <TextBlock>Select</TextBlock>
             <ComboBox></ComboBox>
             <CheckBox>
                  Perfomance mode
             </CheckBox>
             <CheckBox>
                  Show live servers
             </CheckBox>
             <CheckBox>
                  Show test servers
             </CheckBox>
             <CheckBox>
                  Show only parameters with IP's
             </CheckBox>
        </StackPanel>
    </TabItem>
</TabControl>

I haven't separate these into separate views yet. Is the problem they are not separate user controls in different files with their own view models? I have set the different tab items to the corresponding view model however.

Conclusion/Question

How can I get it so that by selecting a new item in the ComboBox, it also updates the server list? By debugging my code, it seems everything works line by line so somehow the ListBox must not be notified of a change? If I close the app with a different "Application" selected, then open it back up, it correctly uses the saved setting to populate the ListBox with the new values. But while the app is open I cannot get the ListBox to change. Any ideas?

Upvotes: 2

Views: 144

Answers (2)

user2819245
user2819245

Reputation:

In your XAML, you create a ServerListViewModel object, which is being set as DataContext for a TabItem.

However, in the method SetCurrentApplication you update the ServerList property of another ServerListViewModel object (the initializer of this static property created it). You don't see any change in the UI because the ServerListViewModel object used as DataContext has actually not been touched.

Simply use the ServerListViewModel object from the static ServerListViewModel.Instance property as your DataContext:

<TabItem
    DataContext="{x:Static viewModel:ServerListViewModel.Instance}"
    Name="SettingsTab" Header="Settings"
>
    ...

Now, updating ServerListViewModel.Instance.ServerList within the SetCurrentApplication method should cause the data bindings to update.

Another, cleaner alternative is to eschew that static Instance property and use some kind of messaging between the view-models. The SetCurrentApplication method could send a message that the server list should be updated. The ServerListViewModel class would process that message and do the actual work of updating the server list. MVVM libraries such as MVVM Light and others provide Messenger components which facilitate this.

Upvotes: 1

SledgeHammer
SledgeHammer

Reputation: 7705

If I follow everything, SetCurrentApplication() would need to refresh the server list and pop an INotifyChanged event. It doesn't look like its doing that.

Upvotes: 1

Related Questions