在winform blazor hybrid中绘图
前几天跟大家介绍了在winform中使用blazor hybrid,而且还说配上blazor的ui可以让我们的winform程序设计的更加好看,接下来我想以一个在winform blazor hybrid中绘图的例子来进行说明,希望对你有所帮助。
效果
在开始之前,先给大家演示一下效果,如下所示:
具体实现
如果你对具体实现感兴趣,可以继续往下阅读。
1、引入ant design blazor
该应用中用到的所有组件都来源于ant design blazor。
在本文中我只介绍绘图部分的实现,首先需要在项目中引入ant design blazor。
安装 Nuget 包引用,如下所示:
如果需要画图的话,还需要引用AntDesign.Charts包引用。
在项目的 Form1.cs
中注册相关服务:
services.AddAntDesign();
如下所示:
引入静态样式和脚本文件:
<link href="_content/AntDesign/css/ant-design-blazor.css" rel="stylesheet">
<script src="_content/AntDesign/js/ant-design-blazor.js"></script>
winform blazor hybrid项目在wwwroot/index.html中引入,如下所示:
这里我也把AntDesign.Charts的引入了。
在 _Imports.razor
中加入命名空间:
@using AntDesign
为了动态地显示弹出组件,需要在 App.razor
中添加一个 <AntContainer />
组件。
这是官网的说法,在winform blazor hybrid中可以在当做主页面的razor中添加,我这里是Index.razor如下所示:
现在就可以使用ant design blazor的组件了。
2、页面设计
绘图页面的设计如下所示:
第一步选择喜欢的布局,我选的是官网中的这一款,如下所示:
自己修改一下图标与名字,那么现在摆在面前的第一个问题就是,如何实现点击切换页面呢?
每一个MenuItem都有一个Key属性,如下所示:
在这里每一个Key都是唯一的。点击不同的MenuItem都会触发点击事件,而点击事件使用了lambda表达式调用了同一个方法,但是参数不同。
现在来看看这个方法:
int selectedMenuItem = 1;
private void NavigateToContent(int menuItemNumber)
{
selectedMenuItem = menuItemNumber;
}
很简单,只是将参数传给selectedMenuItem。
然后在内容这个地方,使用switch case:
<Content Class="site-layout-background" Style="margin: 24px 16px;padding: 24px;min-height: 450px;">
@switch(selectedMenuItem)
{
case 1:
<GetData></GetData>
break;
case 2:
<QueryData></QueryData>
break;
case 3:
<Painting></Painting>
break;
case 4:
<Export></Export>
break;
}
</Content>
然后就可以根据不同的selectedMenuItem值显示不同的组件了。
现在来看看<Painting></Painting>
组件的设计。
<Painting></Painting>
组件的页面代码如下:
<div>
<GridRow>
<GridCol Span="8">
<Space Direction="DirectionVHType.Vertical">
<SpaceItem>
<Text Strong>开始日期:</Text>
</SpaceItem>
<SpaceItem>
<DatePicker TValue="DateTime?" Format="yyyy/MM/dd"
Mask="yyyy/dd/MM" Placeholder="@("yyyy/dd/MM")"
@bind-Value = "Date1"/>
</SpaceItem>
<SpaceItem>
<Text Strong>结束日期:</Text>
</SpaceItem>
<SpaceItem>
<DatePicker TValue="DateTime?" Format="yyyy/MM/dd"
Mask="yyyy/dd/MM" Placeholder="@("yyyy/dd/MM")"
@bind-Value = "Date2"/>
</SpaceItem>
<SpaceItem>
<Text Strong>站名:</Text>
</SpaceItem>
<SpaceItem>
<AutoComplete @bind-Value="@value"
Options="@options"
OnSelectionChange="OnSelectionChange"
OnActiveChange="OnActiveChange"
Placeholder="input here"
Style="width:150px"/>
</SpaceItem>
<SpaceItem>
<Text Strong>绘图指标:</Text>
</SpaceItem>
<SpaceItem>
<div>
<AntDesign.CheckboxGroup Options="@ckeckAllOptions" @bind-Value = "selectedValues"/>
</div>
</SpaceItem>
<SpaceItem>
<Button Type="@ButtonType.Primary" OnClick="Painting_Clicked">绘图</Button>
</SpaceItem>
</Space>
</GridCol>
<GridCol Span="12">
<AntDesign.Charts.Line Data="@Data1" Config="Config1" @ref="lineChartRef" />
</GridCol>
</GridRow>
</div>
3、填充站名
当我们一打开这个组件,就有不同的站名了,如下所示:
这是怎么实现的呢?
首先使用<AutoComplete>
自动完成这个组件,如下所示:
<AutoComplete @bind-Value="@value"
Options="@options"
OnSelectionChange="OnSelectionChange"
OnActiveChange="OnActiveChange"
Placeholder="input here"
Style="width:150px"/>
List<string> options = new List<string>();
protected override void OnInitialized()
{
options = weatherServer.GetDifferentStations();
}
在 Blazor 中,OnInitialized
是一个生命周期方法,用于在组件初始化时执行一些逻辑。具体而言,OnInitialized
方法是 Microsoft.AspNetCore.Components.ComponentBase
类中定义的一个虚拟方法,你可以在派生的组件中覆盖它,以在组件初始化的时候执行一些自定义的操作。
这里采用了三层架构的方式,分为ui层、业务逻辑层、数据库访问层。
其中的weatherServer
是我自定义的服务,使用这个服务,需要在开头添加语句:
@inject IWeatherServer weatherServer;
在 Blazor 中,@inject
是用于在 Razor 页面或组件中注入服务的指令。通过 @inject
,你可以将依赖注入服务引入到 Blazor 页面或组件中,以便在其中使用这些服务。
当然要使用服务,必须先注册服务:
services.AddSingleton<IWeatherServer,WeatherServer>();
services.AddSingleton<DataServer>();
这里一个是业务逻辑的服务一个是数据访问的服务。
其中IWeatherServer
是业务逻辑层的接口,使用接口的好处,大家可以参考一下:
实现多继承:
C# 中的类只支持单一继承,但一个类可以实现多个接口。接口提供了一种方式,允许一个类在不同的维度上获取和实现功能。一个类可以实现多个接口,从而拥有每个接口定义的一组成员。
实现规范:
接口定义了一组规范,要求实现类提供特定的成员。这有助于强制实现类遵循一定的编程规范和标准,从而提高代码的一致性和可读性。
提供抽象和灵活性:
接口本身不提供具体的实现,只是定义了成员的契约。这使得接口成为一种强大的抽象工具,让你可以在不暴露具体实现的情况下描述类的能力。
接口还提供了一种扩展和修改类行为的方法,而无需更改类本身的实现。
实现依赖注入:
接口和依赖注入相结合,使得在应用程序中实现可替代性和可测试性变得更加容易。通过依赖注入框架,你可以在运行时注入不同的实现,从而实现模块之间的低耦合性。
定义公共契约:
接口提供了一种定义公共契约的方式,使得多个实现可以在系统中一起工作,而不管它们的具体类型如何。这对于插件系统、扩展性和模块化设计非常有用。
允许多态性:
通过接口,你可以利用 C# 中的多态性机制。当你引用一个对象的接口类型时,你可以在运行时实际上引用该对象的派生类型,从而实现多态行为。
定义事件契约:接口可以包含事件声明,用于定义类应该提供的事件契约。这有助于规范化事件的使用和处理。
我这里使用接口,主要是为了明晰服务到底实现了哪些功能,因为具体实现类中会有很多代码,不好看清楚。
比如跟绘图相关的接口如下所示:
public List<string> GetDifferentStations();
public List<WeatherData> GetDataByCondition(Condition condition);
然后在实现类中进行具体实现:
public List<string> GetDifferentStations()
{
return dataService.GetDifferentStations();
}
public List<WeatherData> GetDataByCondition(Condition condition)
{
return dataService.GetDataByCondition(condition);
}
业务逻辑层中不与数据库直接相互,使用了数据库访问服务:
public List<string> GetDifferentStations()
{
return db.Queryable<WeatherData>().Select(x => x.StationName ?? "").Distinct().ToList();
}
public List<WeatherData> GetDataByCondition(Condition condition)
{
return db.Queryable<WeatherData>()
.Where(x => x.Date >= condition.StartDate &&
x.Date < condition.EndDate.AddDays(1) &&
x.StationName == condition.StationName).ToList();
}
这里数据库使用的是SQLite,ORM使用的是SQLSugar,具体怎么设置,在这里我就不详细说明了,可以查看官网也可以查看历史文章。
4、绘图的实现
代码如下:
async void Painting_Clicked()
{
if (Date1 != null && Date2 != null && value != null && selectedValues != null)
{
if(Data1?.Length > 0)
{
Data1 = new object[0];
}
if (plotDatas.Count > 0)
{
plotDatas.Clear();
}
var cofig = new MessageConfig()
{
Content = "正在画图中...",
Duration = 0
};
var task = _message.Loading(cofig);
var condition = new Condition();
condition.StartDate = (DateTime)Date1;
condition.EndDate = (DateTime)Date2;
condition.StationName = value;
for(int i = 0;i < selectedValues.Length;i ++)
{
switch (selectedValues[i])
{
case "Tem_Low":
var result1 = weatherServer.GetDataByCondition(condition).Select(x => new PlotData
{
Date = x.Date,
Type = "Tem_Low",
Value = Convert.ToDouble(x.Tem_Low)
}).ToList();
plotDatas.AddRange(result1);
break;
case "Tem_High":
var result2 = weatherServer.GetDataByCondition(condition).Select(x => new PlotData
{
Date = x.Date,
Type = "Tem_High",
Value = Convert.ToDouble(x.Tem_High)
}).ToList();
plotDatas.AddRange(result2);
break;
case "Visibility_Low":
var result3 = weatherServer.GetDataByCondition(condition).Select(x => new PlotData
{
Date = x.Date,
Type = "Visibility_Low",
Value = Convert.ToDouble(x.Visibility_Low)
}).ToList();
plotDatas.AddRange(result3);
break;
case "Visibility_High":
var result4 = weatherServer.GetDataByCondition(condition).Select(x => new PlotData
{
Date = x.Date,
Type = "Visibility_High",
Value = Convert.ToDouble(x.Visibility_High)
}).ToList();
plotDatas.AddRange(result4);
break;
}
}
// 将自定义类型的数组投影为 object[] 类型的数组
Data1 = plotDatas.Select(p => new { date = p.Date, type = p.Type, value = p.Value }).ToArray();
// 更新图表数据
await lineChartRef.ChangeData(Data1);
task.Start();
}
else
{
await _message.Error("请查看开始日期、结束日期、站名与绘图指标是否都已选择!!!");
}
}
在AntDesign.Charts中画多条折线图,官网位置如下所示:
创建一个自定义的画图数据类:
public class PlotData
{
public DateTime? Date { get; set; }
public string? Type { get; set; }
public double Value { get; set; }
}
然后建一个画图数据类的列表:
List<PlotData> plotDatas = new List<PlotData>();
创建一个自定义的条件类:
public class Condition
{
public DateTime StartDate{ get; set; }
public DateTime EndDate { get; set; }
public string? StationName { get; set; }
}
然后在我点击的时候,如果各项不为空,那么创建一个条件对象:
var condition = new Condition();
condition.StartDate = (DateTime)Date1;
condition.EndDate = (DateTime)Date2;
condition.StationName = value;
该对象包含了我们选择的开始时间、结束时间与站名。
然后遍历selectedValues:
for(int i = 0;i < selectedValues.Length;i ++)
selectedValues是string[]?类型。
string[]? selectedValues;
表示的是多选框中选中的值。
static CheckboxOption[] ckeckAllOptions = new CheckboxOption[]{
new CheckboxOption{ Label="最低温度(℃)",Value="Tem_Low" },
new CheckboxOption{ Label="最高温度(℃)", Value="Tem_High" },
new CheckboxOption{ Label="最低可见度(km)", Value="Visibility_Low"},
new CheckboxOption{ Label="最高可见度(km)", Value="Visibility_High" },
};
选择的Label都有对应的value。
switch (selectedValues[i])
{
case "Tem_Low":
var result1 = weatherServer.GetDataByCondition(condition).Select(x => new PlotData
{
Date = x.Date,
Type = "Tem_Low",
Value = Convert.ToDouble(x.Tem_Low)
}).ToList();
plotDatas.AddRange(result1);
break;
case "Tem_High":
var result2 = weatherServer.GetDataByCondition(condition).Select(x => new PlotData
{
Date = x.Date,
Type = "Tem_High",
Value = Convert.ToDouble(x.Tem_High)
}).ToList();
plotDatas.AddRange(result2);
break;
case "Visibility_Low":
var result3 = weatherServer.GetDataByCondition(condition).Select(x => new PlotData
{
Date = x.Date,
Type = "Visibility_Low",
Value = Convert.ToDouble(x.Visibility_Low)
}).ToList();
plotDatas.AddRange(result3);
break;
case "Visibility_High":
var result4 = weatherServer.GetDataByCondition(condition).Select(x => new PlotData
{
Date = x.Date,
Type = "Visibility_High",
Value = Convert.ToDouble(x.Visibility_High)
}).ToList();
plotDatas.AddRange(result4);
break;
}
如果值为Tem_Low
,那么我们的画图数据就是:
var result2 = weatherServer.GetDataByCondition(condition).Select(x => new PlotData
{
Date = x.Date,
Type = "Tem_High",
Value = Convert.ToDouble(x.Tem_High)
}).ToList();
这里首先weatherServer.GetDataByCondition(condition)的实现如下:
public List<WeatherData> GetDataByCondition(Condition condition)
{
return dataService.GetDataByCondition(condition);
}
而dataService.GetDataByCondition(condition)的实现如下:
public List<WeatherData> GetDataByCondition(Condition condition)
{
return db.Queryable<WeatherData>()
.Where(x => x.Date >= condition.StartDate &&
x.Date < condition.EndDate.AddDays(1) &&
x.StationName == condition.StationName).ToList();
}
最终获得了满足日期与站名要求的List<WeatherData>
,然后再使用Select方法构造PlotData对象:
Select(x => new PlotData
{
Date = x.Date,
Type = "Tem_High",
Value = Convert.ToDouble(x.Tem_High)
}).ToList();
然后加入到plotDatas中:
plotDatas.AddRange(result1);
这样遍历完selectedValues之后就得到了我们所有需要的画图数据,选中了几项就有几项,然后需要映射到object[] 类型的数组:
object[]? Data1;
// 将自定义类型的数组投影为 object[] 类型的数组
Data1 = plotDatas.Select(p => new { date = p.Date, type = p.Type, value = p.Value }).ToArray();
这里我也很迷惑,Ant Design Charts Blazor 的 AntLineChart
等组件通常使用 object[]
类型的数组作为图表的数据源。这是因为 JavaScript 本身是一种弱类型语言,而 Blazor 通过 JavaScript Interop 进行与 JavaScript 的通信,这是ChatGPT的解释,大家可以参考一下。
然后更新图表:
// 更新图表数据
await lineChartRef.ChangeData(Data1);
绘图的设置:
LineConfig Config1 = new LineConfig
{
Padding = "auto",
XField = "date",
YField = "value",
SeriesField = "type",
Smooth = true
};
然后就可以实现绘图了。
小结
这是我第一次尝试使用winform blazor hybrid写一个小案例,blazor hybrid也才刚开始了解,不足之处,请各位多多包涵,最后希望对你有所帮助,不清楚的地方,可以与我交流。
在winform blazor hybrid中绘图的更多相关文章
- Blazor Hybrid (Blazor混合开发)更好的读取本地图片
在 Blazor Hybrid 应用中,Razor 组件在设备上本机运行. 组件通过本地互操作通道呈现到嵌入式 Web View 控件. 组件不在浏览器中运行,并且不涉及 WebAssembly. R ...
- C#WinForm应用程序中嵌入ECharts图表
C#WinForm应用程序中嵌入ECharts图表 程序运行效果: 下载ECharts: 官网下载ECharts :http://echarts.baidu.com/download.html 或者直 ...
- Blazor Hybrid / MAUI 简介和实战
1. Blazor Blazor 是一个使用 .NET 生成交互式客户端 Web UI 的框架: 使用 C# 代替 JavaScript 来创建信息丰富的交互式 UI. 共享使用 .NET 编写的服务 ...
- 在Winform界面菜单中实现动态增加【最近使用的文件】菜单项
在我们一些和文件处理打交道的系统中,我们往往需要记录下最近使用的文件,这样方便用户快速打开之前浏览或者编辑过的文件,这种在很多软件上很常见,本文主要介绍在Winform界面菜单中实现[最近使用的文件] ...
- 在C#代码中应用Log4Net(四)在Winform和Web中捕获全局异常
毕竟人不是神,谁写的程序都会有bug,有了bug不可怕,可怕的是出错了,你却不知道错误在哪里.所以我们需要将应用程序中抛出的所有异常都记录起来,不然出了错,找问题就能要了你的命.下面我们主要讨论的是如 ...
- Web API应用架构在Winform混合框架中的应用(5)--系统级别字典和公司级别字典并存的处理方式
在我这个系列中,我主要以我正在开发的云会员管理系统为例进行介绍Web API的应用,由于云会员的数据设计是支持多个商家公司,而每个公司又可以包含多个店铺的,因此一些字典型的数据需要考虑这方面的不同.如 ...
- Web API应用架构在Winform混合框架中的应用(4)--利用代码生成工具快速开发整套应用
前面几篇介绍了Web API的基础信息,以及如何基于混合框架的方式在WInform界面里面整合了Web API的接入方式,虽然我们看似调用过程比较复杂,但是基于整个框架的支持和考虑,我们提供了代码生成 ...
- Web API应用架构在Winform混合框架中的应用(3)--Winfrom界面调用WebAPI的过程分解
最近一直在整合WebAPI.Winform界面.手机短信.微信公众号.企业号等功能,希望把它构建成一个大的应用平台,把我所有的产品线完美连接起来,同时也在探索.攻克更多的技术问题,并抽空写写博客,把相 ...
- Web API应用架构在Winform混合框架中的应用(2)--自定义异常结果的处理
在上篇随笔<Web API应用架构在Winform混合框架中的应用(1)>中我介绍了关于如何在Winfrom里面整合WebAPI,作为一个新型数据源的接入方式,从而形成了三种不同的数据提供 ...
- Web API应用架构在Winform混合框架中的应用(1)
在<Web API应用架构设计分析(1)>和<Web API应用架构设计分析(2)>中对WebAPI的架构进行了一定的剖析,在当今移动优先的口号下,传统平台都纷纷开发了属于自己 ...
随机推荐
- 模块化打包工具-Webpack插件与其他功能
1.Webpack插件机制 上一篇提到的webpack的loader可以用来加载资源,包括各种css,图片文件资源,实现打包文件的功能,而webpack的插件则起到了加强webpack的作用,可以完成 ...
- YbtOJ 质数与约数 4.统计元素
搜不到题解,乐. 由题意,\(a_i\) 对答案有贡献当且仅当 \(i\in{[l,r]}\) 且对于 \(\forall a_j| {a_i},j\notin[l,r]\). 则对于每个 \(a_i ...
- redis 怎么样查看key的大小,多大的key才算大key?
查看key大小的命令 # 格式memory usage [key-name]# 例如:我要查 yino_hash_key 这个key的大小,就在命令行中输入 memory usage yino_has ...
- IIS和PHP相关权限问题及解决方案_500错误_500.19 - Internal Server Error与401未授权错误_401.3 - Unauthorized
在IIS添加网站(假设站点为xxx.yyy.com,本例假设IIS版本为7.5或以上),如果采用IIS默认配置,会在创建站点同时创建相应同名的"应用程序池"(也是xxx.yyy.c ...
- Linux中修改文件权限方法
在此记录一下,看到的一篇写的比较好的帮我解决问题的文章 这里也补充一下,如果要修改当前目录下的所有文件时,文件名用 * 代替,如:chmod 755 *(即可修改当前文件下所有文件的权限为:-rwxr ...
- 多维详述MediaBox互动直播AUI Kit低代码开发方案
本专栏将分享阿里云视频云MediaBox系列技术文章,深度剖析音视频开发利器的技术架构.技术性能.开发能效和最佳实践,一起开启音视频的开发之旅.本文为MediaBox最佳实践篇,重点从互动直播AUI ...
- Codeforces Round #538 (Div. 2) F. Please, another Queries on Array?
原题链接 F. Please, another Queries on Array? 这道题让求\(\phi(\prod\limits_{i = l}^r a_i)\),然后我们化简一下. 设\(P\) ...
- Excel数据统计与分析
- 2023-12-02:用go语言,如何求模立方根? x^3=a mod p, p是大于等于3的大质数, a是1到p-1范围的整数常数, x也是1到p-1范围的整数,求x。 p过大,x不能从1到p-1遍
2023-12-02:用go语言,如何求模立方根? x^3=a mod p, p是大于等于3的大质数, a是1到p-1范围的整数常数, x也是1到p-1范围的整数,求x. p过大,x不能从1到p-1遍 ...
- 性能测试Mysql之profiling参数
一.查看profiling状态 mysql> select @@profiling; 0:表示为关闭 1:表示开启 二.开启 profiling mysql> SET profiling= ...