0

I have a window that displays templates in a tree, these can be selected which updates a ListView with available fields in the template. All related operations until here are managed by the TemplateViewModel declared at windows level as:

    <Window.DataContext>
        <vm:TemplateViewModel/>
    </Window.DataContext>

 extract of the class:

 public class TemplateViewModel : ViewModelBase,INotifyPropertyChanged
  {
    public FieldTypeViewModel FieldTypeView { get; }

    public TemplateViewModel()
    {
      // Create additional view
      FieldTypeView = new FieldTypeViewModel(this);
      ...
    }

Each template field has an identifier and type which are still managed by this view (all working up to here).

Now depending on the type of the field a different page is to be displayed in a reserved window part (Frame). Also the type view model is a separate view model class FieldTypeView . The FieldType object is created in the constructor of the TemplateViewModel and saved in the FieldTypeView property as it needs to be linked to this model for updating as field gets selected.

Both views used to implement the INotifyPropertyChanged interface but since the FieldTypeView is created by the view and not by the window defintion the notification event is not set, so I currently call the parent (TemplateViewModel) event for notification.

So I have a frame defined as:

<Frame DataContext="{Binding FieldTypeView}" Grid.Row="1" Content="{Binding CurrentFieldTypeSetupPage}"/>
        
      public class FieldTypeViewModel : ViewModelBase
      {
        private TemplateViewModel _templateViewModel;
    
        private TTemplateFieldType? _FieldType;
    
        public TTemplateFieldType? FieldType
        {
          get { return _FieldType; }
          set { _FieldType = value;
                UpdateFieldType();
                NotifyPropertyChanged("FieldType"); }
        }
    
    
        private Page? _CurrentFieldTypeSetupPage;
        public Page? CurrentFieldTypeSetupPage
        {
          get { return _CurrentFieldTypeSetupPage; }
          set { _CurrentFieldTypeSetupPage = value; NotifyPropertyChanged("CurrentFieldTypeSetupPage"); }
        }
    
        // Define property per type for easy data context access
        public TTFTText? tfText { get; set;  }
        public TTFTDate? tfDate { get; set; }
    
        //------------------------------------------------------------------------------
        private void UpdateFieldType()
        {
          // Set the appropriate field type, and "null" the others
          tfText = _FieldType as TTFTText;
          tfDate = _FieldType as TTFTDate;
    
          if (_FieldType != null)
          {
            CurrentFieldTypeSetupPage = _FieldType.GetSetupPage();
          }
          else
          {
            CurrentFieldTypeSetupPage = null;
          }
    
        }
    
        //------------------------------------------------------------------------------
        public void NotifyPropertyChanged(string prop)
        {
          _templateViewModel.NotifyPropertyChanged(prop);
        }
    
        //------------------------------------------------------------------------------
        public FieldTypeViewModel(TemplateViewModel templateVM)
        {
         _templateViewModel = templateVM;
        }
    
      }

Every time the field selection changes the TemplateViewModel does set the FieldTypeView which gets the correct window for the current type and sets its CurrentFieldTypeSetupPage, which finally notifies the change via NotifyPropertyChanged("CurrentFieldTypeSetupPage"); which actually calls the TemplateViewModel's NotifyPropertyChanged method calling the event handler to notify the change.

Note that notification in the TemplateViewModel works for all its other fields, but the type page is never shown.

So the question is what I am doing wrong or what is the correct way to implement dynamic page changing in MVVM. My guess is that INotifyPropertyChange is not the correct way to go ?

Gaston
  • 115
  • 1
  • 7
  • 1
    I've seen stuff similar to this and the easiest way I have seen is someone used a tab control with different tabs for each type of "page" needed to be shown. You just bind the data to the appropriate tabitem and then change the tab index to show the page needed. – Kevin Cook Dec 30 '21 at 16:02
  • 1
    The best way is to use a ContentControl as page host and a DataTemplate for each page (to render the associated page model). [How do I toggle between pages in a WPF application?](https://stackoverflow.com/a/58849975/3141792), [C# WPF Navigation Between Pages (Views)](https://stackoverflow.com/a/61323201/3141792). – BionicCode Dec 30 '21 at 16:06
  • Thanks for the suggestions. Unfortunately they both break OO and/or MVVM design goals IMHO. In absence of other options I would of course go for one of these solutions. I did actually already try the TabControl before I wrote but dropped it as the view model would have to steer the tab control. The ContentControl solution is similar even if the logic is moved to XAML it is still steered from the view. – Gaston Dec 30 '21 at 23:29
  • Is there really no solution where I could just assign the page dynamically to a viewmodel property and use data binding to that property to display the current page ? – Gaston Dec 30 '21 at 23:31
  • Short update as I did progress a bit in my quest. I got the page top correctly show by assigning it to CurrentFieldTypeSetupPage. Initially I did use the windows data context and did bind the frames content to TemplateViewModel.FileTypeView.CurrentFieldTypeSetupPage . In that scenario the PropertyChanged event did not get registered in the FiledTypeView and I used the parents event to either notify "FileTypeView.CurrentFieldTypeSetupPage" or "CurrentFieldTypeSetupPage" with no success. Now I did set the local data context to "FileTypeView" which now sets the event and page switches. – Gaston Dec 30 '21 at 23:58
  • @BionicCode But I am still struggling with two issues. 1) Pages only support Windows or Frames as parent, and Frames do have navigation and I did not find any way to disable the history., I could hide the navigation UI, but this does not exclude other ways of navigation and would not be a clean solution anyway, 2) Data binding of the type pages. Not really struggling but needs to get investigated before I can post the complete solution. – Gaston Dec 31 '21 at 00:06
  • Ok, I have solved the frame navigation by deleting the back navigation entry in each "Navigated" event and after verification that it works as expected, set the frames NavigationUIVisibility="Hidden" in XAML – Gaston Dec 31 '21 at 01:07
  • *"Unfortunately they both break OO and/or MVVM design goals IMHO"* - They definitely don't break OOor MVVM. The solution shows a classic view-model-first approach, where you bind a view model class to a ContentControl. The view is then shown via a DataTemplate that targets the view model's type. You can create a UserControl (instead of a Page) to create the actual page content. Simply add the UserControl to the DataTemplate. This is MVVM as pure as it can get. Simply follow the given example. – BionicCode Dec 31 '21 at 01:14

0 Answers0