目录

  • WPF 概述
  • MVVM 概述
  • 创建项目
  • 创建WPF应用基础项目
  • 安装MvvmLight插件
  • 开发步骤
  • 完善项目结构
  • 创建数据模型类
  • 模拟数据源
  • 页面布局和样式
  • 通过ViewModel连接视图和数据


WPF 概述

WPF(Windows Presentation Foundation)是微软推出的基于Windows 的用户界面框架,属于.NET Framework 3.0的一部分。它提供了统一的编程模型、语言和框架,真正做到了分离界面设计人员与开发人员的工作;同时它提供了全新的多媒体交互用户图形界面。

MVVM 概述

MVVM是Model-View-ViewModel的简写。即模型-视图-视图模型。分别定义如下:
【模型】模型是指代表真实状态内容的领域模型(面向对象),或指代表内容的数据访问层(以数据为中心)。
【视图】与MVC和MVP模式中一样相同,视图是用户在屏幕上看到的结构、布局和外观(UI)。
【视图模型】mvvm模式的核心,它是连接view和model的桥梁。它有两个方向:

  • 【模型】转化成【视图】,即将后端传递的数据体现到页面。实现的方式是:数据绑定。
  • 【视图】转化成【模型】,即将页面的数据反馈给后端。实现的方式是:DOM 事件监听。这两个方向都实现的,我们称之为数据的双向绑定。
    MVVM示意图如下所示:

创建项目

示例项目内容,使用选项卡(TabControl控件),修改选项卡默认样式,并通过选项卡页面进行表格内容展示。
注意: 示例项目中的部分变量名称供参考。

创建WPF应用基础项目

选择项目模板:WPF应用(.NET Framework),再点击下一步;

wpf TreeView做虚拟化 wpf中mvvm模式实例_wpf TreeView做虚拟化

配置新项目参数,.NET Framework示例默认4.7.2,并点击创建;

wpf TreeView做虚拟化 wpf中mvvm模式实例_xml_02

获得一个名为WpfApp的解决方案且包含一个名为WpfApp1的项目,项目结构如图。

wpf TreeView做虚拟化 wpf中mvvm模式实例_xml_03

安装MvvmLight插件

选中项目,右键或者顶部项目菜单–>管理NuGet程序包–>搜索MvvmLight–>安装;

wpf TreeView做虚拟化 wpf中mvvm模式实例_应用程序_04

确定–>选择“我接受”;

wpf TreeView做虚拟化 wpf中mvvm模式实例_c#_05

MvvmLight安装成功后,自动引用需要的第三方库,并生成默认内容,项目中会自动生成ViewModel文件夹,和MainViewModel.cs和ViewModelLocator.cs,不需要进行文件夹或文件名修改。

wpf TreeView做虚拟化 wpf中mvvm模式实例_wpf_06

修改ViewModelLocator.cs中的using Microsoft.Practices.ServiceLocation;using CommonServiceLocator;,不修改运行时会出现 error CS0234: 命名空间“Microsoft”中不存在类型或命名空间名“Practices”(是否缺少程序集引用?)

开发步骤

完善项目结构

除开项目和插件默认文件夹,我们创建一些文件夹以便进行代码或者资源文件的分类如图

  • Data 数据来源,比如静态数据或者从数据库获取的数据等等
  • Model 数据模型
  • View 页面或组件
  • Style 存放样式字典
  • Resource 资源文件,比如图标,把图标文件或文件夹直接拖入

涉及到文件创建的部分操作如下

以新增类为例,右键需要添加类的文件夹,添加–>类或者在上方的新建项中添加类。

wpf TreeView做虚拟化 wpf中mvvm模式实例_wpf_07

在新建项中查找需要添加的项。

wpf TreeView做虚拟化 wpf中mvvm模式实例_应用程序_08

创建数据模型类

Model–>DataModel.cs,创建一个本地数据模型

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfApp1.Model
{
    public class DataModel
    {
        /// <summary>
        /// 唯一标识
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// 内容
        /// </summary>
        public string Content { get; set; }
    }
}

模拟数据源

Data–>LocalData.cs,这里用代码内生成数据模拟数据的获取

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WpfApp1.Model;
using System.Timers;

namespace WpfApp1.Data
{
    public class LocalData
    {
        private List<DataModel> _DataModel;

        public LocalData()
        {
            init();
        }

        /// <summary>
        /// 初始化数据
        /// </summary>
        private void init()
        {
            _DataModel = new List<DataModel>();

            // 这里是用来测试生成的随机数
            for (int i = 0; i < 30; i++)
            {
                _DataModel.Add(new DataModel()
                {
                    Id = i,
                    Content = string.Format("第{0}类别描述", i),
                });
            }
        }

