WPF MVVM模式的应用——室内监控可视化

需求

在物联网应用中,可视化端经常需要将实物信息详细的呈现到用户视野之中。在室内环境中,经常可见的设备空调和灯。本次课题主要以室内环境的温湿度和房间用能情况出发,实现室内温湿度和能耗信息的可视化。为了让可视化更加直观,我们需要完成的任务有:

1.用户操作“开始”、“停止”之后,模拟“采集器”实时采集。

2.将实时“采集”的信息第一时间呈现到界面上。

首先上效果:

 

环境

Windows 10

Visual Studio 2019

.Net Framework 4.5.2

 

设计

UI设计:

功能设计:

室内详情信息的展开和收起;

点击“开始”,开启定时器开始采集;

点击“停止”,暂停定时器。

通过MVVM模式中的属性、命令绑定实现以上功能。

 

实现

1.自定义ViewModel模型基类(属性更改通知)

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

namespace Deamon.ViewModel
{
    /// <summary>
    /// ViewModel模型基类(属性更改通知)
    /// </summary>
    public class BaseViewModel : INotifyPropertyChanged
    {
        /// <summary>
        /// 当任何子属性更改其值时激发的事件
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };

        /// <summary>
        /// 调用此函数以触发 <see cref="PropertyChanged"/> 事件
        /// </summary>
        /// <param name="name"></param>
        public void RaisePropertyChanged(string name)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }

        /// <summary>
        /// 更新属性,如果属性值没有发生变化,则不进行通知
        /// </summary>
        /// <typeparam name="T">属性类型</typeparam>
        /// <param name="properValue">返回的属性值</param>
        /// <param name="newValue">新的属性值</param>
        /// <param name="properName">属性名</param>
        protected void RaisePropertyChanged<T>(ref T properValue, T newValue, [CallerMemberName] string properName = "")
        {
            if (object.Equals(properValue, newValue))
                return;

            properValue = newValue;
            RaisePropertyChanged(properName);
        }

    }
}

2.自定义一个实现ICommand接口的基本命令执行操作

using System;
using System.Windows.Input;

namespace Deamon.ViewModel
{
    /// <summary>
    /// 一个基本命令执行传递的操作
    /// </summary>
    public class RelayCommand : ICommand
    {
        #region 私有成员变量

        /// <summary>
        /// 要运行的操作
        /// </summary>
        private Action mAction;

        #endregion

        #region 公共事件

        /// <summary>
        /// 当 <see cref="CanExecute(object)"/> 的值发生变化时触发该事件
        /// </summary>
        public event EventHandler CanExecuteChanged = (sender, e) => { };

        #endregion

        #region 构造函数

        /// <summary>
        /// 默认构造函数
        /// </summary>
        /// <param name="action">命令要运行的操作</param>
        public RelayCommand(Action action)
        {
            this.mAction = action;
        }

        #endregion 

        #region 公共处理方法

        /// <summary>
        /// 判断传递过来的命令是否可以执行
        /// 目前该方法表示始终可以执行
        /// </summary>
        /// <param name="parameter">命令执行传递的参数</param>
        /// <returns></returns>
        public bool CanExecute(object parameter)
        {
            return true;
        }

        /// <summary>
        /// 执行命令的操作
        /// </summary>
        /// <param name="parameter">命令执行传递的参数</param>
        public void Execute(object parameter)
        {
            mAction();
        }

        #endregion
    }
}

3.根据房间信息定义一个继承自BaseViewModel的VM模型

using System;
using System.Timers;

namespace Deamon.ViewModel
{
    /// <summary>
    /// 房间信息
    /// </summary>
    public class ChamberViewModel : BaseViewModel
    {

        #region 公共属性

        /// <summary>
        /// 温度
        /// </summary>
        private double temperature = 28.4;

        /// <summary>
        /// 温度
        /// </summary>
        public double Temperature
        {
            get { return temperature; }
            set
            {
                RaisePropertyChanged(ref temperature, value, nameof(Temperature));
            }
        }

        /// <summary>
        /// 湿度
        /// </summary>
        private double humidity = 64.5;

