rhowe
rhowe

Reputation: 79

WPF - How can I use an ObservableCollection of viewmodels to show TabItems?

I have a window which contains a TabControl and I would like to have TabItems generated based on user actions. Inside the TabItems I would like to display a UserControl which uses a ViewModel.

I can get everything to display properly, however when the UserControl's ViewModel is updated, the changes are not reflected in the TabItem.

Consider the following simplified example:

MainWindowViewModel.cs

public class MainWindowViewModel : ViewModel
{
    public MainWindowViewModel()
    {
        MyControls = new ObservableCollection<MyControlViewModel>();
    }

    private ObservableCollection<MyControlViewModel> _myControls;
    public ObservableCollection<MyControlViewModel> MyControls
    {
        get { return _myControls; }
        set
        {
            _myControls = value;
            RaisePropertyChangedEvent(nameof(MyControls));
        }
    }
}

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    private static MainWindowViewModel _vm;
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainWindowViewModel();
        _vm = DataContext as MainWindowViewModel;

        var myItem1 = new MyItem("Item1");
        var myItem2 = new MyItem("Item2");

        var myControlVm = new MyControlViewModel(new ObservableCollection<MyItem> { myItem1, myItem2 });

        _vm.MyControls.Add(myControlVm);
    }
}

MainWindow.xaml

<Window x:Class="TabControlTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TabControlTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <TabControl ItemsSource="{Binding MyControls}">
            <TabControl.ContentTemplate>
                <DataTemplate DataType="{x:Type local:MyControlViewModel}">
                    <local:MyControl />
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>
    </Grid>
</Window>

MyControlViewModel.cs

public class MyControlViewModel : ViewModel
{
    public MyControlViewModel(ObservableCollection<MyItem> items)
    {
        MyItems = items;
    }

    private ObservableCollection<MyItem> _myItems;
    public ObservableCollection<MyItem> MyItems
    {
        get { return _myItems; }
        set
        {
            _myItems = value;
            RaisePropertyChangedEvent(nameof(MyItems));
        }
    }
}

MyControl.xaml.cs

public partial class MyControl : UserControl
{
    public MyControl()
    {
        InitializeComponent();
    }

    private void ListBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
    {
        var vm = DataContext as MyControlViewModel;

        foreach (var item in vm.MyItems)
        {
            item.ShouldBold = !item.ShouldBold;
        }
    }
}

MyControl.xaml

<UserControl x:Class="TabControlTest.MyControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:TabControlTest"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <ListBox ItemsSource="{Binding MyItems}" MouseDoubleClick="ListBox_MouseDoubleClick">
            <ListBox.Resources>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Setter Property="Content" Value="{Binding Label}" />
                    <Setter Property="FontWeight" Value="Normal" />
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding ShouldBold}" Value="True">
                            <Setter Property="FontWeight" Value="Bold" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </ListBox.Resources>
        </ListBox>
    </Grid>
</UserControl>

MyItem.cs

public class MyItem : ViewModel
{
    public MyItem(string label)
    {
        Label = label;
    }

    private string _label;
    public string Label
    {
        get { return _label; }
        set
        {
            _label = value;
            RaisePropertyChangedEvent(nameof(Label));
        }
    }

    private bool _shouldBold;
    public bool ShouldBold
    {
        get { return _shouldBold; }
        set
        {
            _shouldBold = value;
            RaisePropertyChangedEvent(nameof(ShouldBold));
        }
    }
}

When I double-click on the MyControl in the MainWindow I would expect the items to be displayed in bold via the ListBox_MouseDoubleClick method, however the style is not applying. When I step through the ListBox_MouseDoubleClick method, I can see that the ShouldBold is being updated correctly, but again the style is not applying.

Is there another way I should structure this or something else I need to do to have the style apply? Any help is appreciated!

Edit

Here's my ViewModel.cs

public class ViewModel
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChangedEvent(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Upvotes: 0

Views: 492

Answers (1)

mm8
mm8

Reputation: 169200

Your ViewModel class should implement INotifyPropertyChanged:

public class ViewModel : INotifyPropertyChanged
...

Upvotes: 3

Related Questions