nuget包

1.LiveChartsCore.SkiaSharpView.WPF

基于 LiveChartsCoreSkiaSharp 构建。它可以帮助开发者在 WPF 应用程序中创建漂亮、交互式的图表,支持多种类型的图表和丰富的自定义选项。

  • 1-安装LiveChartsCore.SkiaSharpView.WPF包 2-在xaml中添加

xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF"

2.CommunityToolkit.Mvvm

由 .NET Community Toolkit 提供的 NuGet 包,它专为 .NET 应用程序(尤其是 WPF、WinUI、Xamarin 等)提供了一个轻量级的 MVVM(Model-View-ViewModel)实现工具集。

1-安装CommunityToolkit.Mvvm包 2-添加ObservableProperty 特性,简化属性通知机制,避免了手动实现 INotifyPropertyChanged

ObservablePropertyCommunityToolkit.Mvvm 提供的特性,它会自动为属性实现 INotifyPropertyChanged 接口。这意味着,当这些属性的值发生变化时,UI 会自动更新。

字段通常是小驼峰命名法(例如 _data_myData),比如,字段 _data 会生成一个公开属性 Data

窗口大小

WindowState="Maximized" 该属性用于控制窗口的状态。WindowState 可以有以下几种值:

Normal:常规大小 Maximized:窗口被最大化到屏幕的最大大小。 Minimized:窗口被最小化。

提高显示的清晰度和准确度

SnapsToDevicePixels="True"控制窗口或元素的绘制精度的属性。当设置为 True 时,WPF 会确保控件的边界和像素点对齐,以避免出现模糊的效果,特别是在高分辨率显示器或高DPI环境中。

控制图像适应容器

<Image Grid.ColumnSpan="3" Grid.RowSpan="3" Source="/图片/上下框.png" Stretch="Fill" />

Grid.ColumnSpan="3" Grid.RowSpan="3" 图片会占用 3 列和 3 行的空间。

stretch 属性控制图像如何适应其容器的大小。

None:图片保持原始大小,不进行缩放。

Uniform:图片保持纵横比进行缩放,直到图片的长边与容器对齐。

UniformToFill:图片保持纵横比缩放,直到图片的短边与容器对齐,可能会裁剪图片的部分。

Fill:图片会拉伸以填充整个容器,可能会失去纵横比,图片会被拉伸或压缩以适应容器。

TextBlock 设置

<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding 实时信息.当前时间, StringFormat=\{0:yyyy-MM-dd HH:mm:ss\}}" FontSize="20" Foreground="Black"/>

渐变色背景

RadialGradientBrush 作为背景,GradientOrigin:指定渐变的起点。Center:指定渐变的中心点。在这里,0.5, 0 表示渐变从窗口顶部的中间开始。RadiusXRadiusY:控制渐变的扩展范围。RadiusXRadiusY 的值控制渐变的水平和垂直范围,值 0.7 表示渐变将从中心向外扩展 70%。

GradientStop:定义渐变中的颜色和其位置。第一个 GradientStop 颜色位于渐变的起始位置,第二个 GradientStop 颜色位于渐变的终点位置。

<Window.Background>
    <RadialGradientBrush GradientOrigin="0.5,-0.8" Center="0.5,0" RadiusY="0.7" RadiusX="0.7">
        <GradientStop Color="#A02B40" Offset="0"/>
        <GradientStop Color="#08113c" Offset="1"/>
    </RadialGradientBrush>
</Window.Background>

使用 INotifyPropertyChangedTask.Delay 实现异步刷新

private async Task 每秒更新时间()
{
    while (true)
    {
        await Task.Delay(1000);// 每1秒钟等待
        实时信息.当前时间 = DateTime.Now;// 更新当前时间
    }
}

*DispatcherTimer** 是最简单的方法,可以直接在主线程上定时执行任务。

Task.Delay 配合异步方法可以避免使用定时器,适合处理延时任务。

