需求

为了实现一个完整软件系统,必须具备一些基本的数据呈现控件,例如曲线图、柱状图、饼图等。本次的业务需求为:利用LiveCharts展示后台模拟的温度变化。像Winform里面,微软为我们提供了比较完整的Chart控件,但是在WPF组件中,就没有找到类似的控件,它的意图是让我们自己去实现。我们应该对当下的代码共享时代环抱感激,迄今位置有很多面向WPF的第三方控件库,大部分都是免费开源的。例如:OxyPlot、ModernuiCharts......以及我们今天的主角——LiveCharts。这是一个具备动画效果的图表控件。

首先上效果:

 

环境

Windows 10

Visual Studio 2019

.Net Framework 4.7.2

Ninject

LiveCharts

LiveCharts.Wpf

 

实现

1.通过NuGet引入Ninject、LiveCharts.Wpf、LiveCharts程序包。

解决方案资源管理器-->项目(右键)-->管理NuGet程序包

在“浏览”中搜索:

Ninject

LiveCharts

LiveCharts.Wpf

并安装,完成之后如下图所示:

 

2.在XAML中引用LiveCharts控件的程序集。

在ChamberView.xaml文件的节点上添加对程序集的应用,如下:

<local:AnimationPageBaseView 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"
      xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
      Title="ChamberView">

其中 xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"就是命名空间的引用。

3.使用LiveCharts控件,值用绑定的方式传递到界面。

                    <lvc:CartesianChart Grid.Row="1" AnimationsSpeed="0:0:0.2" Hoverable="False" DataTooltip="{x:Null}">
                        <lvc:CartesianChart.Series>
                            <lvc:LineSeries Values="{Binding ChartValues}" 
                                    PointGeometry="{x:Null}"
                                LineSmoothness="0"
                                StrokeThickness="3" 
                                Stroke="#12B1AD"
                                Fill="#E6F7F8"/>
                        </lvc:CartesianChart.Series>
                        <lvc:CartesianChart.AxisX>
                            <lvc:Axis LabelFormatter="{Binding DateTimeFormatter}" 
                          MaxValue="{Binding AxisMax}" 
                          MinValue="{Binding AxisMin}" ShowLabels="False"
                          Unit="{Binding AxisUnit}">
                                <lvc:Axis.Separator>
                                    <lvc:Separator Step="{Binding AxisStep}" />
                                </lvc:Axis.Separator>
                            </lvc:Axis>
                        </lvc:CartesianChart.AxisX>
                        <lvc:CartesianChart.AxisY>
                            <lvc:Axis ShowLabels="false"  />
                        </lvc:CartesianChart.AxisY>
                    </lvc:CartesianChart>

如上所示,我们这里有一些属性是通过绑定来赋值的。因此接下来我们来完善这块工作。

4.数据绑定与刷新

4.1定义一个数据点结构——MeasureModel

    /// <summary>
    /// 测量模型
    /// </summary>
    public class MeasureModel
    {
        /// <summary>
        /// X 轴数据
        /// </summary>
        public DateTime DateTime { get; set; }

        /// <summary>
        /// Y 轴数据
        /// </summary>
        public double Value { get; set; }
    }

4.2定义界面相关的视图模型ViewModel

定义通知属性、普通属性和命令以及构造时初始化等工作。

