【问题标题】:WPF Navigate through views using MVVM patternWPF 使用 MVVM 模式浏览​​视图
【发布时间】:2014-05-12 15:45:44
【问题描述】:

我正在使用 MVVM 模式构建我的第一个 WPF。在这个社区的帮助下,我设法创建了我的模型、我的第一个 ViewModel 和视图。现在我想为设计基本应用程序布局界面的应用程序添加一些复杂性。我的想法是至少有 2 个子视图和一个主视图,并将它们分隔在几个 XAML 上:

  • Main.XAML
  • Products.XAML
  • Clients.XAML

Main 将有一个菜单和一个空间来加载子视图(产品和客户端)。现在遵循 MVVM 模式,视图之间的所有导航逻辑都应该写在 ViewModel 上。所以我的想法是有 4 个 ViewModel:

  • 主视图模型
  • ProductsViewModel
  • ClientsViewModel
  • 导航视图模型

所以 NavigationViewModel 应该包含子视图模型的集合?和一个活动的视图模型是对的吗?

所以我的问题是:

1) 如何使用 MVVM 模式在主视图上加载不同的视图(产品、​​客户端)?

2) 如何实现导航视图模型?

3) 如何控制打开或活动视图的最大数量?

4) 如何在打开的视图之间切换?

我一直在进行大量搜索和阅读,但找不到任何使用 WPF 在主视图中加载多个视图的 MVVM 导航的简单工作示例。很多时候:

1) 使用外部工具包,我现在不想使用。

2) 将用于创建所有视图的所有代码放在一个 XAML 文件中,这似乎不是一个好主意,因为我需要实现近 80 个视图!

我在这里走对了吗?任何帮助,尤其是一些代码,我们将不胜感激。

更新

所以,我按照@LordTakkera 的建议构建了一个测试项目,但被卡住了。这就是我的解决方案的样子:

我创造:

  • 两种模型(客户端和产品)

  • 一个 MainWindow 和两个 wpf 用户控件(客户端和产品)XAML。

  • 三个 ViewModel(客户端、产品和主 ViewModel)

然后我将每个视图上的 dataContext 设置为对应的 viewModel。之后,我像这样使用 ContentPresenter 创建 MainWindow 并将其绑定到 viewmodel 的属性。

MainWindow.XAML

<Window x:Class="PruevaMVVMNavNew.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="519" Width="890">    
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="150"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="80"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="20"/>
    </Grid.RowDefinitions>        
    <Border Grid.Column="0" Grid.ColumnSpan="2" Background="AntiqueWhite" ></Border>
    <Border Grid.Row="1" Grid.RowSpan="2" Background="AliceBlue"></Border>
    <Border Grid.Row="1" Grid.Column="1" Background="CadetBlue"></Border>                
    <ContentPresenter Grid.Row="1" Grid.Column="1" x:Name="ContentArea" Content="{Binding CurrentView}"/>        
    <StackPanel Margin="5" Grid.Column="0" Grid.Row="1">            
        <Button>Clients</Button>
        <Button>Products</Button>
    </StackPanel>
</Grid>

这也是来自 MainWindow 的视图模型:

class Main_ViewModel : BaseViewModel
    {
        public Main_ViewModel()
        {
            CurrentView = new Clients();
        }

        private UserControl _currentView;
        public UserControl CurrentView
        {
            get
            {
                return _currentView;
            }
            set
            {
                if (value != _currentView)
                {
                    _currentView = value;
                    OnPropertyChanged("CurrentView");
                }
            }
        }

    }

所以默认情况下加载的客户端视图看起来像这样(这恰到好处!):

所以我想我需要一种方法将左侧的按钮与某个 viemodel 相关联,然后将它们与 Main viewModel 的 CurrentView 属性绑定。我该怎么做?

更新2

根据@LordTakkera 的建议,我以这种方式修改我的主视图模型:

class Main_ViewModel : BaseViewModel
    {
        public ICommand SwitchViewsCommand { get; private set; }

        public Main_ViewModel()
        {
            //CurrentView = new Clients();
            SwitchViewsCommand = new RelayCommand((parameter) => CurrentView = (UserControl)Activator.CreateInstance(parameter as Type));
        }

        private UserControl _currentView;
        public UserControl CurrentView
        {
            get
            {
                return _currentView;
            }
            set
            {
                if (value != _currentView)
                {
                    _currentView = value;
                    OnPropertyChanged("CurrentView");
                }
            }
        }
    }

我使用 RelayCommand 而不是 DelegateCommand 但我认为它的工作方式相同。当我点击按钮并且类型参数字符串正常时执行该命令,但我收到此错误:

翻译:值不能为空。参数名称:类型。建议使用 New 关键字创建对象实例 我不知道在哪里放置 New 关键字。我尝试过 CommandParameter 但它不起作用。任何想法?谢谢