        /// <summary>
        /// 湿度
        /// </summary>
        public double Humidity
        {
            get { return humidity; }
            set
            {
                RaisePropertyChanged(ref humidity, value, nameof(Humidity));
            }
        }

        /// <summary>
        /// 电压
        /// </summary>
        private double voltage = 220.1;

        /// <summary>
        /// 电压
        /// </summary>
        public double Voltage
        {
            get { return voltage; }
            set
            {
                RaisePropertyChanged(ref voltage, value, nameof(Voltage));
            }
        }

        /// <summary>
        /// 电流
        /// </summary>
        private double current = 8;

        /// <summary>
        /// 电流
        /// </summary>
        public double Current
        {
            get { return current; }
            set
            {
                RaisePropertyChanged(ref current, value, nameof(Current));
            }
        }

        /// <summary>
        /// 电功率
        /// </summary>
        private double power = 164.5;

        /// <summary>
        /// 电功率
        /// </summary>
        public double Power
        {
            get { return power; }
            set
            {
                RaisePropertyChanged(ref power, value, nameof(Power));
            }
        }

        /// <summary>
        /// 电能
        /// </summary>
        private double energy = 1.3;

        /// <summary>
        /// 电能
        /// </summary>
        public double Energy
        {
            get { return energy; }
            set
            {
                RaisePropertyChanged(ref energy, value, nameof(Energy));
            }
        }

        #endregion

        #region 公共命令

        /// <summary>
        /// 开始命令
        /// </summary>
        public RelayCommand StartCommand
        {
            get
            {
                return new RelayCommand(() =>
                {
                    timer.Interval = 100;
                    timer.Start();
                });
            }
        }

        /// <summary>
        /// 停止命令
        /// </summary>
        public RelayCommand StopCommand
        {
            get
            {
                return new RelayCommand(() =>
                {
                    timer.Stop();
                });
            }
        }

        #endregion

        #region 本地服务

        Timer timer;

        Random rand;

        public ChamberViewModel()
        {
            timer = new Timer(100);
            timer.Elapsed += Timer_Elapsed;
            rand = new Random();
        }

        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            Temperature = Math.Round(rand.NextDouble() * 10 + 20, 2);
            Humidity = Math.Round(rand.NextDouble() * 20 + 50, 2);
            Voltage = Math.Round(rand.NextDouble() * 5 + 219, 2);
            Current = Math.Round(rand.NextDouble() * 5 + 5, 2);
            Power = Math.Round(rand.NextDouble() * 100 + 135, 2);
            Energy = Energy + Math.Round(rand.NextDouble() * 0.1, 2);

            timer.Interval = 1000;
        }

        #endregion

    }
}

4.设计XAML实现UI,文本直接绑定VM模型中的属性和命令