BackgroundWorkerThread 方法适用于需要在后台线程中执行较复杂任务时。

System.Timers.Timer 适合简单的定时任务,但需要使用 Dispatcher.Invoke 来更新 UI。

Grid.Row="1"在父 Grid 中的第二行

Grid.Row="1" 表示该控件位于第二行。如果没有父 Grid,则这行代码会抛出错误,因为 Grid.Row 只能在父 Grid 中使用。

自定义控件UserControl

<local:外框 Margin="10"/>
//外框
public partial class 外框 : UserControl
{
    public 外框()
    {
        InitializeComponent();
    }
}
<UserControl x:Class="电子大屏.外框"/>

Border绘制矩形

<Border BorderThickness="1" BorderBrush="#02a6b5" Grid.RowSpan="2" Background="#101f51" Opacity="0.1"></Border>
​
<Border BorderThickness="1" Grid.Row="0" Grid.Column="2" Margin="10" Background="#C0FFFFFF" CornerRadius="10" BorderBrush="Black" Opacity="0.2" Grid.RowSpan="2">
    <Border.Effect>
        <DropShadowEffect ShadowDepth="5" BlurRadius="10" Color="Black" Opacity="0.5"/>
    </Border.Effect>
</Border>

BorderThickness="1" 设置边框的宽度为 1。BorderBrush="#02a6b5" 设置边框的颜色为 #02a6b5

Grid.RowSpan="2" 表示该 Border 会跨越 2 行,覆盖两行的区域。Background="#101f51" 设置背景颜色为深蓝色。Opacity="0.1" 设置透明度为 10%

CornerRadius="10"设置 Border 的圆角半径为 10.

DropShadowEffectBorder 添加了阴影效果:

ShadowDepth="5":阴影的深度,表示阴影偏移的距离。

BlurRadius="10":阴影的模糊半径,值越大阴影越模糊。

Color="Black":阴影的颜色为黑色。

Opacity="0.5":阴影的透明度为 50%,即阴影有一半的透明度,显得更加柔和。

Line绘制边框

<Line X1="0" Y1="0" X2="0" Y2="10" StrokeThickness="2" Stroke="#02a6b5" HorizontalAlignment="Right" VerticalAlignment="Bottom"></Line>

X1Y1 作为起始点坐标,X2Y2 作为终止点坐标。StrokeThickness="2" 设置线条的粗细为 2。

Stroke="#02a6b5" 设置线条的颜色为 #02a6b5 HorizontalAlignmentVerticalAlignment相对于父元素的对齐方式来定滴

DataGrid绑定数据源

<DataGrid  x:Name="表格_工序合格率显示" ItemsSource="{Binding 工序合格率显示}" FontSize="23" Margin="15" Background="Transparent" AutoGenerateColumns="False">
<DataGrid.Columns>
    <DataGridTextColumn Header="#" Binding="{Binding 序号}"/>
    <DataGridTextColumn Header="工位" Binding="{Binding 工位}"/>
    <DataGridTextColumn Header="加工总数" Binding="{Binding 测试总数}"/>
    <DataGridTextColumn Header="合格数" Binding="{Binding 合格数}"/>
    <DataGridTextColumn Header="合格率" Binding="{Binding 合格率}"/>
</DataGrid.Columns>
</DataGrid>

背景为透明,外边距为 15,并且没有自动生成列(AutoGenerateColumns="False")。

public BindingList<工序合格率> 工序合格率显示 { get; set; } = new();
[AddINotifyPropertyChangedInterface]
public partial class 工序合格率
{
    public int 序号 { get; set; }
    public string 工位 { get; set; }
    public string 测试总数 { get; set; }
    public string 合格数 { get; set; }
    public string 合格率 { get; set; }
}
List<工序合格率> 工序暂存 = new();
for (int i = 50; i < 410; i += 10)
{
    var 合格temp = new Random().Next(10, 100);
    var 总数 = new Random().Next(合格temp, 合格temp + 20);
    var 合格率 = ((double)合格temp / 总数) * 100;
    var temp = new 工序合格率()
    {
        工位 = $"OP{i}",
        合格数 = 合格temp.ToString(),
        测试总数 = 总数.ToString(),
        合格率 = 合格率.ToString("0.0") + "%"//93.3%
    };
    工序暂存.Add(temp);
}
int ii = 1;
foreach (var item in 工序暂存.OrderByDescending(s => Convert.ToDouble(s.合格率.Replace("%", string.Empty))))
{
    item.序号 = ii;
    工序合格率显示.Add(item);
    ii++;
}

