需求
在物联网应用中,可视化端经常需要将实物信息详细的呈现到用户视野之中。在室内环境中,经常可见的设备空调和灯。本次课题主要以室内环境的温湿度和房间用能情况出发,实现室内温湿度和能耗信息的可视化。为了让可视化更加直观,我们需要完成的任务有:
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
每次记录一小步...点点滴滴人生路...