1、はじめに ✨
Winform 開発において、データ表示機能は頻繁に必要となります。これまでは GridControl コントロールを使用していましたが、今回は Winform Blazor Hybrid で Ant Design Blazor の Table コンポーネントを使ってデータ表示を行う方法をサンプルを通してご紹介します。
2、効果 ✨
まずは実装の効果をご覧ください:


3、具体的な実装 ✨
Winform Blazor Hybrid プロジェクトで Ant Design Blazor を使用する方法については、前回の記事をご覧ください。
Ant Design Blazor の Table コンポーネントを導入します:
<table
TItem="IData"
DataSource="@datas"
OnRowClick="OnRowClick"
@ref="antTableRef"
>
<PropertyColumn Property="c=>c.StationName"> </PropertyColumn>
<PropertyColumn Property="c=>c.Weather"> </PropertyColumn>
<PropertyColumn Property="c=>c.Tem_Low"> </PropertyColumn>
<PropertyColumn Property="c=>c.Tem_High"> </PropertyColumn>
<PropertyColumn Property="c=>c.Wind"> </PropertyColumn>
<PropertyColumn Property="c=>c.Visibility_Low"> </PropertyColumn>
<PropertyColumn Property="c=>c.Visibility_High"> </PropertyColumn>
<PropertyColumn Property="c=>c.Fog"> </PropertyColumn>
<PropertyColumn Property="c=>c.Haze"> </PropertyColumn>
<PropertyColumn Property="c=>c.Date"> </PropertyColumn>
</table>
このうち:
TItem は DataSource 内の単一項目の型を表します。0.16.0 以降、Table は普通のクラス、record、インターフェース、抽象クラスを DataSource の型としてサポートしています。
ここでは TItem を IData というインターフェースに設定しています。その定義は以下の通りです:
public interface IData
{
[DisplayName("站名")]
public string? StationName { get; set; }
[DisplayName("天气")]
public string? Weather { get; set; }
[DisplayName("最低温度/℃")]
public string? Tem_Low { get; set; }
[DisplayName("最高温度/℃")]
public string? Tem_High { get; set; }
[DisplayName("风力风向")]
public string? Wind { get; set; }
[DisplayName("最低可见度/km")]
public string? Visibility_Low { get; set; }
[DisplayName("最高可见度/km")]
public string? Visibility_High { get; set; }
[DisplayName("雾")]
public string? Fog { get; set; }
[DisplayName("霾")]
public string? Haze { get; set; }
[DisplayName("日期")]
public DateTime? Date { get; set; }
}
[DisplayName("站名")] はプロパティまたはメンバーに対するメタデータ注釈であり、より親しみやすい表示名を提供します。Ant Design Blazor はこれを自動的に使用して名前を表示します。
DataSource はテーブルのデータソースを表し、型は IEnumerable です:

ここでは DataSource="@datas" とすることで、datas というデータソースを Table コンポーネントの DataSource プロパティに割り当てています。
datas の定義は以下の通りです:
WeatherData[] datas = Array.Empty<WeatherData>();
WeatherData はカスタムクラスで、IData インターフェースを実装しています:
public class WeatherData : IData
{
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
public string? StationName { get; set; }
public string? Weather { get; set; }
public string? Tem_Low { get; set; }
public string? Tem_High { get; set; }
public string? Wind { get; set; }
public string? Visibility_Low { get; set; }
public string? Visibility_High { get; set; }
public string? Fog { get; set; }
public string? Haze { get; set; }
public DateTime? Date { get; set; }
}
}
ここで疑問に思われるかもしれません。先ほど TItem は DataSource 内の単一項目の型を表すと説明しましたが、ここでは DataSource が WeatherData[] であり、単一項目の型は WeatherData であって、設定した IData ではありません。これで問題ないのでしょうか?
以下の簡単な例を見れば、疑問が解けるかもしれません:
public interface IFlyable
{
void Fly();
}
public class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("The bird is flying.");
}
}
class Program
{
// 主方法
static void Main()
{
Bird myBird = new Bird();
IFlyable flyableObject = myBird; // 类型转换
// 调用接口方法
flyableObject.Fly();
}
}
IFlyable インターフェースと、そのインターフェースを実装した Bird クラスを定義し、Main 関数で Bird クラスをインスタンス化し、そのオブジェクトをインターフェース型に暗黙的に変換し、インターフェースを介して実装クラスのメソッドを呼び出しています。出力結果は次のとおりです:
The bird is flying.
これは、C# ではクラスがインターフェースを実装している場合、そのクラスのインスタンスが暗黙的にインターフェース型に変換できることを示しています。ここでは WeatherData が IData に暗黙的に変換されます。
DataSource についても同様です。公式ドキュメントには型が IEnumerable と書かれていますが、ここでは WeatherData[] でも問題ありません。これも Array が IEnumerable インターフェースを実装しているためです:

