I’m writing an app in C# using WinUI 3 platform.
Being new to this world, my starting point is the template created through Template Studio.
I’m also using MVVM Community Toolkit.
I’m planning to use the blank space in the left Navigation Pane to add context-based UI controls.
So, for example, if the user is in page SimpRec and clicks a button, a section appears on the left navigation pane with 2 ComboBoxes used to filter some data.
The Navigation Pane is defined in my ShellPage.xaml as follows and its Visibility property is to be bound to property IsFilterMenuVisible.
ShellPage.xaml:
<Page>
<!--Other code-->
<NavigationViewItemHeader Content="Simp Rec" />
<NavigationViewItem Icon="Filter"
Content="Filters"
SelectsOnInvoked="False"
IsSelected="False"
Visibility="{x:Bind IsFilterMenuVisible, Mode=OneWay}"
AutomationProperties.Name="filter">
<NavigationViewItem.MenuItems>
<StackPanel Orientation="Horizontal">
<FontIcon FontFamily="{StaticResource FabExIcons}"
Glyph=""
Margin="{StaticResource SmallRightMargin}" />
<ComboBox Text="Filter by Period"
Width="200"
ToolTipService.ToolTip="Periods" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<FontIcon FontFamily="{StaticResource CustomIconFont}"
Glyph=""
Margin="{StaticResource SmallRightMargin}" />
<ComboBox Text="Filter by Model"
Width="200"
ToolTipService.ToolTip="Models" />
</StackPanel>
</NavigationViewItem.MenuItems>
</NavigationViewItem>
<!--More code-->
</Page>
I did some research and to reach my goal I decided to create the [ObservableProperty]
in my SimpRecViewModel as highlighted in the following code where the [RelayCOmmand]
to be used to updated the status of property IsFilterMenuVisible is also shown.:
SimpRecViewModel.cs:
public partial class SimpRecViewModel : ObservableRecipient, INavigationAware
{
private readonly INavigationService _navigationService;
private readonly IAppService _appDiagnosticsService;
[ObservableProperty]
private Visibility isFilterMenuVisible = Visibility.Collapsed;
public SimpRecViewModel(INavigationService navigationService, IAppService appDiagnosticsService)
{
_navigationService = navigationService;
_appDiagnosticsService = appDiagnosticsService;
}
public async void OnNavigatedTo(object parameter)
{
// Some code
}
public void OnNavigatedFrom()
{
}
[RelayCommand]
private async Task ShowFilters(int selectedIndex)
{
// Some code
IsFilterMenuVisible = Visibility.Visible;
// More code
}
I added a SimpRecViewModel as a parameter in the constructor of ShellViewModel:
ShellPage.xaml.cs:
public sealed partial class ShellPage : Page
{
public ShellViewModel ViewModel
{
get;
}
public SimpRecViewModel SRViewModel
{
get;
}
public ShellPage(ShellViewModel viewModel, SimpRecViewModel srViewModel)
{
ViewModel = viewModel;
InitializeComponent();
SRViewModel = srViewModel;
ViewModel.NavigationService.Frame = NavigationFrame;
ViewModel.NavigationViewService.Initialize(NavigationViewControl);
// More code
}
And I updated the DataContext of the NavigationViewItem
to the SRViewModel:
ShellPage.xaml:
<Page
xmlns:srvm="using:MyApp.ViewModels">
<!--Some code-->
<NavigationViewItem Icon="Filter"
Content="Filters"
SelectsOnInvoked="False"
IsSelected="False"
Visibility="{x:Bind SRViewModel.IsFilterMenuVisible, Mode=OneWay}"
AutomationProperties.Name="filter">
<NavigationViewItem.DataContext>
<srvm:SimpRecViewModel />
</NavigationViewItem.DataContext>
<!--More code-->
</Page>
And I had to add a parameterless constructor to my SimpRecViewModel to avoid error XLS0507.
When I tried the code, the filter section in the left pane was correctly collapsed, but when I hit the button, nothing changed. I expected to see the section appear instead.
Another approach I tried based on an answer to a similar question (/questions/16506653/accessing-a-property-in-one-viewmodel-from-another) was to create the [ObservableProperty]
IsFilterMenuVisible in the ShellViewModel as follows:
ShellViewModel.cs:
public partial class ShellViewModel : ObservableRecipient
{
// Some code
[ObservableProperty]
private Visibility isFilterMenuVisible = Visibility.Collapsed;
public INavigationService NavigationService
{
get;
}
public INavigationViewService NavigationViewService
{
get;
}
public ShellViewModel(INavigationService navigationService, INavigationViewService navigationViewService)
{
NavigationService = navigationService;
NavigationService.Navigated += OnNavigated;
NavigationViewService = navigationViewService;
}
private void OnNavigated(object sender, NavigationEventArgs e)
{
IsBackEnabled = NavigationService.CanGoBack;
if (e.SourcePageType == typeof(SettingsPage))
{
Selected = NavigationViewService.SettingsItem;
return;
}
// More code
}
}
And to modify the SimpRecViewModel to add ShellViewModel as a parameter in the constructor:
SimpRecViewModel.cs:
public partial class SimpRecViewModel : ObservableRecipient, INavigationAware
{
private readonly INavigationService _navigationService;
private readonly IAppService _appDiagnosticsService;
// Some code
public ShellViewModel ShellViewModel { get; set; }
public SimpRecViewModel(INavigationService navigationService, IAppService appDiagnosticsService, ShellViewModel shellViewModel)
{
_navigationService = navigationService;
_appDiagnosticsService = appDiagnosticsService;
ShellViewModel = shellViewModel;
}
public async void OnNavigatedTo(object parameter)
{
// Other code
}
public void OnNavigatedFrom()
{
}
[RelayCommand]
private async Task ShowFilters(int selectedIndex)
{
// Some code
ShellViewModel.IsFilterMenuVisible = Visibility.Visible;
// More code
}
Pressing the button had no effect on the Visibility of the Filter section in the Navigation Pane.
As a check, I added the option of changing the Visibility as a result of the selection of Settings Page within the ShellViewModel.
ShellViewModel.cs:
public partial class ShellViewModel : ObservableRecipient
{
// Some code as before
[ObservableProperty]
private Visibility isFilterMenuVisible = Visibility.Collapsed;
// Same code too
private void OnNavigated(object sender, NavigationEventArgs e)
{
IsBackEnabled = NavigationService.CanGoBack;
if (e.SourcePageType == typeof(SettingsPage))
{
Selected = NavigationViewService.SettingsItem;
IsFilterMenuVisible = Visibility.Visible;
return;
}
// Same code shown previously
}
}
This time, clicking on the Settings icon had indeed the effect of making the Filter section visible.
The linked answer also proposed to create a static instance of ShellViewModel (in my case) and access it from SimpRecViewModel.
I tried this approach by first creating a parameterless constructor for ShellViewModel.
The reason being that I don’t know what parameters to pass to
private static ShellViewModel _instance = new ShellViewModel();
if the constructor has the following signature:
public ShellViewModel(INavigationService navigationService, INavigationViewService navigationViewService)
ShellViewModel.cs:
public partial class ShellViewModel : ObservableRecipient
{
// Usual code
[ObservableProperty]
private Visibility isFilterMenuVisible = Visibility.Collapsed;
private static ShellViewModel _instance = new ShellViewModel();
public static ShellViewModel Instance { get { return _instance; } }
ShellViewModel() { }
public INavigationService NavigationService
{
get;
}
public INavigationViewService NavigationViewService
{
get;
}
public ShellViewModel(INavigationService navigationService, INavigationViewService navigationViewService)
{
NavigationService = navigationService;
NavigationService.Navigated += OnNavigated;
NavigationViewService = navigationViewService;
}
// All the other code
}
Then I used the static instance of ShellViewModel to change the status of the property IsFilterMenuVisible. With no effect.
SimpRecViewModel.cs:
public partial class SimpRecViewModel : ObservableRecipient, INavigationAware
{
// Previous code
public SimpRecViewModel(INavigationService navigationService, IAppService appDiagnosticsService)
{
_navigationService = navigationService;
_appDiagnosticsService = appDiagnosticsService;
}
// Unrelated code
[RelayCommand]
private async Task ShowFilters(int selectedIndex) // async Task ShowFilters(int selectedIndex)
{
// Other code
ShellViewModel.Instance.IsFilterMenuVisible = Visibility.Visible;
// Final code
}
Which means that I’m clearly failing to understand how to properly set the DataContext of that section of the Navigation Pane to a different ViewModel.
My ideal scenario is one in which the DataContext is set to the SRViewModel page. That is because I’m planning to have other properties from that page to affect some controls of the UI in ShellPage.xaml.
And other pages are going to have similar impacts on ShellPage.xaml.
Any help is appreciated.