【问题标题】:How to use MVVM ViewModel to change stuff in a View based on a ListView SelectionChanged event如何使用 MVVM ViewModel 根据 ListView SelectionChanged 事件更改视图中的内容
【发布时间】:2019-11-05 05:51:58
【问题描述】:

我想制作一个带有 ListBox 导航的 UWP 应用程序,它的选定项确定框架的内容。 When the selection changes, the frame content should change.我找到了一个示例(下面的方法 1),但它在后面的代码中使用了事件处理程序。我想通过这个项目学习 MVVM,因此想使用 MVVM 解决这个问题。我是 MVVM 的新手,我目前的理解是,为了将 View 与 ViewModel 分离,ViewModel 不应引用任何特定于 View 的东西。这是一个正确的理解吗?我能想到的使用 ViewModel 更改框架视图的唯一方法是基本上将事件处理程序代码移动到 ViewModel 并将 Frame 传递给构造函数(下面的方法 2)。但这违反了我对 ViewModel 与 View 关系的理解,因为 ViewModel 引用了 View 中事物的特定实例;此外,这似乎是毫无意义的开销,而且对组织的好处很少。

对于这个问题,您将如何实施 MVVM 解决方案?或者这是使用事件处理程序更好的情况?

方法 1 - 事件处理程序: 此代码基于 Microsoft 提供的示例。 (这里是相关代码的链接: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>

方法 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; }
}

【问题讨论】:

    标签: c# mvvm uwp viewmodel xbind


    【解决方案1】:

    当您的模型属性更改时,您将需要 PropertyChangedNotification - 任何绑定到模型属性的内容都会自动更新。

    Bindings.Update() 有时也是你的朋友。

    如果您要拥有一个 View 和一个 ViewModel,您需要将 View 的 DataContext 设置为您要绑定的 ViewModel 或 Model 的实例。

    【讨论】:

    • 我认为这不能回答问题。诚然,这个例子并没有展示模型,但那是因为我认为它与我的问题无关。我相信使用 x:bind 并引用 View 的 ViewModel 实例使得设置 DataContext 变得不必要。有什么办法可以澄清我的问题吗?
    【解决方案2】:

    对于这个问题,您将如何实施 MVVM 解决方案?或者这是使用事件处理程序更好的情况?

    要实现 MVVM 导航,您可以参考 Template 10Template Studio 工作流程。

    在模板 10 中,它将Click 事件与导航方法绑定。

    <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>
    

    视图模型

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

    在 Template Studio 中,它使用 NavHelper 类导航。

    <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>
    

    视图模型

    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);
    }
    

    【讨论】:

    • 感谢您对模板 10 的引用。我认为这条路线对我来说很有希望。您知道它可能必须更新显示在 中的页面的控件吗?比如,有没有办法让Navigate() 只在一个框架内导航,而不是远离 MainPage?
    • 你的意思是你只想用this.Frame.Navigate(type page)
    • 不,那是我在上面的“方法2”中提出的“MVVM”解决方案。但唯一可行的方法是 ViewModel 知道 View 的框架。如上所述,在我的原始帖子中,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? 如果我要使用 Frame.Navigate() 调用不同控件框(例如 ListBox 或控件:PageHeader)上的事件,我不知道该怎么做没有 ViewModel 知道视图中的东西。这有意义吗?
    • 是的,ViewModels 不应该引用任何特定于 View 的东西是正确的。您可以使用 NavigationService 在您的视图模型中托管所有导航,就像模板 10 一样。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-27
    • 2015-04-17
    • 1970-01-01
    • 1970-01-01
    • 2011-11-04
    • 1970-01-01
    • 2012-01-29
    相关资源
    最近更新 更多