OnRowClick は行のクリックイベントを表し、型は EventCallback<RowData> です。この例では実際には使用していません。
Blazor において、@ref は、Blazor コンポーネント内で HTML 要素またはコンポーネントインスタンスを参照するためのディレクティブです。@ref を使用することで、Blazor コンポーネント内で DOM 要素や子コンポーネントへの参照を取得し、コード内で操作したり、そのプロパティやメソッドにアクセスしたりできます。
ここでは Table コンポーネントに @ref="antTableRef" を追加し、コード領域に:
Table<IData>? antTableRef;
と追加することで、Table コンポーネントインスタンスを参照しています。
<PropertyColumn> はプロパティ列、つまり表示する列を表します。その Property プロパティはバインドするプロパティを指定し、型は Expression<Func<TItem, TProp>> です。
ここで疑問に思うかもしれません:Expression<Func<TItem, TProp>> とは一体何でしょうか?
Expression<Func<TItem, TProp>> は C# の式ツリーであり、TItem 型のパラメータを受け取り、TProp 型の値を返すラムダ式を表します。
分解してみると、Expression<T> はラムダ式のツリー構造を表すクラスで、T はデリゲート型です。詳細については公式ドキュメントをご覧ください:

Func<TItem, TProp> はジェネリックデリゲート型で、1つの入力パラメータと1つの出力パラメータを持つメソッドを表します。詳細については公式ドキュメントをご覧ください:

ここでも簡単な例で説明します:
Expression<Func<Person, string>> getNameExpression = person => person.Name;
getNameExpression はラムダ式を表します。どのようなラムダ式かというと、入力パラメータ型が Person(ここでは person)、出力型が string(ここでは person.Name)であるラムダ式です。
したがって、次のコード:
<PropertyColumn Property="c=>c.StationName"> </PropertyColumn>
は理解できるでしょう。Property の型は、入力パラメータ型が TItem(ここでは IData)、出力型が TProp(ここでは string)であるラムダ式 c=>c.StationName です。
以上を理解した上で、この部分のコードを見てみましょう:

コードは以下の通りです:
<GridRow>
<Space>
<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>
<button type="@ButtonType.Primary" OnClick="QueryButton_Clicked">
查询
</button>
</SpaceItem>
</Space>
</GridRow>
駅名の自動補完:
<AutoComplete
@bind-Value="@value"
Options="@options"
OnSelectionChange="OnSelectionChange"
OnActiveChange="OnActiveChange"
Placeholder="input here"
Style="width:150px"
/>
この実装は前回の記事ですでに紹介しているため、ここでは繰り返し説明しません。
2つの日付選択コンポーネントはデータバインディングを使用しています:
<SpaceItem>
<DatePicker TValue="DateTime?" Format="yyyy/MM/dd" Mask="yyyy/dd/MM"
Placeholder="@("yyyy/dd/MM")" @bind-Value = "Date1"/>
</SpaceItem>
<SpaceItem>
<DatePicker TValue="DateTime?" Format="yyyy/MM/dd" Mask="yyyy/dd/MM"
Placeholder="@("yyyy/dd/MM")" @bind-Value = "Date2"/>
</SpaceItem>
このうち:
TValue は値の型を表します。ここでは DateTime? に設定しています。
@bind-Value でデータバインディングを行い、日付選択コンポーネントの値を Date1 および Date2 にバインドします:
DateTime? Date1;
DateTime? Date2;
検索ボタン:
<button type="@ButtonType.Primary" OnClick="QueryButton_Clicked">查询</button>
クリックイベントのコード:
async void QueryButton_Clicked()
{
if (Date1 != null && Date2 != null && value != null)
{
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;
datas = weatherServer.GetDataByCondition(condition).ToArray();
StateHasChanged();
task.Start();
}
else
{
await _message.Error("请查看开始日期、结束日期与站名是否都已选择!!!");
}
}
条件が成立する場合、Condition 型を作成し、開始日、終了日、駅名を設定します。Condition クラスの定義は次のとおりです:
public class Condition
{
public DateTime StartDate{ get; set; }
public DateTime EndDate { get; set; }
public string? StationName { get; set; }
}
次に、ビジネスロジック層の weatherServer の GetDataByCondition メソッドを呼び出します:
datas = weatherServer.GetDataByCondition(condition).ToArray();
weatherServer の GetDataByCondition メソッドは以下の通りです:
public List<WeatherData> GetDataByCondition(Condition condition)
{
return dataService.GetDataByCondition(condition);
}
データベースの読み書きが含まれるため、データアクセス層の dataService の GetDataByCondition メソッドを呼び出しています。
データアクセス層の dataService の GetDataByCondition メソッドは以下の通りです:
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();
}
再クエリ時には:
StateHasChanged();
このメソッドを呼び出すとコンポーネントが更新されます。Blazor において、StateHasChanged は Blazor フレームワークに対してコンポーネントとその子コンポーネントを再レンダリングするよう通知するメソッドです。Blazor コンポーネントの UI レンダリングはコンポーネントの状態に基づいており、状態が変化した場合に StateHasChanged メソッドを呼び出してフレームワークに再レンダリングを通知する必要があります。
var cofig = new MessageConfig()
{
Content = "正在更新中...",
Duration = 0
};
var task = _message.Loading(cofig);
task.Start();
これはユーザーへの情報通知です。
4、まとめ ✨
以上、完全なサンプルを通じて、Winform において GridControl だけでなく、Ant Design Blazor の Table を使用してもデータ表示が可能であることを説明しました。