使用LiveChartsCore.SkiaSharpView创建图表

<lvc:PieChart x:Name="饼图_站点在线情况" LegendPosition="Right" Series="{Binding 在线情况Series}" LegendTextPaint="{Binding 白色}" Grid.ColumnSpan="2"/>

折线图 (LineChart)用于显示数据的趋势,常用于时间序列分析。

柱状图 (ColumnChart)用于显示不同类别的数值对比,适合展示各类数据的差异。

条形图 (BarChart)和柱状图类似,区别在于条形图是水平显示的。

区域图 (AreaChart)区域图类似于折线图,但线下方的区域被填充,常用于显示累积数据或多层数据的堆叠。

雷达图 (RadarChart)雷达图通常用于显示多变量数据,图形呈现多边形状,适合比较多个维度的数值。等

LegendPosition="Right": 设置图例的位置为右侧。Series="{Binding 在线情况Series}": 数据绑定

LegendTextPaint="{Binding 白色}": 设置图例文本的颜色为白色

public SolidColorPaint 白色 { get; set; } = new SolidColorPaint(SKColors.White);
//运行情况饼图数据绑定
public IEnumerable<ISeries> 运行状态Series { get; set; } =
    [
        new PieSeries<int>
        {
            Values= new  []{3},
            Fill = new SolidColorPaint(SKColor.Parse("#2196f3")),
            Name = "待机中"//中文名称
        },
        new PieSeries<int>
        {
            Values= new  []{1},
            Fill = new SolidColorPaint(SKColors.Black),
            Name = "加工中"//中文名称
        },
        new PieSeries<int>
        {
            Values= new  []{2},
            Fill = new SolidColorPaint(SKColors.Purple),
            Name = "米诺"//中文名称
        },
        new PieSeries<int>
        {
            Values= new  []{4},
            Fill = new SolidColorPaint(SKColor.Parse("#f44336")),
            Name = "故障中"//中文名称
        },
        new PieSeries<int>
        {
            Values= new  []{3},
            Fill = new SolidColorPaint(SKColors.Plum),
            Name = "离线"//中文名称
        },
    ];

在app.xaml.cs中添加

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
​
            //var fontPath = Path.Combine(Directory.GetCurrentDirectory(), "字体", "MiSans.ttf");
            //var typeface = SKTypeface.FromFile(fontPath);
            //LiveCharts.Configure(config => config.HasGlobalSKTypeface(typeface));
            var index = SKFontManager.Default.FontFamilies.ToList().IndexOf("黑体");
            var songtiTypeface = SKFontManager.Default.GetFontStyles(index).CreateTypeface(0);
            LiveCharts.Configure(config =>      config.HasGlobalSKTypeface(songtiTypeface).AddDarkTheme().AddLightTheme());
        }
如果同时安装了LiveCharts.Wpf
LiveChartsCore.LiveCharts.Configure

支持中文显示

<lvc:CartesianChart> 是一个用于展示二维坐标图表的控件,它支持线图、柱状图、区域图等类型的图表。你可以通过设置 SeriesXAxisYAxis 来定义图表的外观和数据。

<lvc:CartesianChart DataContext="{Binding OEE}" LegendPosition="Hidden" LegendTextPaint="{Binding 白色}" 
    YAxes="{Binding YAxes}"
    XAxes="{Binding XAxes}" 
    Series="{Binding 数据}">
</lvc:CartesianChart>

