Daine
Daine

Reputation: 9

C# WPF MVVM - View doesn't change when Item Added to ObservableCollection in VM Instance

Description

I am currently building an app where if the user triggers an event on a combobox found on the Main View, it will take the selecteditem and add it to a ObservableCollection on a secondary view, when the user triggers the event to add the item, it successfully adds it to the Collection but does not update the UI in the ItemsControl that the ObservableCollection is a binding of.

Problem

UI isn't updating when ObservableCollection has an item added to it in another ViewModel.

JobList

JobList is derived from the ObservableCollection<Job> type allowing for the loading of later stored data using xml on loading the application, it has a limit of 6 items.

class JobList : ObservableCollection<Job>
{
    public JobList () : base ()
    {
        if (JobListExists)
            LoadExistingJobList();
    }

    private bool JobListExists
    {
        get {

            bool result = false;

            if (File.Exists(recentsPath + @"\RecentItems.xml"))
            {
                result = true;
            }

            return result;
        }
    }

    private void LoadExistingJobList ()
    {
        using (var reader = new StreamReader(recentsPath + @"\RecentItems.xml"))
        {
            XmlSerializer deserializer = new XmlSerializer(typeof(List<Job>),
                new XmlRootAttribute("Jobs"));
            List<Job> jobs = (List<Job>)deserializer.Deserialize(reader);

            foreach (Job job in jobs)
            {
                Add(job);
            }
        }
    }

    public void AddToCollection (Job job)
    {
        Insert(0, job);

        if (Count == 7)
            RemoveAt(6);
    }
}

MainView

The MainView is being used for the MainWindow and stores the SearchBox (To be used in multiple places) and a ContentControl that stores the CurrentView. When an event occurs from here, no subsequent changes happen to the view bound to the ContentControl.

<Window.DataContext>
   <viewModel:MainViewModel/>
</Window.DataContext>

    <Border Background="#272537"
            CornerRadius="15">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            
            <Grid.RowDefinitions>
                <RowDefinition Height="50"/>
                <RowDefinition/>
            </Grid.RowDefinitions>

            <Image Source="/Images/LegIcon.png" Width="30" Height="30" />

            <StackPanel Grid.Row="1">
                <RadioButton Height="40" Margin="5" Content="/Images/Home.png" Style="{StaticResource MenuButtonTheme}" IsChecked="True" Command="{Binding HomeViewCommand}"/>
                <RadioButton Height="40" Margin="5" Content="/Images/Globals.png" Style="{StaticResource MenuButtonTheme}" Command="{Binding GlobalViewCommand}"/>
            </StackPanel>
            <RadioButton Grid.Row="1" Height="40" Margin="5" Content="/Images/Settings.png" VerticalAlignment="Bottom" Style="{StaticResource MenuButtonTheme}"/>

            <StackPanel Grid.Column="1" Orientation="Horizontal">
                <ComboBox Width="190"
                     Height="40"
                     VerticalAlignment="Center"
                     HorizontalAlignment="Left"
                     Margin="5"
                     Grid.Column="1"
                     Style="{StaticResource SearchBoxTheme}"
                     ItemsSource="{Binding CurrentDropdownData}"
                        IsEditable="true"
                     DisplayMemberPath="Name"
                     IsSynchronizedWithCurrentItem="True">
                    <ComboBox.InputBindings>
                        <KeyBinding Gesture="Enter"
                        Command="{Binding SearchBoxCommand}"
                        CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ComboBox}}}"/>
                    </ComboBox.InputBindings>
                </ComboBox>

                <Button Name="MinimizeButton" Style="{StaticResource MinimizeButton}" Margin="5" Width="40" Click="MinimizeButton_Click" />
            </StackPanel>
            <ContentControl Grid.Row="1"
                            Grid.Column="1"
                            Content="{Binding CurrentView}"/>
        </Grid>
    </Border>

MainViewModel

class MainViewModel : ObservableObject
{
    public RelayCommand HomeViewCommand { get; set; }
    public RelayCommand GlobalViewCommand { get; set; }
    public RelayCommand SearchBoxCommand { get; set; }

    public HomeViewModel HomeVM { get; set; }
    public GlobalViewModel GlobalVM { get; set; }

    private object _currentView;

    public object CurrentView
    {
        get { return _currentView; }
        set
        {
            _currentView = value;
            OnPropertyChanged();
        }
    }