        /// <summary>
        /// 查询数据
        /// </summary>
        /// <returns></returns>
        public List<DataModel> Query()
        {
            return _DataModel;
        }
    }
}

页面布局和样式

MainWindow.xaml主页面–>从工具箱添加一个TabControl控件并进行排版

<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"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TabControl HorizontalAlignment="Left" Height="400" Margin="0,21,0,-0.333" VerticalAlignment="Top" Width="793">
            <TabItem Header="TabItem" Margin="21,66,-24.667,-67.667">
                <Grid Background="#FFE5E5E5" Margin="115,0,0.333,0"/>
            </TabItem>
            <TabItem Header="TabItem" Margin="-37.333,130,33.667,-132.667">
                <Grid Background="#FFE5E5E5" Margin="115,0,0.333,0"/>
            </TabItem>
        </TabControl>
    </Grid>
</Window>

wpf TreeView做虚拟化 wpf中mvvm模式实例_应用程序_09

主页面布局好了,我们对默认选项卡的按钮进行样式改造,在Style文件夹下创建资源字典(资源字典中的每个资源必须有一个唯一的键。在标记中定义资源时,通过x:key指令分配唯一的键,通常键是字符串),Style–>MainWindowStyleDictionary.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <!--这里设置MainWindow相关样式-->
    <!--图片资源,用到就放进来-->
    <ImageBrush x:Key="LeftCustomTab" ImageSource="pack://application:,,,../WpfApp1;component/Resource/Home.png"/>
    <ImageBrush x:Key="LeftCustomTabDown" ImageSource="pack://application:,,,../WpfApp1;component/Resource/Home按下.png"/>

    <!--TextBlock中的样式-->
    <Style x:Key="LeftTabFontSize" TargetType="{x:Type  TextBlock}">
        <Setter Property="FontSize" Value="9" />
    </Style>

    <!--TabItem按钮样式-->
    <Style TargetType="{x:Type  TabItem}" x:Key="CustomTab1">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TabItem}">
                    <Border BorderThickness="1" Background="#f0f0f0" Name="CustomTab1Bg">
                        <StackPanel HorizontalAlignment="Left" Height="38" Width="80">
                            <Border Width="0" Height="5" />
                            <Border Name="CustomTab1Ico" Width="16" Height="16" Background="{StaticResource LeftCustomTab}"/>
                            <TextBlock Name="CustomTab1Text" Text="维护" HorizontalAlignment="Center" Foreground="#000000" Style="{StaticResource LeftTabFontSize}" />
                        </StackPanel>
                    </Border>
                    <!--点击tabitem更改 -->
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsSelected" Value="False">
                            <!--点击之前的 -->
                            <Setter TargetName="CustomTab1Bg" Property="Background" Value="#f0f0f0" />
                            <Setter TargetName="CustomTab1Ico" Property="Background" Value="{StaticResource LeftCustomTab}" />
                        </Trigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsSelected" Value="True" />
                            </MultiTrigger.Conditions>
                            <!--点击之后的 -->
                            <Setter TargetName="CustomTab1Bg" Property="Background" Value="#c1c1c1" />
                            <Setter TargetName="CustomTab1Ico" Property="Background" Value="{StaticResource LeftCustomTabDown}" />
                        </MultiTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

在App.xaml中引用资源

<Application x:Class="WpfApp1.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApp1" StartupUri="MainWindow.xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d1p1:Ignorable="d" xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
  <Application.Resources>
    <ResourceDictionary>
      <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" xmlns:vm="clr-namespace:WpfApp1.ViewModel" />
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/WpfApp1;component/Style/MainWindowStyleDictionary.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
  </Application.Resources>
</Application>

样式字典设定后,为主页面TabItem引用Style修改如下

<TabItem Header="TabItem" Margin="21,66,-24.667,-67.667" Style="{StaticResource CustomTab1}">
	 <Grid Background="#FFE5E5E5" Margin="115,0,0.333,0"/>
</TabItem>

样式设置后,在View文件夹下添加用户控件,View–>UserControl1.xaml,这个用户控件就是用来填充到主页面的某个现象卡对应的选项页面的,所以长宽取TabControl中对应TabItem的Grid长宽值。

