MVVM分层导航教程StackOverFlow异常

时间:2017-04-02 07:56:52

标签: c# wpf xaml mvvm uwp

我试图重现WPF的MVVM教程,但将其应用于UWP。但是我已经完成了教程中的所有内容,我相信教程中显示的完全相同的代码。

但是当我运行代码时,我不断得到一个StackOverflowException,这是因为MainPageView一次又一次地进行初始化,直到抛出异常为止。

我对MVVM有点了解并希望掌握它,所以有人可以解释一下我为什么会这样做?

我会留下每个班级和观点的代码。

这是我的MainPageView.Xaml:

<Page
x:Class="MVVMHierarchiesDemo.MainPageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVMHierarchiesDemo"
xmlns:views="using:MVVMHierarchiesDemo.Views"
xmlns:viewmodel="using:MVVMHierarchiesDemo.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<!--Anytime the current view model is set to an instance of a CustomerListViewModel,
it will render out a CustomerListView with the ViewModel is hooked up. It’s an order ViewModel,
it'll render out OrderView and so on.

We now need a ViewModel that has a CurrentViewModel property and some logic and commanding
to be able to switch the current reference of ViewModel inside the property.-->
<Page.DataContext>
    <local:MainPageView/>
</Page.DataContext>

<Page.Resources>
    <DataTemplate x:Key="CustomerTemplate" x:DataType="viewmodel:CustomerListViewModel">
        <views:CustomerListView/>
    </DataTemplate>

    <DataTemplate x:Key="OrderTemplate" x:DataType="viewmodel:OrderViewModel">
        <views:OrderView/>
    </DataTemplate>
</Page.Resources>

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <Grid x:Name="NavBar"
          Grid.Row="0">
        <Button Content="Customers"
                Command="{Binding NavCommand}"
                CommandParameter="customers"
                Grid.Column="0"
                Grid.Row="0"/>

        <Button Content="Orders"
                Command="{Binding NavCommand}"
                CommandParameter="orders"
                Grid.Column="2"
                Grid.Row="0"/>
    </Grid>

    <Grid x:Name="MainContent"
          Grid.Row="1">
        <ContentControl Content="{Binding CurrentViewModel}"/>
    </Grid>      
</Grid>
</Page>

这是我的代码隐藏MainPageView.xaml.cs - 这里是StackoverflowException在它一直调用它的构造函数中抛出的地方。

using Windows.UI.Xaml.Controls;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409

namespace MVVMHierarchiesDemo
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPageView : Page
    {
        public MainPageView()
        {
            this.InitializeComponent();
        }        
    }
}

这是我的BindableBase.cs,如教程所示:

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace MVVMHierarchiesDemo
{
    /*The main idea behind this class is to encapsulate the INotifyPropertyChanged implementation
     * and provide helper methods to the derived class so that they can easily trigger the appropriate notifications.
     * Following is the implementation of BindableBase class.*/
    public class BindableBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName]string propertyName = null)
        {
            if (object.Equals(member, val))
                return;

            member = val;
            OnPropertyChanged(propertyName);
        }
    }
}

这是MyCommand.cs或更好地称为中继命令模式:

using System;
using System.Windows.Input;

namespace MVVMHierarchiesDemo
{
    /* Now it's time to actually start doing some view switching using our CurrentViewModel property.
     * We just need some way to drive the setting of this property. And we're going to make it so that
     * the end user can command going to the customer list or to the order view. First add a new class
     * in your project which will implement the ICommand interface. Following is the implementation of
     * ICommand interface.*/
    public class MyCommand<T> : ICommand
    {
        Action<T> _TargetExecuteMethod;
        Func<T, bool> _TargetCanExecuteMethod;

        public MyCommand(Action<T> targetExecuteMethod)
        {
            _TargetExecuteMethod = targetExecuteMethod;
        }

        public MyCommand(Action<T> targetExecuteMethod, Func<T,bool> targetCanExecuteMethod)
        {
            _TargetExecuteMethod = targetExecuteMethod;
            _TargetCanExecuteMethod = targetCanExecuteMethod;
        }

        public event EventHandler CanExecuteChanged = delegate { };

        public void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }

        bool ICommand.CanExecute(object parameter)
        {
            if (_TargetCanExecuteMethod != null)
            {
                T tparam = (T)parameter;
                return _TargetCanExecuteMethod(tparam);
            }

            if (_TargetExecuteMethod != null)
                return true;

            return false;
        }

        void ICommand.Execute(object parameter)
        {
            if(_TargetExecuteMethod!=null)
            {
                T tparam = (T)parameter;
                _TargetExecuteMethod(tparam);
            }
        }
    }
}

这是我对OrdersView.xaml的用户控件:

<UserControl
    x:Class="MVVMHierarchiesDemo.Views.OrderView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MVVMHierarchiesDemo.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">

    <Grid>
        <TextBlock Text="Order View"/>
    </Grid>
</UserControl>

这是我的用户控件CustomerListView.xaml:

<UserControl
    x:Class="MVVMHierarchiesDemo.Views.CustomerListView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MVVMHierarchiesDemo.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">

    <Grid>
        <TextBlock Text="Customer List View"/>
    </Grid>
</UserControl>

这是我的OrderViewModel:

namespace MVVMHierarchiesDemo.ViewModel
{
    /*Derive all of your ViewModels from BindableBase class.*/
    public class OrderViewModel : BindableBase
    {
    }
}

这是我的CustomerViewModel:

namespace MVVMHierarchiesDemo.ViewModel
{
    /*Derive all of your ViewModels from BindableBase class.*/
    public class CustomerListViewModel : BindableBase
    {
    }
}

最后这是我的MainPageViewModel:

namespace MVVMHierarchiesDemo.ViewModel
{
    /*Derive all of your ViewModels from BindableBase class.*/
    public class MainPageViewModel : BindableBase
    {
        public MainPageViewModel()
        {
            NavCommand = new MyCommand<string>(OnNavigation);
        }

        private CustomerListViewModel _customerListViewModel = new CustomerListViewModel();

        private OrderViewModel _orderViewModel = new OrderViewModel();

        private BindableBase _currentViewModel;

        public BindableBase CurrentViewModel
        {
            get
            {
                return _currentViewModel;
            }

            set
            {
                SetProperty(ref _currentViewModel, value);
            }
        }

        public MyCommand<string> NavCommand { get; private set; }

        private void OnNavigation(string destination)
        {
            switch (destination)
            {
                case "orders":
                    {
                        CurrentViewModel = _orderViewModel;
                        break;
                    }
                case "customers":
                default:
                    CurrentViewModel = _customerListViewModel;
                    break;
            }
        }
    }
}

最后我认为MainPageView导致无限循环,但我不明白为什么?

如果有人能够如此友善地告诉我我在UWP上做错了什么?

我也可以使用MVVM Light或MVVMCross我对那些我想手动学习MVVM的解决方案不感兴趣,后来我可能会检查这些框架。

1 个答案:

答案 0 :(得分:3)

这是因为在MainPageView.xaml你有这个:

<Page.DataContext>
    <local:MainPageView/>
</Page.DataContext>

因此,每个MainPageview都会创建一个嵌套的MainPageView作为其DataContext。这些是在你炸掉堆栈之前创建的。

我认为你打算在这里放一个MainPageViewModel