    private ICollectionView _currentDropdownData;

    public ICollectionView CurrentDropdownData
    { 
        get { return _currentDropdownData; }
        set
        {
            _currentDropdownData = value;
            OnPropertyChanged("CurrentDropdownData");
        }
    }

    public MainViewModel()
    {
        CurrentDropdownData = CollectionViewSource.GetDefaultView(DataProvider.GetProjectItems());
        HomeVM = new HomeViewModel();
        GlobalVM = new GlobalViewModel();
        CurrentView = HomeVM;

        HomeViewCommand = new RelayCommand(o =>
        {
            CurrentDropdownData = CollectionViewSource.GetDefaultView(DataProvider.GetProjectItems());
            CurrentView = HomeVM;
            SearchBoxCommand = new RelayCommand(e =>
            {
                ((HomeViewModel)CurrentView).Collection.AddToCollection((Job)CurrentDropdownData.CurrentItem);
            });
        });

        GlobalViewCommand = new RelayCommand(o =>
        {
            CurrentDropdownData = CollectionViewSource.GetDefaultView(DataProvider.GetGlobalItems());
            CurrentView = GlobalVM;
            SearchBoxCommand = new RelayCommand(e =>
            {
            });
        });

        SearchBoxCommand = new RelayCommand(e =>
        {
            ((HomeViewModel)CurrentView).Collection.AddToCollection((Job)CurrentDropdownData.CurrentItem);
        });
    }
}

HomeView

The Home View solely uses a ItemsControl that refers to the "Collections" JobList as a DataBinding to display the currently selected Jobs

<UserControl.DataContext>
        <viewModel:HomeViewModel/>
    </UserControl.DataContext>
    <ItemsControl x:Name="TilePanel" ItemsSource="{Binding Collection, Mode=TwoWay}" xmlns:model="clr-namespace:JobMate_2.MVVM.Model">
        <ItemsControl.ItemTemplate>
            <DataTemplate DataType="{x:Type model:Job}">
                <Border Background="#353340"
                        CornerRadius="5" 
                        Margin="5"
                        Height="50">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition Width="25"/>
                        </Grid.ColumnDefinitions>

                        <Grid.RowDefinitions>
                            <RowDefinition/>
                            <RowDefinition/>
                        </Grid.RowDefinitions>

                        <TextBlock Text="{Binding Name, Mode=OneWay}"
                                   Foreground="White"
                                   Margin="5"
                                   FontFamily="/Fonts/#Poppins"
                                   x:Name="JobTitle"/>

                        <Button Grid.Column="1"
                                Content="."
                                HorizontalAlignment="Stretch"
                                VerticalAlignment="Stretch"
                                Margin="2.5"
                                Visibility="Hidden"/>

                        <UniformGrid Columns="3" Grid.Row="1">
                            <Button Content="Shared" Margin="2.5" Style="{StaticResource FileLocationButton}"/>
                            <Button Content="Job Data" Margin="2.5" Style="{StaticResource FileLocationButton}"/>
                            <Button Content="Proc. Data" Margin="2.5" Style="{StaticResource FileLocationButton}"/>
                        </UniformGrid>
                    </Grid>
                </Border>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>

HomeViewModel

The HomeViewModel very simply stores the Collection that is being used in the View for databinding.

class HomeViewModel : ObservableObject
{
    private JobList _collection;

    public JobList Collection
    {
        get { return _collection; }
        set
        {
            _collection = value;
            OnPropertyChanged("Collection");
        }
    }

    public HomeViewModel ()
    {
        Collection = new JobList()
        {
            new Job () { Name = "Test" },
            new Job () { Name = "Test 2" },
            new Job () { Name = "Test 3" }
        };
    }
}

Upvotes: 0

Views: 550

Answers (2)

EldHasp
EldHasp

Reputation: 7908

If you create an instance of a UserControl in a Window with the MainViewModel in its Data Context, then you need a binding like this:

<UserControl *****
    ***********
    DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.CurrentView}">
    <d:UserControl.DataContext>
        <viewModel:HomeViewModel/>
    </d:UserControl.DataContext>

Upvotes: 0

EldHasp
EldHasp

Reputation: 7908

There can be only one reason.
You have different instances of HomeViewModel in Window and UserControl.
The UserControl does not have to create its own Data Context.
He should get it from the Window by binding to the CurrentView.

Upvotes: 1

Related Questions