<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="370" d:DesignWidth="670">
    <Grid>
        <DataGrid HorizontalAlignment="Left" Height="370" VerticalScrollBarVisibility="Auto" AutoGenerateColumns="False" CanUserAddRows="False" CanUserSortColumns="False" Width="670">
            <DataGrid.Columns>
                <DataGridTextColumn Header="ID" Width="100"></DataGridTextColumn>
                <DataGridTextColumn Header="内容" Width="100"></DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</UserControl>

通过ViewModel连接视图和数据

ViewModel–>UserControl1VIewModel.cs

using System;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WpfApp1.Data;
using WpfApp1.Model;
using WpfApp1.View;
using System.Collections.ObjectModel;

namespace WpfApp1.ViewModel
{
    public class UserControl1VIewModel : ViewModelBase
    {
        #region 属性及构造函数
        public UserControl1VIewModel()
        {
            _LocalData = new LocalData();
            Query();
        }

        private LocalData _LocalData;

        private ObservableCollection<DataModel> _ModelDataList;

        public ObservableCollection<DataModel> ModelDataList
        {
            get { return _ModelDataList; }
            set
            {
                _ModelDataList = value;
                RaisePropertyChanged();
            }
        }
        #endregion

        // 展示内容
        public void Query()
        {
            List<DataModel> dataList;
            dataList = _LocalData.Query();
            ModelDataList = new ObservableCollection<DataModel>();
            dataList.ForEach((item) =>
            {
                ModelDataList.Add(item);
            });
        }
    }
}

写好了组件的再关联到MainViewModel.cs中

using GalaSoft.MvvmLight;
using System.Windows.Controls;
using WpfApp1.View;

namespace WpfApp1.ViewModel
{
    /// <summary>
    /// This class contains properties that the main View can data bind to.
    /// <para>
    /// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel.
    /// </para>
    /// <para>
    /// You can also use Blend to data bind with the tool's support.
    /// </para>
    /// <para>
    /// See http://www.galasoft.ch/mvvm
    /// </para>
    /// </summary>
    public class MainViewModel : ViewModelBase
    {
        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// </summary>
        public MainViewModel()
        {
            if (IsInDesignMode)
            {
                // Code runs in Blend --> create design time data.
            }
            else
            {
                // Code runs "for real"
            }
        }

        /// <summary>
        /// 用户控件1
        /// </summary>
        private UserControl _UserControl1 = new UserControl1();
        public UserControl UserControl1
        {
            get { return _UserControl1; }
            set { Set(ref _UserControl1, value); }
        }
    }
}

View、Model和ViewModel都已准备,此时修改View层(包含主页面和View文件夹下的内容)联系数据上下文
VIew–>UserControl1.xaml

<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"
             xmlns:viewmodel="clr-namespace:WpfApp1.ViewModel"
             mc:Ignorable="d" 
             d:DesignHeight="370" d:DesignWidth="670">
    <!--#region ViewModel绑定-->
    <UserControl.DataContext>
        <viewmodel:UserControl1VIewModel/>
    </UserControl.DataContext>
    <!--#endregion-->
    <Grid>
        <DataGrid ItemsSource="{Binding ModelDataList}" HorizontalAlignment="Left" Height="370" VerticalScrollBarVisibility="Auto" AutoGenerateColumns="False" CanUserAddRows="False" CanUserSortColumns="False" Width="670">
            <DataGrid.Columns>
                <DataGridTextColumn Header="ID" Width="100" Binding="{Binding Id}"></DataGridTextColumn>
                <DataGridTextColumn Header="内容" Width="100" Binding="{Binding Content}"></DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</UserControl>

MainWindow.xaml

<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"
        mc:Ignorable="d"
        DataContext="{Binding Source={StaticResource Locator},Path=Main}"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TabControl HorizontalAlignment="Left" Height="400" Margin="0,21,0,-0.333" VerticalAlignment="Top" Width="793">
            <TabItem Header="TabItem" Margin="21,66,-24.667,-67.667" Style="{StaticResource CustomTab1}">
                <Grid Background="#FFE5E5E5" Margin="115,0,0.333,0">
                    <ContentControl Content="{Binding UserControl1}"/>
                </Grid>
            </TabItem>
            <TabItem Header="TabItem" Margin="-37.333,130,33.667,-132.667">
                <Grid Background="#FFE5E5E5" Margin="115,0,0.333,0"/>
            </TabItem>
        </TabControl>
    </Grid>
</Window>

运行效果,第二个TabItem没有修改和挂载内容可以用来作实验和对比。

wpf TreeView做虚拟化 wpf中mvvm模式实例_xml_10


wpf TreeView做虚拟化 wpf中mvvm模式实例_c#_11