<Page x:Class="Deamon.View.ChamberView"
      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:Deamon.View"
      Title="ChamberView">

    <Grid>

        <Grid >
            <Viewbox>
                <Image Source="..\Images\bg.jpg"/>
            </Viewbox>
        </Grid>

        <Grid >

            <Expander Background="#DD000000" IsExpanded="True" ExpandDirection="Left" HorizontalAlignment="Right">
                <Expander.Header>
                    <StackPanel >
                        <TextBlock Foreground="White" Text="室"/>
                        <TextBlock Foreground="White" Text="内"/>
                        <TextBlock Foreground="White" Text="详"/>
                        <TextBlock Foreground="White" Text="情"/>
                    </StackPanel>
                </Expander.Header>
                <Border Padding="10" HorizontalAlignment="Center" >
                    <StackPanel>
                        <Rectangle Height="1" Fill="YellowGreen" Margin="0 5"/>
                        <WrapPanel>
                            <TextBlock Text="温度:" Foreground="AliceBlue"/>
                            <TextBlock Text="{Binding Temperature}" TextAlignment="Right" Padding="0 0 5 0" Width="60" Foreground="Lime"/>
                            <TextBlock Text="℃" Foreground="AliceBlue"/>
                        </WrapPanel>
                        <WrapPanel>
                            <TextBlock Text="湿度:" Foreground="AliceBlue"/>
                            <TextBlock Text="{Binding Humidity}" TextAlignment="Right" Padding="0 0 5 0" Width="60" Foreground="Lime"/>
                            <TextBlock Text="%" Foreground="AliceBlue"/>
                        </WrapPanel>
                        <Rectangle Height="1" Fill="YellowGreen" Margin="0 5"/>
                        <WrapPanel>
                            <TextBlock Text="电压:" Foreground="AliceBlue"/>
                            <TextBlock Text="{Binding Voltage}" TextAlignment="Right" Padding="0 0 5 0" Width="60" Foreground="Lime"/>
                            <TextBlock Text="V" Foreground="AliceBlue"/>
                        </WrapPanel>
                        <WrapPanel>
                            <TextBlock Text="电流:" Foreground="AliceBlue"/>
                            <TextBlock Text="{Binding Current}" TextAlignment="Right" Padding="0 0 5 0" Width="60" Foreground="Lime"/>
                            <TextBlock Text="A" Foreground="AliceBlue"/>
                        </WrapPanel>
                        <WrapPanel>
                            <TextBlock Text="功率:" Foreground="AliceBlue"/>
                            <TextBlock Text="{Binding Power}" TextAlignment="Right" Padding="0 0 5 0" Width="60" Foreground="Lime"/>
                            <TextBlock Text="W" Foreground="AliceBlue"/>
                        </WrapPanel>
                        <Rectangle Height="1" Fill="YellowGreen" Margin="0 5"/>
                        <WrapPanel>
                            <TextBlock Text="电能:" Foreground="AliceBlue"/>
                            <TextBlock Text="{Binding Energy}" TextAlignment="Right" Padding="0 0 5 0" Width="60" Foreground="Lime"/>
                            <TextBlock Text="kW·h" Foreground="AliceBlue"/>
                        </WrapPanel>
                        <Rectangle Height="1" Fill="YellowGreen" Margin="0 5"/>

                        <WrapPanel Margin="0 50 0 0" HorizontalAlignment="Center">
                            <Button Command="{Binding StartCommand}" Content="开始" Width="50" Height="30"/>
                            <Button Command="{Binding StopCommand}" Content="停止" Width="50" Height="30"/>
                        </WrapPanel>
                    </StackPanel>
                </Border>
            </Expander>
            
        </Grid>

    </Grid>
</Page>

5.DataContext数据上下文赋值

using Deamon.ViewModel;
using System.Windows.Controls;

namespace Deamon.View
{
    /// <summary>
    /// ChamberView.xaml 的交互逻辑
    /// </summary>
    public partial class ChamberView : Page
    {
        public ChamberView()
        {
            InitializeComponent();
            DataContext = new ChamberViewModel();
        }
    }
}

 

划重点

MVVM中分别是:Model、View、ViewModel,其中View是界面(UI),ViewModel是界面模型(Model For View),View和ViewModel之间的沟通一般通过两种方式:传递数据(属性)和传递操作(命令)。绑定的数据源一般情况下会设置到DataContext上面,如本文中:DataContext = new ChamberViewModel();

属性绑定(双向):

可以实现更改通知的属性是需要继承INotifyPropertyChanged接口的属性,同时需要在属性发生更改时(也就是在set中),手动的触发该事件PropertyChanged(this, new PropertyChangedEventArgs(name));.本文中自定义了一个RaisePropertyChanged方法,继承BaseViewModel基类的VIewModel可以直接调用RaisePropertyChanged实现属性通知。其中重载方法可以更方便的在属性的set中使用。

当ViewModel中的属性发送变化时,Binding会通知View刷新;同样,我们把绑定的Mode设置为TwoWay等方式时,当View中绑定的属性发生变化时,也会通知ViewModel中属性。

命令绑定(单向):

命令是单向传递的,即只能通过View传递给ViewModel一个动作(Action,后台执行的方法)。使用时,是将ViewModel继承ICommand接口类型(本文中的RelayCommand)定义的命令属性绑定到View中元素的Command属性值。但并不是所有的元素都具备Command属性,没有的我们需要通过使用System.Windows.Interactivity.dll进行扩展。

 

Over

每次记录一小步...点点滴滴人生路...

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值