Series:这个绑定指定了图表的系列数据,在这里是一个线图

XAxesYAxes:这两个绑定分别设置了 X 轴和 Y 轴的显示内容,例如标签、格式等。

定义一个VM类,变量要用小驼峰命名法,变量与绑定的变量一致
[INotifyPropertyChanged]
public partial class 折线图ModelView
{
    [ObservableProperty]
    private LabelVisual _Title;

    [ObservableProperty]
    private IEnumerable<ISeries> _数据;

    [ObservableProperty]
    private Axis[] _XAxes;
    

    [ObservableProperty]
    private Axis[] _YAxes;

}
public 折线图ModelView 工位合格率 { get; set; } = new();
public 折线图ModelView 线体直通率 { get; set; }
public 折线图ModelView OEE { get; set; }

private CancellationTokenSource 工位Token = new();
private CancellationTokenSource 线体Token = new();
private CancellationTokenSource OEEToken = new();

private Queue<DateTime> 线体时间队列 { get; set; } = new();
private Queue<DateTime> OEE时间队列 { get; set; } = new();

private void MetroWindow_Loaded(object sender, RoutedEventArgs e)
{
	工站合格率初始化();
	线体直通率初始化();
	OEE初始化();

	采集数据();
}


private void MetroWindow_Closed(object sender, EventArgs e)
{
	工位Token.Cancel();
	线体Token.Cancel();
	OEEToken.Cancel();
}
#region 采集数据
private void 采集数据()
{
	Task.Run(async () =>
	{
		while (true)
		{
			lock (lockObj)
			{
				采集工位合格率数据();
				// 调用自动翻页逻辑
			}
			await Task.Delay(5000);
			自动翻页();
		}
	}, 工位Token.Token);
	Task.Run(async () =>
	{
		while (true)
		{
			//await Task.Delay(TimeSpan.FromHours(1)); // 等待 1 小时
			await Task.Delay(5000);
			lock (lockObj)
			{
				采集线体直通率数据();
			}

		}
	}, 线体Token.Token);
	Task.Run(async () =>
	{
		while (true)
		{
			//await Task.Delay(TimeSpan.FromHours(1)); // 等待 1 小时
			await Task.Delay(5000);
			lock (lockObj)
			{
				采集OEE数据();
			}

		}
	}, OEEToken.Token);
}
private void 采集线体直通率数据()
{
	// 将当前时间加入队列
	线体时间队列.Enqueue(DateTime.Now);

	// 如果队列超过 8 个数据,删除第一个数据
	if (线体时间队列.Count > 8)
	{
		线体时间队列.Dequeue();
	}

	// 切换到 UI 线程更新界面
	Application.Current.Dispatcher.Invoke(() =>
	{
		// 更新图表的 X 轴标签ds
		线体直通率.XAxes.First().Labels = 线体时间队列
			.Select(t => t.ToString("HH:mm"))
			.ToList();

		线体直通率.数据.First().Values = 计算线体直通率(startTime, endTime);
	});
}
private void 采集OEE数据()
{
	OEE时间队列.Enqueue(DateTime.Now);

	if (OEE时间队列.Count > 8)
	{
		OEE时间队列.Dequeue();
	}
	// 切换到 UI 线程更新界面
	Application.Current.Dispatcher.Invoke(() =>
	{
		// 更新图表的 X 轴标签ds
		OEE.XAxes.First().Labels = OEE时间队列
			.Select(t => t.ToString("HH:mm"))
			.ToList();

		OEE.数据.First().Values = 计算OEE(startTime, endTime);
	});
}