using Deamon.Util.DI;
using LiveCharts;
using LiveCharts.Configurations;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Deamon.ViewModel
{    
    /// <summary>
    /// 房间信息
    /// </summary>
    public class ChamberViewModel : BaseViewModel
    {
        /// <summary>
        /// 默认构造函数
        /// </summary>
        public ChamberViewModel()
        {
            // 初始化创建一个 X Y 轴上数据显示的图表,并确定 X Y 轴的数据结构
            var mapper = Mappers.Xy<MeasureModel>()
              .X(model => model.DateTime.Ticks) 
              .Y(model => model.Value);

            // 配置这个图表,可以被其他地方(特定的方式)使用
            Charting.For<MeasureModel>(mapper);

            // 初始化测量的数据集
            ChartValues = new ChartValues<MeasureModel>();

            // 设置 X 轴显示的标签格式
            DateTimeFormatter = value => new DateTime((long)value).ToString("mm:ss");

            // 设置图表的其他相关属性
            AxisStep = TimeSpan.FromSeconds(1).Ticks;
            AxisUnit = TimeSpan.TicksPerSecond;

            SetAxisLimits(DateTime.Now);

            // 默认情况下开始数据刷新
            IsReading = false;
            this.InjectStopOnClick();
        }

        #region 公共命令

        /// <summary>
        /// 显示导航栏命令
        /// </summary>
        public RelayCommand ShowBarCommand
        {
            get
            {
                return new RelayCommand(() =>
                {
                    IoC.Get<ApplicationViewModel>().HasNavigationBar = true;
                });
            }
        }

        /// <summary>
        /// 显示导航栏命令
        /// </summary>
        public RelayCommand ReadingCommand
        {
            get
            {
                return new RelayCommand(() =>
                {
                    this.InjectStopOnClick();
                    RaisePropertyChanged(nameof(ReadingCommandText));
                });
            }
        }

        #endregion

        #region 属性

        /// <summary>
        /// 缓存的测量数据
        /// </summary>
        public ChartValues<MeasureModel> ChartValues { get; set; }

        /// <summary>
        /// 时间格式化器
        /// </summary>
        public Func<double, string> DateTimeFormatter { get; set; }

        /// <summary>
        /// X 轴上每个数据的等距跳变(时)长
        /// </summary>
        public double AxisStep { get; set; }
        public double AxisUnit { get; set; }

        /// <summary>
        /// 正在读取
        /// </summary>
        public bool IsReading { get; set; }

        /// <summary>
        /// 控制按钮的文本
        /// </summary>
        public string ReadingCommandText
        {
            get
            {
                return IsReading ? "Stop" : "Start";
            }
        }

        private string curvalue;

        /// <summary>
        /// 当前值
        /// </summary>
        public string Curvalue
        {
            get { return curvalue; }
            set
            {
                curvalue = value;
                RaisePropertyChanged(nameof(Curvalue));
            }
        }

        private double axisMax;

        /// <summary>
        /// X轴最大
        /// </summary>
        public double AxisMax
        {
            get { return axisMax; }
            set
            {
                axisMax = value;
                RaisePropertyChanged(nameof(AxisMax));
            }
        }

        private double axisMin;

        /// <summary>
        /// X轴最小
        /// </summary>
        public double AxisMin
        {
            get { return axisMin; }
            set
            {
                axisMin = value;
                RaisePropertyChanged(nameof(AxisMin));
            }
        }

        #endregion

        /// <summary>
        /// 模拟数据采集
        /// </summary>
        public void Read()
        {
            while (IsReading)
            {
                Thread.Sleep(400);
                var now = DateTime.Now;
                double value = Math.Round(new Random().Next(10, 11) + (1 * new Random().NextDouble()), 2);
                ChartValues.Add(new MeasureModel
                {
                    DateTime = now,
                    Value = value
                });
                SetAxisLimits(now);
                Curvalue = value + "℃";
                // 只保留160个数据,满了就把前面的数据移除
                if (ChartValues.Count > 160) ChartValues.RemoveAt(0);
            }
            IsReading = false;
        }

        /// <summary>
        /// 设置 X 轴上呈现的时间范围
        /// </summary>
        /// <param name="now"></param>
        private void SetAxisLimits(DateTime now)
        {
            // X轴显示区域的最大时间
            AxisMax = now.Ticks + TimeSpan.FromSeconds(1).Ticks;
            // X轴显示区域的最大时间
            AxisMin = now.Ticks - TimeSpan.FromSeconds(8).Ticks; 
        }

        /// <summary>
        /// 根据标识是否启动采集
        /// </summary>
        public void InjectStopOnClick()
        {
            IsReading = !IsReading;
            if (IsReading)
            {
                Task.Factory.StartNew(Read);
            }
        }

    }
}

4.3数据绑定和界面完善

<local:AnimationPageBaseView x:Class="Deamon.View.ChamberView"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="clr-namespace:Deamon.View"
      xmlns:vm="clr-namespace:Deamon.ViewModel"
      xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
      DataContext="{Binding ChamberVM, Source={ x:Static vm:ViewModelLocator.Locator}}"
      Title="ChamberView">
    <Grid Background="#FF033F42">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal" Margin="10 0 0 0">
            <Button  VerticalAlignment="Center" 
                     Style="{StaticResource OnlyTextButton}" 
                     Command="{Binding ReadingCommand}" 
                     Content="{Binding ReadingCommandText}"/>
        </StackPanel>
        
        <DockPanel Grid.Row="1">
            <Grid Width="300" Height="200">
                <Border CornerRadius="5" Background="White"/>
                <Grid  Margin="5">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="60"/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <StackPanel Margin="5 0 0 0"  VerticalAlignment="Center">
                        <TextBlock Text="NO.607" FontSize="22" VerticalAlignment="Center" Foreground="#6C757D"/>
                        <TextBlock Text="Temperature:" FontSize="15"  Foreground="#6C757D"/>
                    </StackPanel>
                    <TextBlock Text="{Binding Curvalue}" Margin="10 5 0 0" Grid.Row="1" FontFamily="Consolas" FontSize="22" Foreground="#6C757D" FontWeight="Bold" />
                    <lvc:CartesianChart Grid.Row="1" AnimationsSpeed="0:0:0.2" Hoverable="False" DataTooltip="{x:Null}">
                        <lvc:CartesianChart.Series>
                            <lvc:LineSeries Values="{Binding ChartValues}" 
                                    PointGeometry="{x:Null}"
                                LineSmoothness="0"
                                StrokeThickness="3" 
                                Stroke="#12B1AD"
                                Fill="#E6F7F8"/>
                        </lvc:CartesianChart.Series>
                        <lvc:CartesianChart.AxisX>
                            <lvc:Axis LabelFormatter="{Binding DateTimeFormatter}" 
                          MaxValue="{Binding AxisMax}" 
                          MinValue="{Binding AxisMin}" ShowLabels="False"
                          Unit="{Binding AxisUnit}">
                                <lvc:Axis.Separator>
                                    <lvc:Separator Step="{Binding AxisStep}" />
                                </lvc:Axis.Separator>
                            </lvc:Axis>
                        </lvc:CartesianChart.AxisX>
                        <lvc:CartesianChart.AxisY>
                            <lvc:Axis ShowLabels="True"  />
                        </lvc:CartesianChart.AxisY>
                    </lvc:CartesianChart>
                </Grid>
            </Grid>
            
        </DockPanel>
       
    </Grid>
</local:AnimationPageBaseView>

 

Over

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

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