2

I have one Window with multiple Frames. Each Frame holds one or more Pages. All Pages use the same ViewModel. I control the Visibility of each Frame to manipulate the UI.

I've recently realized that when my program launches each of the Pages are firing the same Constructor of the ViewModel (so the same constructor is firing multiple times) (a simple MessageBox.Show in the constructor fires multiple times at launch). It kind of makes sense to me why this is happening, but not what I want.

Also, I've come to believe that the reason I am having trouble manipulating the different frames in C# is because I may have created new "objects" of the ViewModel?? I'm not exactly sure if this is what's happening, but I'm pretty sure it is not what I want.

Is there a way of setting the DataContext (in Xaml) for the Pages so that the Constructor fires only once, but also still sets each pages' DataContext to the ViewModel? Or is there a different approach I should be exploring? I am still learning...

Each Page has the following Xaml:

<Page.DataContext>
    <ViewModel:ActiveJobViewModel/>
</Page.DataContext>

my Frames look like this. I realize a single frame can hold multiple pages, but manipulating the visibility gave me better performance and I don't want the Constructor to run every time the Page source changes.

<Frame Source="ActiveJobPage.xaml" Grid.Row="2" Grid.Column="3" 
       Visibility="{Binding ElementName=ActiveJobPageToggleButton, Path=IsChecked, Converter={StaticResource booleanToVisibility}, UpdateSourceTrigger=PropertyChanged}" 
       Style="{StaticResource FramePage}">
</Frame>

<Frame  Source="CustomerPage.xaml" Grid.Row="2" Grid.Column="3"
        Visibility="{Binding objHomePage_PageVisibility.CustomersPageToggleButtonIsChecked, Converter={StaticResource booleanToVisibility}, UpdateSourceTrigger=PropertyChanged}"
        Style="{StaticResource FramePage}">
</Frame>
mjordan
  • 319
  • 2
  • 22

2 Answers2

2

Remove the following from Page XAML.

<Page.DataContext>
    <ViewModel:ActiveJobViewModel/>
</Page.DataContext>

And set the DataContext of both pages to the same instance in the code manually.

For example in your Page codebehind class.

public void SetDataContext(ActiveJobViewModel commonContext)
{
   this.DataContext = commonContext;
}

Then create a common instance of ActiveJobViewModel instance and set the same datacontext for multiple pages.

CharithJ
  • 46,289
  • 20
  • 116
  • 131
  • Thanks. Can you explain what you mean by "set the DataContext of both pages to the same instance in the code manually."? – mjordan Aug 18 '18 at 22:32
  • See the updated answer. Does that make sense? You have to create a common DataContext instance and set specifically for multiple pages. – CharithJ Aug 18 '18 at 23:02
  • I almost have it... I removed Xaml DataContext. I added the SetDataContext method to each of my Pages, if that is what you meant me to do. However, I'm not sure where/how I should create a common instance of ActiveJobViewModel or how to set the same DataContext for the multiple pages? Sorry, struggling a bit to wrap my head around this... – mjordan Aug 18 '18 at 23:22
  • while researching, I found this Xaml based answer. https://stackoverflow.com/questions/4590446/how-do-i-set-a-viewmodel-on-a-window-in-xaml-using-datacontext-property C# or Xaml doesn't really matter to me. I'm just trying to understand... I believe it is accomplishing the same thing. CharithJ do you agree? – mjordan Aug 18 '18 at 23:47
  • @mjordan: Sorry that I missed your comment. Yes, that will do the same. I think you need DynamicResource if the DataContext can change in run time. https://stackoverflow.com/a/15714079/591656 – CharithJ Aug 20 '18 at 10:03
  • No problem. Your feedback got me on the right track. A few days ago I assumed I was pointing pages at a static ViewModel. Now that I learned that wasn't correct the rest of the program is falling into place! thanks again. – mjordan Aug 20 '18 at 16:59
2

The following creates a new instance of the ActiveJobViewModel class:

<ViewModel:ActiveJobViewModel/>

If all your pages and frames share the same view model, you only want to do this once - in the window where the frames are defined. If you define the view model as a resource in the window:

<Window ...>
    <Window.Resources>
        <ViewModel:ActiveJobViewModel x:Key="viewModel"/>
    </Window.Resources>
    <!-- frames ... -->
</Window>

...you could then handle the LoadCompleted event of the Frames and set the DataContext of the their content programmatically:

private void Frame_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
    Frame frame = (Frame)sender;
    FrameworkElement content = frame.Content as FrameworkElement;
    if (content != null)
        content.DataContext = Resources["viewModel"];
}

XAML:

<Frame Source="ActiveJobPage.xaml" Grid.Row="2" Grid.Column="3"
        LoadCompleted="Frame_LoadCompleted"
        Visibility="{Binding ElementName=ActiveJobPageToggleButton, Path=IsChecked, Converter={StaticResource booleanToVisibility}, UpdateSourceTrigger=PropertyChanged}" 
        Style="{StaticResource FramePage}">
</Frame>

Don't forget to remove the <Page.DataContext> element from the pages.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • This is a great approach too. Ultimately I defined the ViewModel in App.xaml and then applied the pages' dataContext in xaml. DataContext="{StaticResource ActiveJobViewModel}". So far this seems to be working the way I had hoped. Thanks for your reply. I am constantly learning from them! – mjordan Aug 20 '18 at 16:57