本記事は以下の公式ドキュメントを解釈していく記事です。
Radzen Blazor Components


Blazor DropZone Component | Free UI Components by Radzen
Demonstration and configuration of the Radzen Blazor DropZone component.
@* --- ドラッグ&ドロップの全体を管理するコンテナ --- *@
<RadzenDropZoneContainer TItem="MyTask"
Data="data"
ItemSelector="@ItemSelector"
ItemRender="@OnItemRender"
CanDrop="@CanDrop"
Drop="@OnDrop">
<ChildContent>
@* 横並びのレイアウト設定 *@
<RadzenStack Orientation="Orientation.Horizontal" Gap="1rem" Wrap="FlexWrap.Wrap" class="rz-p-12">
@* --- 未着手ゾーン --- *@
<RadzenDropZone Value="Status.NotStarted" class="rz-display-flex rz-flex-column rz-background-color-warning-lighter rz-border-warning-light rz-border-radius-2 rz-p-4" Style="flex: 1; gap: 1rem;">
<RadzenText Text="Not started" TextStyle="TextStyle.Subtitle2" />
</RadzenDropZone>
@* --- 進行中ゾーン --- *@
<RadzenDropZone Value="Status.Started" class="rz-display-flex rz-flex-column rz-background-color-info-lighter rz-border-info-light rz-border-radius-2 rz-p-4" Style="flex: 1; gap: 1rem;">
<RadzenText Text="Started" TextStyle="TextStyle.Subtitle2" />
</RadzenDropZone>
@* --- 完了ゾーン --- *@
<RadzenDropZone Value="Status.Completed" class="rz-display-flex rz-flex-column rz-background-color-success-lighter rz-border-success-light rz-border-radius-2 rz-p-4" Style="flex: 1; gap: 1rem;">
<RadzenText Text="Completed" TextStyle="TextStyle.Subtitle2" />
</RadzenDropZone>
@* --- 削除ゾーン(ここに入れると見えなくなる) --- *@
<RadzenDropZone Value="Status.Deleted" class="rz-display-flex rz-flex-column rz-background-color-danger-lighter rz-border-danger-light rz-border-radius-2 rz-p-4" Style="flex: 1; gap: 1rem;">
<RadzenText Text="Drop here to delete" TextStyle="TextStyle.Subtitle2" />
</RadzenDropZone>
</RadzenStack>
</ChildContent>
@* --- 各タスク(カード)の中身のデザイン --- *@
<Template>
<strong>@context.Name</strong>
</Template>
</RadzenDropZoneContainer>
<style>
/* ドロップ可能な場所に重なった時の背景色(青っぽくなる) */
.rz-can-drop {
background-color: var(--rz-background-color-primary);
}
</style>
@code {
/// <summary>
/// アイテム振り分けロジック:どのアイテムをどのゾーンに表示するか
/// </summary>
Func<MyTask, RadzenDropZone<MyTask>, bool> ItemSelector = (item, zone) =>
item.Status == (Status)zone.Value && item.Status != Status.Deleted;
/// <summary>
/// ドロップ許可ロジック:移動させて良いかどうかを判定
/// </summary>
Func<RadzenDropZoneItemEventArgs<MyTask>, bool> CanDrop = request =>
{
// 1. 同じ列内での並び替えはOK
// 2. 「削除」列への移動はどこからでもOK
// 3. ステータスが「1段階だけ」変わる移動(例: 未着手→進行中)はOK
return request.FromZone == request.ToZone ||
(Status)request.ToZone.Value == Status.Deleted ||
Math.Abs((int)request.Item.Status - (int)request.ToZone.Value) == 1;
};
/// <summary>
/// 各アイテムの描画カスタマイズ
/// </summary>
void OnItemRender(RadzenDropZoneItemRenderEventArgs<MyTask> args)
{
// 名前が "Task2" の場合のみドラッグ禁止にする
if (args.Item.Name == "Task2")
{
args.Attributes["draggable"] = "false";
args.Attributes["style"] = "cursor:not-allowed";
args.Attributes["class"] = "rz-card rz-variant-flat rz-background-color-primary-lighter rz-color-on-primary-lighter";
}
else
{
// 通常のアイテムの見た目
args.Attributes["class"] = "rz-card rz-variant-filled rz-background-color-primary-light rz-color-on-primary-light";
}
// ステータスが Deleted なら画面上に表示しない
args.Visible = args.Item.Status != Status.Deleted;
}
/// <summary>
/// ドロップが完了した時の処理
/// </summary>
void OnDrop(RadzenDropZoneItemEventArgs<MyTask> args)
{
// 別の列に移動した場合、アイテムのステータスを更新
if (args.FromZone != args.ToZone)
{
args.Item.Status = (Status)args.ToZone.Value;
}
// 列内での並び替え、または新しい列の特定の位置への挿入
if (args.ToItem != null && args.ToItem != args.Item)
{
data.Remove(args.Item); // 一旦リストから外す
data.Insert(data.IndexOf(args.ToItem), args.Item); // ターゲットの場所に差し込む
}
}
// 表示データ保持用
IList<MyTask> data;
// 初期データ作成
protected override void OnInitialized()
{
//Task0-5 が作られる
data = Enumerable.Range(0, 5)
.Select(i =>
new MyTask()
{
Id = i,
Name = $"Task{i}",
Status = i < 3 ? Status.NotStarted : Status.Started
})
.ToList();
}
// タスクのデータモデル
public class MyTask
{
public int Id { get; set; }
public string Name { get; set; }
public Status Status { get; set; } = Status.NotStarted;
}
// 状態を定義する列挙型
public enum Status
{
NotStarted, // 0
Started, // 1
Completed, // 2
Deleted // 3
}
}実装マニュアル
1. 事前準備
Radzenコンポーネントがプロジェクトで使用可能な状態か確認してください。
- Program.cs:
builder.Services.AddRadzenComponents();が記述されていること。 - _Imports.razor:
@using Radzenおよび@using Radzen.Blazorが記述されていること。 - MainLayout.razor:
<RadzenComponents />が配置されていること。
2. コンポーネントの作成
この機能を独立したページとして作成します。
- プロジェクト内の
Pagesフォルダ(またはComponents/Pages)に新しいファイルを作成します。- ファイル名:
TaskBoard.razor
- ファイル名:
- コードファイルの**一番上(1行目)**に以下を追加して、ブラウザからアクセスできるようにします。
@page "/taskboard"
@using Radzen
@using Radzen.Blazor
3. 動作確認(テストシナリオ)
アプリを起動し、/taskboard にアクセスして以下の動作を確認してください。
A. 基本的な移動(正常系)
- 操作: 「Not started」にある
Task0を「Started」の列へドラッグする。 - 結果: ドロップでき、その列に収まること。
B. ルールによる制限(異常系)
このコードには CanDrop ロジックにより「1段階ずつしか進めない」というルールがあります。
- 操作: 「Not started」にあるタスクを、いきなり「Completed」へドラッグする。
- 結果: ドロップ禁止マークが表示され、移動できないこと(
Math.Abs(...) == 1の判定)。
C. ドラッグ禁止アイテムの確認
- 操作:
Task2(色が薄く表示されているカード)をドラッグしようとする。 - 結果: マウスカーソルが禁止マークになり、掴むことができないこと(
OnItemRenderでの制御)。
D. 並び替え(ソート)
- 操作: 同じ列の中で、タスクの上下を入れ替える。
- 結果: 順番が入れ替わること。
E. 削除機能
- 操作: 任意のタスクを「Drop here to delete」ゾーン(赤色)へドラッグする。
- 結果: ドロップした瞬間に画面からタスクが消えること(
StatusがDeletedになり、表示対象外になるため)。
4. 実践的なカスタマイズ(DB連携など)
実際の業務アプリでは、メモリ上のリスト(data)だけでなく、データベースを更新する必要があります。その場合の修正箇所は以下の通りです。
データの読み込み
OnInitialized を OnInitializedAsync に変更し、DBからデータを取得します。
@inject TaskService TaskService // サービスを注入
protected override async Task OnInitializedAsync()
{
// DBからタスク一覧を取得
data = (await TaskService.GetTasksAsync()).ToList();
}
ドロップ時の保存処理
OnDrop メソッド内で、変更をDBに保存します。
async Task OnDrop(RadzenDropZoneItemEventArgs<MyTask> args)
{
// ...(既存のステータス更新・並び替えロジック)...
if (args.FromZone != args.ToZone)
{
args.Item.Status = (Status)args.ToZone.Value;
// 【追加】DB更新処理
await TaskService.UpdateTaskStatusAsync(args.Item.Id, args.Item.Status);
// 通知を表示(オプション)
NotificationService.Notify(new NotificationMessage
{
Severity = NotificationSeverity.Success,
Summary = "更新完了",
Detail = $"{args.Item.Name} を移動しました",
Duration = 2000
});
}
}
ステータス定義の分離
現在 @code ブロック内にある public class MyTask や public enum Status は、実際の開発では Models フォルダなどの別ファイル(例: MyTask.cs)に移動させることを推奨します。これにより、他のページからもこのクラスを参照できるようになります。

コメント