0
votes

I want to make a UWP application with a ListBox navigation whose selected item determines the content of a frame. When the selection changes, the frame content should change. I found an example for how to this (Method 1 below), but it uses event handlers in the code behind. I want to learn MVVM with this project and thus want to use a MVVM solution to this problem. I'm new to MVVM and my current understanding is that in order to decouple the View from the ViewModel, ViewModels shouldn't reference anything particular to the View. Is this a correct understanding? The only way I can think of using the ViewModel to change the frame view is to basically move the event handler code to the ViewModel and pass in the Frame to the constructor (Method 2 below). But that violates my understanding of the ViewModel relationship to the View since the ViewModel is referencing particular instances of things in the View; furthermore it seems like pointless overhead and like it would give very little organization benefit.

How would you implement a MVVM solution to this problem? Or is this a case where using event handlers is better?

METHOD 1 - Event handler: This code is based on an example Microsoft provides. (Here's the link to the relevant code: https://github.com/microsoft/Windows-universal-samples/tree/master/Samples/Playlists/cs)

public sealed partial class MainPage : Page
{
    List<Scenario> scenarios = new List<Scenario>
    {
        new Scenario() { Title = "Scenario 1", ClassType = typeof(Scenario1) },
        new Scenario() { Title = "Scenario 2", ClassType = typeof(Scenario2) }
    };

    public MainPage()
    {
        this.InitializeComponent();
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        ScenarioControl.ItemsSource = scenarios;
    }

    private void ScenarioControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        ListBox scenarioListBox = sender as ListBox;
        Scenario s = scenarioListBox.SelectedItem as Scenario;
        if (s != null)
        {
            ScenarioFrame.Navigate(s.ClassType);
        }
    }
}

public class Scenario
{
    public string Title { get; set; }
    public Type ClassType { get; set; }

    public override string ToString()
    {
        return Title;
    }
}

<!-- MainPage.xaml -->
<Grid>
    <SplitView x:Name="Splitter" IsPaneOpen="True" DisplayMode="Inline">
        <SplitView.Pane>
            <RelativePanel>
                <ListBox x:Name="ScenarioControl" SelectionChanged="ScenarioControl_SelectionChanged"/>
            </RelativePanel>
        </SplitView.Pane>
        <RelativePanel>
            <Frame x:Name="ScenarioFrame" />
        </RelativePanel>
    </SplitView>
</Grid>

METHOD 2 - MVVM(?):

<!-- MainPage.xaml -->
<Grid>
    ...
    <ListBox x:Name="ScenarioControl" SelectionChanged="{x:Bind MyViewModel.SwitchScenario}"/>
    ...
</Grid>
// MainPage.xaml.cs
...
    public MainPage()
    {
        this.InitializeComponent();
        MyViewModel = new MyViewModel(ScenarioFrame);
    }
...
    MyViewModel MyViewModel { get; set; }
}

// MyViewModel.cs
public class MyViewModel
{
    public MyViewModel(Frame scenarioFrame)
    {
        ScenarioFrame = scenarioFrame;
    }

    public void SwitchScenario(object sender, SelectionChangedEventArgs e)
    {
        ListBox scenarioListBox = sender as ListBox;
        Scenario s = scenarioListBox.SelectedItem as Scenario;
        if (s != null)
        {
            ScenarioFrame.Navigate(s.ClassType);
        }
    }

    public Frame ScenarioFrame { get; set; }
}
2

2 Answers

0
votes

You’ll need PropertyChangedNotification when your model property changes- anything binding to the model property will then update automatically.

Also Bindings.Update() is your friend sometimes.

And if you’re going to have a View and a ViewModel you’ll need to set the DataContext of your View to the instance of the ViewModel or Model you are binding to.

0
votes

How would you implement a MVVM solution to this problem? Or is this a case where using event handlers is better?

For implementing MVVM navigation, you could refer Template 10 and Template Studio workflow.

In template 10, it bind Click event with navigation method.

<controls:PageHeader x:Name="pageHeader" RelativePanel.AlignLeftWithPanel="True"
                     RelativePanel.AlignRightWithPanel="True"
                     RelativePanel.AlignTopWithPanel="True" Text="Main Page">

    <!--  secondary commands  -->
    <controls:PageHeader.SecondaryCommands>
        <AppBarButton Click="{x:Bind ViewModel.GotoSettings}" Label="Settings" />
        <AppBarButton Click="{x:Bind ViewModel.GotoPrivacy}" Label="Privacy" />
        <AppBarButton Click="{x:Bind ViewModel.GotoAbout}" Label="About" />
    </controls:PageHeader.SecondaryCommands>

</controls:PageHeader>

ViewModel

 public void GotoDetailsPage() =>
     NavigationService.Navigate(typeof(Views.DetailPage), Value);

 public void GotoSettings() =>
     NavigationService.Navigate(typeof(Views.SettingsPage), 0);

In Template Studio, it navigates with NavHelper class.

<winui:NavigationViewItem x:Uid="Shell_Main" Icon="Document" helpers:NavHelper.NavigateTo="views:MainPage" />
<winui:NavigationViewItem x:Uid="Shell_Blank" Icon="Document" helpers:NavHelper.NavigateTo="views:BlankPage" />
<winui:NavigationViewItem x:Uid="Shell_MediaPlayer" Icon="Document" helpers:NavHelper.NavigateTo="views:MediaPlayerPage" />
<winui:NavigationViewItem x:Uid="Shell_WebView" Icon="Document" helpers:NavHelper.NavigateTo="views:WebViewPage" />


<ic:EventTriggerBehavior EventName="ItemInvoked">
                <ic:InvokeCommandAction Command="{x:Bind ViewModel.ItemInvokedCommand}" />
            </ic:EventTriggerBehavior>

ViewModel

private void OnItemInvoked(WinUI.NavigationViewItemInvokedEventArgs args)
{
    if (args.IsSettingsInvoked)
    {
        NavigationService.Navigate(typeof(SettingsPage));
        return;
    }

    var item = _navigationView.MenuItems
                    .OfType<WinUI.NavigationViewItem>()
                    .First(menuItem => (string)menuItem.Content == (string)args.InvokedItem);
    var pageType = item.GetValue(NavHelper.NavigateToProperty) as Type;
    NavigationService.Navigate(pageType);
}