private void 采集工位合格率数据()
{
	运行工站 = JsonSerializer.Deserialize<List<string>>(File.ReadAllText(Path.Combine(系统参数.配置文件路径, "运行工站.json")));
	Application.Current.Dispatcher.Invoke(() =>
	{
		工位合格率.数据.First().Values = 计算各站合格率(startTime, endTime);
		工位合格率.XAxes.First().Labels = 运行工站;
	});
}
#endregion
#region 初始化
private void 线体直通率初始化()
{
	线体时间队列 = new Queue<DateTime>();
	线体时间队列.Enqueue(DateTime.Now);

	var 时间标签 = 线体时间队列.Select(t => t.ToString("HH:mm")).ToList();
	List<ISeries<double>> re =
	[
		new LineSeries<double>()
		{
			Name = "直通率",
			Fill = null,
			Values = new List<double>(),
			GeometrySize = 8,
			Stroke = new SolidColorPaint(SKColors.Cyan) { StrokeThickness = 6 },
			DataLabelsPosition =LiveChartsCore.Measure.DataLabelsPosition.Start,
			DataLabelsSize=12,
			DataLabelsFormatter = point => $"{point.Coordinate.PrimaryValue:0}%", // 设置标签格式
			DataLabelsPaint = new SolidColorPaint(SKColors.White), // 标签颜色
		}
	];
	线体直通率 = new()
	{
		数据 = re,
		XAxes =
	[
		new Axis()
		{
			LabelsPaint = new SolidColorPaint(SKColors.White){IsAntialias = true},
			TextSize=14,
			ShowSeparatorLines = false,
			ForceStepToMin=true,
			Labels = 时间标签,
			LabelsRotation=0,
			TicksPaint = new SolidColorPaint(SKColors.White) { IsAntialias = true },  // 设置刻度线的颜色
			DrawTicksPath=true,
		}
	],
		YAxes =
	[
		new Axis()
		{
			LabelsPaint = new SolidColorPaint(SKColors.White) { IsAntialias = true },
			MaxLimit = 110,
			MinLimit = 0,
			TextSize = 14,
			MinStep = 20,
			Labeler = value=>$"{value:0}%", // 防止空标签导致问题
			ShowSeparatorLines = false,
			ForceStepToMin=true
		}
	],

	};
	re[0].Values = 计算线体直通率(startTime, endTime);
}
private void 工站合格率初始化()
{
	List<ISeries<double>> re =
	[
		new LineSeries<double>()
		{
			Name = "合格率",
			Fill = null,
			//Values = new List<double>(),
			GeometrySize = 8,
			Stroke = new SolidColorPaint(SKColors.Cyan) { StrokeThickness = 6 },
			DataLabelsPosition =LiveChartsCore.Measure.DataLabelsPosition.Start,
			DataLabelsSize=12,
			DataLabelsFormatter = point => $"{point.Coordinate.PrimaryValue:F2}%", // 设置标签格式
			DataLabelsPaint = new SolidColorPaint(SKColors.White), // 标签颜色
		}
	];
	工位合格率 = new()
	{
		//new LineSeries<> 生成随机数据(DateTime.Now, 4)
		数据 = re,
		XAxes =
	[
		new Axis()
		{
			LabelsPaint = new SolidColorPaint(SKColors.White){IsAntialias = true},
			TextSize=14,
			ShowSeparatorLines = false,
			ForceStepToMin=true,
			Labels = 运行工站,
			LabelsRotation=-30,
			TicksPaint = new SolidColorPaint(SKColors.White) { IsAntialias = true },  // 设置刻度线的颜色
			DrawTicksPath=true,// 启用坐标轴绘制
			MinLimit=-0.5,
			MaxLimit=3.5
		}
	],
		YAxes =
	[
		new Axis()
		{
			LabelsPaint = new SolidColorPaint(SKColors.White) { IsAntialias = true },
			MaxLimit = 110,
			MinLimit = 0,
			TextSize = 14,
			MinStep = 20,
			Labeler = value=>$"{value:0}%", // 防止空标签导致问题
			ShowSeparatorLines = false,
			ForceStepToMin=true
		}
	],

	};
	re[0].Values = 计算各站合格率(startTime, endTime);
}

