0
votes

I have a simple 2 x View + ViewModel program, that shares some data between the ViewModels, such as Members ObservableCollection. I do this via a Parent MainViewModel. However, every time either of the Views is accessed (via the buttons), it "refreshes" the data, and seems to re-generate the Members ObservableCollection. Both Views can access the Members ObservableCollection, but the data is different. It seems a new "MainViewModel" is being created every time either of the Views are accessed...?

Have I got some program logic wrong? I am trying to create a Parent ViewModel that allows the Child ViewModels/Views to share the same DataCollections. I am not too sure where to set the DataContext for the Child Views...

Thanks for your help!

App.xaml

<Application x:Class="CLM_Scheduler.App"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:local="clr-namespace:CLM_Scheduler"
         StartupUri="Views\MainWindow.xaml">
<Application.Resources>
</Application.Resources>

MainWindow.xaml

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

<Window.Resources>
    <DataTemplate DataType="{x:Type local1:MembersViewModel}">
          <local:MembersView/> 
    </DataTemplate> 
    <DataTemplate DataType="{x:Type local1:WeeksViewModel}">
            <local:WeeksView/> 
    </DataTemplate>

</Window.Resources>

<Grid>
    <DockPanel LastChildFill="True">
        <StackPanel x:Name="navigation" DockPanel.Dock="Left">
            <Button Content="Members" Command="{Binding SelectMembersView}"></Button>
            <Button Content="Schedule" Command="{Binding SelectWeeksView}"></Button>
        </StackPanel>
        <ContentControl x:Name="Pages" DockPanel.Dock="Right" Content="{Binding SelectedViewModel}" />
    </DockPanel>
</Grid>

MainViewModel.cs

class MainViewModel : ObservableObject
{
    private MembersViewModel membersViewModel;
    private WeeksViewModel weeksViewModel;

    public MainViewModel()
    {
        membersViewModel = new MembersViewModel();
        weeksViewModel = new WeeksViewModel();
    }

    public MembersViewModel MembersViewModel
    {
        get { return membersViewModel; }
        set { membersViewModel = value; RaisePropertyChanged("MembersViewModel"); }
    }

    public WeeksViewModel WeeksViewModel
    {
        get { return weeksViewModel; }
        set { weeksViewModel = value; RaisePropertyChanged("WeeksViewModel"); }
    }

    private object selectedViewModel;
    public object SelectedViewModel
    {
        get { return selectedViewModel; }
        set { selectedViewModel = value; RaisePropertyChanged("SelectedViewModel");  }
    }

    public ICommand SelectMembersView { get { return new RelayCommand(SelectMembersViewExecute, CanSelectMembersViewExecute); } }
    void SelectMembersViewExecute()
    {
        SelectedViewModel = MembersViewModel;
    }
    bool CanSelectMembersViewExecute()   {        return true;    }

    public ICommand SelectWeeksView { get { return new RelayCommand(SelectWeeksViewExecute, CanSelectWeeksViewExecute); } }
    void SelectWeeksViewExecute()
    {
        SelectedViewModel = WeeksViewModel;
    }
    bool CanSelectWeeksViewExecute() { return true; }
 }

MembersViewModel.cs

class MembersViewModel :  ObservableObject
{
        private Member _SelectedMember;

        ObservableCollection<Member> _members = new ObservableCollection<Member>();

        public ObservableCollection<Member> Members
        {
            get{ return _members;}
            set
            {
                _members = value;
                RaisePropertyChanged("Members");
            }
        }

        public Member SelectedMember
        {
            get { return _SelectedMember; }
            set { _SelectedMember = value; }
        }

        public MembersViewModel()
        {
            for (int i = 0; i < 3; ++i)
            {
            _members.Add(new Member(_database.GetRandomFirstName, _database.GetRandomLastName));
            }
        }

        void AddMemberExecute()
        {
            if (_members == null)
                return;
        _members.Add(new Member(_database.GetRandomFirstName, _database.GetRandomLastName));
        }

        bool CanAddMemberExecute()  {return true;}
        public ICommand AddMember { get { return new RelayCommand(AddMemberExecute, CanAddMemberExecute); } }
}

Members.xaml

<UserControl.DataContext>
    <local2:MainViewModel />
</UserControl.DataContext>
<Grid>
    <Button Grid.Column="0" Grid.Row="1" Content="Add Member" Command="{Binding MembersViewModel.AddMember}" />
    <DataGrid Grid.Column="1" Grid.Row="3" ItemsSource="{Binding MembersViewModel.Members}" SelectedItem="{Binding SelectedMember, Mode=TwoWay}" >
    </DataGrid>
</Grid>
</UserControl>

Weeks.xaml

<UserControl.DataContext>
    <local1:MainViewModel />
</UserControl.DataContext>
<Grid>
    <Button Grid.Column="0" Grid.Row="1" Content="Add Member" Command="{Binding MembersViewModel.AddMember}" />
    <DataGrid Grid.Column="1" Grid.Row="3" ItemsSource="{Binding MembersViewModel.Members}" SelectedItem="{Binding SelectedMember, Mode=TwoWay}" />
</Grid>

1
Why are you setting the DataContext to a MainViewModel in the view? Isn't a MembersView supposed to have a MembersViewModel as its DataContext? It can't have two.mm8
I'd like both MembersView and WeeksView to have MainViewModel as their data context, so they can share data between them. I am not sure where to set this. If I set this in the MainWindow Resources, like this: <DataTemplate DataType="{x:Type local1:MainViewModel}"> <local:MembersView/> </DataTemplate> <DataTemplate DataType="{x:Type local1:MainViewModel}"> <local:WeeksView/> </DataTemplate> Then the program throws a run-time error "Key is already in dictionary"Damian
Make MembersViewModel and ViewWeekModel inherit from MainViewModel?mm8
Yes, I am unsure how to do this... could you please provide some code?Damian
What's all the stuff in the MembersViewModel?mm8

1 Answers

2
votes

A view can only have a single DataContext (view model). This creates a new MainViewModel each time the view is created:

<UserControl.DataContext>
    <local1:MainViewModel />
</UserControl.DataContext>

If you want to inherit the DataContext of the MainWindow, you could bind to it like this:

<UserControl ... DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=Window}}">

You could also bind to any property of the MainWindowViewModel from within a UserControl like this (without setting the DataContext of the UserControl at all):

<TextBlock Text="{Binding DataContext.SomeProperty, RelativeSource={RelativeSource AncestorType=Window}}">