更新 3

在这里收到了所有的建议和帮助,以及大量的工作,这是我最终的导航菜单和我的应用程序界面的基础。

【问题讨论】:

  • Nice App Eric.I 是 wpf 的新手。我正在努力设计具有相同布局的应用程序。您能否发布代码。获得更多知识会非常有帮助。

标签: c# wpf xaml mvvm


【解决方案1】:

我不确定您是否需要单独的“导航”视图模型,您可以轻松地将其放入主视图模型中。无论哪种方式:

为了分隔您的“子”视图,我会在您的“主”视图上使用一个简单的 ContentPresenter:

<ContentPresenter Content="{Binding CurrentView}"/>

实现支持属性的最简单方法是将其设为UserControl,尽管有些人认为这样做违反了 MVVM(因为 ViewModel 现在依赖于“View”类)。你可以把它变成一个对象,但你会失去一些类型安全。在这种情况下,每个视图都是一个 UserControl。

要在它们之间切换,您将需要某种选择控制。我以前用单选按钮做过这个,你像这样绑定它们:

<RadioButton Content="View 1" IsChecked="{Binding Path=CurrentView, Converter={StaticResource InstanceEqualsConverter}, ConverterParameter={x:Type views:View1}"/>

转换器非常简单,在“Convert”中它只是检查当前控件是否是参数的一种类型,在“ConvertBack”中它返回参数的新实例。

public class InstanceEqualsConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return (parameter as Type).IsInstanceOfType(value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return (bool)value ? Activator.CreateInstance(parameter as Type) : Binding.DoNothing;
    }
}

绑定到组合框或其他选择控件将遵循类似的模式。

当然,您也可以使用 DataTemplates(带有选择器,遗憾的是我以前没有做过)并使用合并的字典(允许单独的 XAML)将它们加载到您的资源中。我个人更喜欢用户控制路由,选择最适合你的!

这种方法是“一次一个视图”。转换为多个视图会相对容易(您的 UserControl 成为用户控件的集合,在转换器中使用 .Contains 等)。

要使用按钮执行此操作,我会使用命令并利用 CommandParameter。

XAML 按钮如下所示:

<Button ... Command={Binding SwitchViewsCommand} CommandParameter={x:Type local:ClientsView}/>

然后你有一个委托命令(教程here)从转换器运行激活器代码:

public ICommand SwitchViewsCommand {get; private set;}

public MainViewModel()
{
    SwitchViewsCommand = new DelegateCommand((parameter) => CurrentView = Activator.CreateInstance(parameter as Type));
}

这不是我的想法,但应该非常接近。告诉我进展如何!

如果我提供任何进一步的信息,请告诉我!

更新:

回答您的疑虑:

  1. 是的,每次按下按钮都会创建一个新的视图实例。您可以通过持有具有预先创建的视图和索引的Dictionary&lt;Type, UserControl&gt; 轻松解决此问题。就此而言,您可以使用Dictonary&lt;String, UserControl&gt; 并使用简单的字符串作为转换器参数。缺点是您的 ViewModel 与它可以呈现的视图类型紧密耦合(因为它必须填充所述字典)。

  2. 只要没有其他人持有对它的引用(想想它注册的事件处理程序),该类就应该被释放。

  3. 正如您所指出的,一次只创建一个视图,因此您不必担心内存问题。当然,您是在调用构造函数,但这并不昂贵,尤其是在现代计算机上,我们往往有足够的 CPU 时间可用。与往常一样,性能问题的答案是“对其进行基准测试”,因为只有您才能访问预期的部署目标和整个源代码,才能查看实际性能最佳的内容。

【讨论】:

  • 谢谢!。我不太了解您发布的所有内容,但我不明白基本的想法是主要有一个 ContentPresenter (某种空框),然后我可以为活动视图加载外部 XAML。我将尝试用这个想法制作非常简单的示例代码并更新问题,以便您可以进一步帮助我。再次感谢您!
  • 几乎有了!!但出现错误。请查看我的更新。
  • 参数未正确通过。在调试器中,参数变量看起来是否正确(并且是“System.Type”)?如果不是,“as”转换返回 null,导致异常。
  • 好的。我的错误是我更改了您的 XAML,因为给了我错误。如果我使用 CommandParameter="{x:Type local:ClientsView}" 它将不起作用(找不到类型 local:ClientsView)
  • 我明白了!问题是我没有在 XAML 上包含对项目名称的引用(现在我不需要!)所以我将它添加到 mainWINdow XAML: xmlns:local="clr-namespace:TestMVVMNavNew" 现在工作完美!
【解决方案2】:

恕我直言,您最好的选择是使用 MVVM 框架(PRISM、MMVM Light、Chinch 等),因为导航已经实现。如果您想创建自己的导航 - 试试 DataTemplate。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多