private void OEE初始化()
{
	OEE时间队列 = new Queue<DateTime>();
	OEE时间队列.Enqueue(DateTime.Now);
	var 时间标签 = OEE时间队列.Select(t => t.ToString("HH:mm")).ToList();
	List<ISeries<double>> re =
	[
		new LineSeries<double>()
		{
			Name = "OEE",
			Fill = null,
			Values = new List<double>(),
			GeometrySize = 8,
			Stroke = new SolidColorPaint(SKColors.Cyan) { StrokeThickness = 6 },
			DataLabelsPosition =LiveChartsCore.Measure.DataLabelsPosition.Start,
			DataLabelsSize=12,
			DataLabelsFormatter = point => $"{point.Coordinate.PrimaryValue:0.0}%", // 设置标签格式
			DataLabelsPaint = new SolidColorPaint(SKColors.White), // 标签颜色
		}
	];
	OEE = new()
	{
		数据 = re,
		XAxes =
	[
		new Axis()
		{
			LabelsPaint = new SolidColorPaint(SKColors.White){IsAntialias = true},
			TextSize=14,
			ShowSeparatorLines = false,
			ForceStepToMin=true,
			Labels = 时间标签,
			LabelsRotation=0,
			TicksPaint = new SolidColorPaint(SKColors.White) { IsAntialias = true },  // 设置刻度线的颜色
			DrawTicksPath=true,
			MinLimit=-0.5,
		}
	],
		YAxes =
	[
		new Axis()
		{
			LabelsPaint = new SolidColorPaint(SKColors.White) { IsAntialias = true },
			MaxLimit = 120,
			MinLimit = 0,
			TextSize = 14,
			MinStep = 40,
			Labeler = value=>$"{value:0}%", // 防止空标签导致问题
			ShowSeparatorLines = false,
			ForceStepToMin=true
		}
	],

	};
	re[0].Values = 计算OEE(startTime, endTime);
}
#endregion
#region 计算数据
private void 自动翻页()
{
	// 获取总页数
	int totalPages = (int)Math.Ceiling((double)运行工站.Count / pageSize);

	// 根据方向更新当前页
	if (isForward)
	{
		currentPage++;

		// 如果超过最后一页,切换方向并停留在最后一页
		if (currentPage >= totalPages)
		{
			currentPage = totalPages - 1;
			isForward = false; // 切换为向后翻页
		}
	}
	else
	{
		currentPage--;

		// 如果低于第一页,切换方向并停留在第一页
		if (currentPage < 0)
		{
			currentPage = 0;
			isForward = true; // 切换为向前翻页
		}
	}

	// 更新分页后的 X 轴范围
	工位合格率.XAxes.First().MinLimit = currentPage * pageSize - 0.5; // 给点留空
	工位合格率.XAxes.First().MaxLimit = Math.Min((currentPage + 1) * pageSize, 运行工站.Count) - 0.5;
}
private List<double>? 计算线体直通率(string startTime, string endTime)
{
	try
	{
		var 生产实时信息 = 数据库.生产实时信息.AsNoTracking().Where(s => string.Compare(s.updateTime, startTime) >= 0 && string.Compare(s.updateTime, endTime) <= 0).ToList();
		if (生产实时信息 == null)
		{
			return null;
		}
		var 总数 = 生产实时信息.Count;
		var 合格数 = 生产实时信息.Count(s => s.isbad == false);
		if (总数 == 0)
		{
			线体直通数据.Add(100);
		}
		else
		{
			线体直通数据.Add((double)合格数 / 总数 * 100);
		}
		if (线体直通数据.Count > 8)
		{
			线体直通数据.RemoveAt(0);
		}
		return 线体直通数据;
	}
	catch (Exception ex)
	{
		throw ex;
	}


}
private List<double>? 计算各站合格率(string startTime, string endTime)
{
	try
	{
		工站运行总数.Clear();
		工站运行错误数.Clear();
		//避免缓存查询结果
		var 生产实时信息 = 数据库.生产实时信息.AsNoTracking().Where(s => string.Compare(s.updateTime, startTime) >= 0 && string.Compare(s.updateTime, endTime) <= 0).ToList();
		if (生产实时信息 == null)
		{
			return null;
		}
		var re = new List<double>();
		for (int i = 0; i < 运行工站.Count; i++)
		{
			工站运行总数.Add(0);
			工站运行错误数.Add(0);
		}
		foreach (var info in 生产实时信息)
		{
			if (info.stationCode == "0")
			{
				continue;
			}
			if ((bool)info.isbad)
			{
				var index = 运行工站.IndexOf(info.ngStation);
				for (int i = 0; i <= index; i++)
				{
					工站运行总数[i]++;
				}
				工站运行错误数[index]++;
			}
			else if (string.IsNullOrWhiteSpace(info.stationCode))
			{
				for (int i = 0; i < 运行工站.Count; i++)
				{
					工站运行总数[i]++;
				}
			}
			else
			{
				var index = 运行工站.IndexOf(info.stationCode);
				for (int i = 0; i <= index; i++)
				{
					工站运行总数[i]++;
				}
			}
		}
		for (int j = 0; j < 工站运行总数.Count; j++)
		{
			if (工站运行总数[j] == 0)
			{
				re.Add(100);
			}
			else
			{
				re.Add((1 - (double)工站运行错误数[j] / 工站运行总数[j]) * 100);
			}
		}
		return re;
	}
	catch (Exception ex)
	{
		throw ex;
	}
}
private List<double>? 计算OEE(string startTime, string endTime)
{
	var 设备信息 = 数据库.设备实时信息.AsNoTracking().ToList();
	var 生产实时信息 = 数据库.生产实时信息.AsNoTracking().Where(s => string.Compare(s.updateTime, startTime) >= 0 && string.Compare(s.updateTime, endTime) <= 0).ToList();
	var 生产节拍信息 = 数据库.生产节拍信息.AsNoTracking().ToList();
	int 设备运转时间 = 0;
	int 负荷时间 = 0;
	int 实际生产数量 = 0;
	int 合格品数量 = 0;
	int 生产数量 = 0;
	foreach (var item in 设备信息)
	{
		设备运转时间 += Convert.ToInt32(item.totalRunningDuration);
		负荷时间 += Convert.ToInt32(item.totalOnLineDuration);
	}
	负荷时间 = 设备运转时间 - 负荷时间;
	double 时间稼动率 = (double)设备运转时间 / 负荷时间;

	foreach (var item in 生产实时信息)
	{
		if (item.isbad == true || string.IsNullOrWhiteSpace(item.stationCode))
		{
			var 对应产品理论生产节拍 = 生产节拍信息.Where(x => x.StationCode == item.snNumber).Select(x => x.TheoreticalTempo).FirstOrDefault();
			实际生产数量 += 对应产品理论生产节拍;
			生产数量++;
		}
		if (item.isbad == false && string.IsNullOrWhiteSpace(item.stationCode))
		{
			合格品数量++;
		}
	}
	double 性能稼动率 = (double)实际生产数量 / 设备运转时间;
	double 产品合格率 = (double)合格品数量 / 生产数量;
	设备OEE数据.Add(性能稼动率 * 产品合格率 * 时间稼动率 * 100);
	if (设备OEE数据.Count > 8)
	{
		设备OEE数据.RemoveAt(0);
	}
	return 设备OEE数据;
}
#endregion

画刷

1.LinearGradientBrush,这种画刷是由左上角向右下.

2.RadialGradientBrush,这种画刷是有中心向外面渐变(此项目使用这种)

<!--设计背景颜色渐变-->
        <Grid.Background>
            <RadialGradientBrush>
                <GradientStop Color="#ff285173" Offset="0"></GradientStop>
                <GradientStop Color="#ff244967" Offset="0.5"></GradientStop>
                <GradientStop Color="#13164B" Offset="1"></GradientStop>
            </RadialGradientBrush>

贝塞尔曲线绘制边框