ContentControl的Content绑定View还是ViewModel

一、背景

在WPF中有一种控件叫做ContenControl,其有一个object类型的依赖属性Content。我们常规做法是将UserControl赋值给Content,但是这么做两个问题。

1)一般我们会将Content绑定到ViewModel层的数据,如果该数据为UserControl,那么在ViewModel层中就出现了View层。

2)一般使用Content绑定UserControl的时候,都是用与动态切换的场景,如果我们有比较复杂的UserControl,那么在每一次切换界面的时候都会进行界面的销毁和重新渲染,影响性能。

上述两个问题有解决办法吗?如果我们将ViewModel绑定到Content就可以解决这个问题。

二、实现

那么问题又出现了,如果我们将Content绑定ViewModel,该如何与ViewModel对应的View进行关联呢?

其实在WPF中有一个叫资源的东西,如果我们将数据模板存放在资源中,并且给数据模板指定控件模板,那么我们将Content设置为一个数据模板的时候,其实这个数据模板已经在资源中提前绑定到控件模板上了,所以在界面显示的就是我们的控件模板。

直接上代码:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        xmlns:viewModel="clr-namespace:WpfApp1.ViewModel"
        mc:Ignorable="d"
        x:Name="mainWindow"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <viewModel:MainViewModel Window="{x:Reference mainWindow}"/>
    </Window.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <ComboBox Grid.Column="0" VerticalAlignment="Top" Margin="10" SelectedIndex="{Binding SelectedIndex}">
            <ComboBoxItem Content="Page1" IsSelected="True"/>
            <ComboBoxItem Content="Page2"/>
            <ComboBoxItem Content="Page3"/>
        </ComboBox>

        <ContentControl Grid.Column="1" Content="{Binding ViewModelContent}"/>
    </Grid>
</Window>
public class MainViewModel : BaseModel
    {
        public ResourceDictionary Resources { get; set; } = new ResourceDictionary();

        private int selectedIndex;
        public int SelectedIndex
        {
            get { return selectedIndex; }
            set
            {
                selectedIndex = value;
                OnPropertyChanged();
                SetViewModelIndex(value);
            }
        }

        private object viewModelContent;
        public object ViewModelContent
        {
            get{ return viewModelContent; }
            set 
            {
                viewModelContent = value;
                OnPropertyChanged();
            }
        }


        private FrameworkElement window;
        public FrameworkElement Window
        {
            get { return window; }
            set 
            {
                window = value;
                window.Resources = this.Resources;
            }
        }


        private void SetViewModelIndex(int index)
        {
            if (index >= 0 && index < this.models.Count)
                ViewModelContent = this.models[index];
        }


        public MainViewModel()
        {
            this.SetView(new ViewModel1(), typeof(UserControl1));
            this.SetView(new ViewModel2(), typeof(UserControl2));
            this.SetView(new ViewModel3(), typeof(UserControl3));

            SetViewModelIndex(0);
        }

        private List<object> models = new List<object>();
        private void SetView(object viewModel, Type type)
        {
            DataTemplateKey key = new DataTemplateKey(viewModel.GetType());
            DataTemplate template = new DataTemplate(viewModel.GetType());
            template.VisualTree = new FrameworkElementFactory(type);
            this.Resources.Add(key, template);
            this.models.Add(viewModel);
        }
    }

上面SetView方法和下面的效果是一致的,,,并且下面的写法感觉更方便。。。

<Window
    x:Class="MVVMLight.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:MVVMLight"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:view="clr-namespace:MVVMLight.View"
    xmlns:viewModel="clr-namespace:MVVMLight.ViewModel"
    Title="MainWindow"
    mc:Ignorable="d">
    <Window.Resources>
        <DataTemplate DataType="{x:Type viewModel:Page1ViewModel}">
            <view:Page1View />
        </DataTemplate>

        <DataTemplate DataType="{x:Type viewModel:Page2ViewModel}">
            <view:Page2View />
        </DataTemplate>
    </Window.Resources>
</Window>

因为上述ViewModel都是类似的,所以只展示其中一个的代码:

public class Model1
{
     public string FirstName { get; set; }

     public string LastName { get; set; }
}

public class ViewModel1
{
     public List<Model1> model1s { get; set; } = new List<Model1>();
     public ViewModel1()
     {
         model1s.Add(new Model1 { LastName = "杨", FirstName = "超越" });
         model1s.Add(new Model1 { LastName = "赵", FirstName = "丽颖" });
         model1s.Add(new Model1 { LastName = "刘", FirstName = "诗诗" });
         model1s.Add(new Model1 { LastName = "高", FirstName = "圆圆" });
     }
}
<UserControl x:Class="WpfApp1.View.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp1.View"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <ListBox ItemsSource="{Binding model1s}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding LastName}"/>
                        <TextBlock Text="{Binding FirstName}" Margin="10 0 0 0"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</UserControl>

三、效果

在这里插入图片描述

四、总结

上述方法在每一次切换ViewModel的时候,都会去找它对应的View,也就意味着每一次的都会重新实例化一个View对象。因为ViewModel对象没有变化并且绑定到了View,所以View层也好像没有改变一样,但其实是重新实例化了一个对象。这里感觉有点依赖注入的意思,现在我们期望管理依赖对象的生命周期,如果没有办法做到这一点,这种方式应该也不是很友好。。。。。除非你做到了真正意义的MVVM,在View层并没有多余的信息,不然这样使用是有问题的

  1. 每次切换都会重新实例化View层,消耗资源
  2. 如果View层有数据,那么每一次都会复原,不会像在ViewModel层中的数据一样复原到新实例化的View层
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值