Winform Blazorハイブリッドでの描画
先日、winformでBlazorハイブリッドを使用することを紹介しましたが、Blazor UIを使用するとwinformプログラムがより良く見えるようになると述べました。次に、winform Blazorハイブリッドで描画する例を示したいと思います。
効果は
始める前に、以下のように効果を示します。


具体的な達成
具体的な実装に興味がある場合は、以下を読むことができます。
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ハイブリッドプロジェクトは、以下のようにw w wroot/index.htmlに導入されました。

AntDesign.Chartsも紹介しました。
在 _Imports.razor 中加入命名空间:
@using AntDesign
为了动态地显示弹出组件,需要在 App.razor 中添加一个 <AntContainer /> 组件。
これは公式サイトによると、winform blazor hybridではホームページのrazorとして追加できます。ここではindex.razorを以下のようにします。

Ant Design Blazorのコンポーネントを使用することができます。
2、ページデザイン
図面シートのデザインは次のようになります。

最初のステップは、好きなレイアウトを選択し、私は公式ウェブサイトのこのセクションを選択しました。

自分でアイコンと名前を変更するので、最初の質問は、どのようにクリックしてページを切り替えるかです。
各MenuItemには次のようなKeyプロパティがあります。

各キーはユニークです。異なるMenuItemをクリックするとクリックイベントが発生し、同じメソッドが異なる引数でラムダ式を使用して呼び出されます。
この方法を見てみましょう
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層、ビジネスロジック層、データベースアクセス層に分かれた3層アーキテクチャを採用しています。
其中的weatherServer是我自定义的服务,使用这个服务,需要在开头添加语句:
@inject IWeatherServer weatherServer;
在 Blazor 中,@inject 是用于在 Razor 页面或组件中注入服务的指令。通过 @inject,你可以将依赖注入服务引入到 Blazor 页面或组件中,以便在其中使用这些服务。
サービスを利用するには、まずサービスを登録する必要があります。
services.AddSingleton<IWeatherServer,WeatherServer>();
services.AddSingleton<DataServer>();
1つはビジネスロジックのサービスで、もう1つはデータアクセスのサービスです。
其中IWeatherServer是业务逻辑层的接口,使用接口的好处,大家可以参考一下:
** 複数の継承の実装 **
C#のクラスは単一の継承しかサポートしないが、1つのクラスは複数のインタフェースを実装できる。インターフェイスは、クラスが異なる次元の機能を取得して実装できるようにする方法を提供します。クラスは複数のインターフェイスを実装し、各インターフェイスで定義されたメンバのセットを持つことができます。
** 仕様の実装:**
インタフェースは、実装クラスに特定のメンバを要求する仕様のセットを定義します。これにより、実装クラスが特定のプログラミング仕様や標準に準拠することができ、コードの一貫性と可読性が向上します。
** 抽象化と柔軟性の提供:**
インターフェイス自体は具体的な実装を提供せず、メンバーの契約を定義するだけです。これにより、インターフェイスは強力な抽象化ツールとなり、具体的な実装を公開することなくクラスの機能を記述できます。
インタフェースはまた、クラス自体の実装を変更することなく、クラスの動作を拡張および変更する方法を提供します。
** 依存性注入の実装:**
インターフェイスと依存性注入の組み合わせにより、アプリケーションでの代替性とテスト性の実装が容易になります。Dependency Injectionフレームワークを使用すると、実行時に異なる実装を注入できるため、モジュール間の低結合を実現できます。
** 公的契約の定義:**
インタフェースは、特定の型に関係なく、複数の実装がシステム内で連携できるように、共通の契約を定義する方法を提供します。これは、プラグインシステム、拡張性、およびモジュラー設計に非常に役立ちます。
** 許可された多形性:**
インターフェイスを使用すると、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" },
};
選択されたラベルには値があります。
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();
ここで、まずweServer.GetDataByition itionの実装は次のようになります。
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);
Drawの設定:
LineConfig Config1 = new LineConfig
{
Padding = "auto",
XField = "date",
YField = "value",
SeriesField = "type",
Smooth = true
};
その後、図面を作成できます。
概要まとめまとめ
これは私がwinform blazor hybridを使用して小さなケースを書くのは初めてですが、blazor hybridは理解し始めたばかりです。欠点は、あなたを許してください。