テンプレートの FluentUI Blazor プロジェクトの新規作成
- Visual Studio 2022を開いて
- プロジェクトの新規作成を押下
- Fluent Blazorのウエブアプリテンプレートを選択
- 「次」を押下
- プロジェクト名を入力
- 「次」を押下
- 「作成」を押下
デフォルトのアプリの作り直し
デフォルトのアプリは Fluent UI を使用していますが、アプリケーションの基本的のレイアウトを定義するために Layout の代わりに MainLayout を使用したいと考えました。
MainLayout.razor
まず Components/Layout/MainLayout.razor
の内容を書き換えましょう。
ビフォアー
@inherits LayoutComponentBase
<FluentLayout>
<FluentHeader>
FluentBlazorTodoList
</FluentHeader>
<FluentStack Class="main" Orientation="Orientation.Horizontal" Width="100%">
<NavMenu />
<FluentBodyContent>
<div class="content">
@Body
</div>
</FluentBodyContent>
</FluentStack>
<FluentFooter>
<div class="link1">
<a href="https://www.fluentui-blazor.net" target="_blank">Documentation and demos</a>
</div>
<div class="link2">
<a href="https://learn.microsoft.com/en-us/aspnet/core/blazor" target="_blank">About Blazor</a>
</div>
</FluentFooter>
</FluentLayout>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
アフター
@inherits LayoutComponentBase
<FluentMainLayout>
<Header>
FluentBlazorTodoList
</Header>
<Body>
@Body
</Body>
<NavMenuContent>
<NavMenu />
</NavMenuContent>
</FluentMainLayout>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
まとめ
-
FluentLayout
をFluentMainLayout
に置き換え -
FluentHeader
をHeader
に置き換え - 後で不要になるため、
FluentFooter
を削除 - 「main」のスタック(
FluentStack
)を 2 つに分割:
4.1.FluentBodyContent
タグとそのコンテンツを @Body ディレクティブを含む単純なBody
タグに変換
4.2.NavMenu
コンポーネントをNavMenuContent
タグに配置します
NavMenu.razor
次に、変更に合わせて Components/Layout/NavMenu.razor
の内容を書き換えましょう。
ビフォアー
@rendermode InteractiveServer
<div class="navmenu">
<input type="checkbox" title="Menu expand/collapse toggle" id="navmenu-toggle" class="navmenu-icon" />
<label for="navmenu-toggle" class="navmenu-icon"><FluentIcon Value="@(new Icons.Regular.Size20.Navigation())" Color="Color.Fill" /></label>
<nav class="sitenav" aria-labelledby="main-menu" onclick="document.getElementById('navmenu-toggle').click();">
<FluentNavMenu Id="main-menu" Collapsible="true" Width="250" Title="Navigation menu" @bind-Expanded="expanded">
<FluentNavLink Href="/" Match="NavLinkMatch.All" Icon="@(new Icons.Regular.Size20.Home())" IconColor="Color.Accent">Home</FluentNavLink>
<FluentNavLink Href="counter" Icon="@(new Icons.Regular.Size20.NumberSymbolSquare())" IconColor="Color.Accent">Counter</FluentNavLink>
<FluentNavLink Href="weather" Icon="@(new Icons.Regular.Size20.WeatherPartlyCloudyDay())" IconColor="Color.Accent">Weather</FluentNavLink>
</FluentNavMenu>
</nav>
</div>
@code {
private bool expanded = true;
}
アフター
<FluentNavMenu Id="main-menu" Collapsible="true" Width="250" Title="Navigation menu">
<FluentNavLink Href="/" Match="NavLinkMatch.All" Icon="@(new Icons.Regular.Size20.Home())" IconColor="Color.Accent">Home</FluentNavLink>
<FluentNavLink Href="counter" Icon="@(new Icons.Regular.Size20.NumberSymbolSquare())" IconColor="Color.Accent">Counter</FluentNavLink>
<FluentNavLink Href="weather" Icon="@(new Icons.Regular.Size20.WeatherPartlyCloudyDay())" IconColor="Color.Accent">Weather</FluentNavLink>
</FluentNavMenu>
まとめ
- サイド・メニューの折りたたみはメイン・レイアウトによって管理されるため、
FluentNavMenu
タグとそのコンテンツだけ必要です。 - 不要になった
Collapsible
属性を削除。 -
Width
属性も不要で残すとBody
の内容のレイアウトが崩れる。
結果
ご覧のとおり、マークアップは簡素化されたのに、書き換えの外観はデフォルトのアプリケーションの外観とほぼ同じです
Todo リスト アプリを構築する
このアプリを、概要ページを備えた ToDo リスト アプリに変換しましょう。
不要機能の削除
まず、「Counter(カウンター)」と「Weather(天気)」のページを削除しましょう。
ファイル削除
-
Components/Pages/Counter.razor
とComponents/Pages/Weather.razor
のファイルを選択 - 右クリックすると表示されるコンテキストメニューで Delete を選択
リンク処理
Components/Layout/NavMenu.razor
ファイルの中で:
- 1番目の「Home(ホーム)」のリンクの名前を「Todo リスト」に変更
- 1番目のリンクのアイコンをチェックされたチェックボックスに変更
<FluentNavMenu Id="main-menu" Title="Navigation menu"> <FluentNavLink Href="/" Match="NavLinkMatch.All" Icon="@(new Icons.Regular.Size20.CheckboxChecked())" IconColor="Color.Accent">Todo リスト</FluentNavLink> <FluentNavLink Href="weather" Icon="@(new Icons.Regular.Size20.WeatherPartlyCloudyDay())" IconColor="Color.Accent">Weather</FluentNavLink> <FluentNavLink Href="counter" Icon="@(new Icons.Regular.Size20.NumberSymbolSquare())" IconColor="Color.Accent">Counter</FluentNavLink> </FluentNavMenu>
- 2番目と3番目のリンクを削除
<FluentNavMenu Id="main-menu" Title="Navigation menu"> <FluentNavLink Href="/" Match="NavLinkMatch.All" Icon="@(new Icons.Regular.Size20.CheckboxChecked())" IconColor="Color.Accent">Todo リスト</FluentNavLink> </FluentNavMenu>
- 「About」ページへのリンクを追加する
<FluentNavMenu Id="main-menu" Title="Navigation menu"> <FluentNavLink Href="/" Match="NavLinkMatch.All" Icon="@(new Icons.Regular.Size20.CheckboxChecked())" IconColor="Color.Accent">Todo リスト</FluentNavLink> <FluentNavLink Href="about" Icon="@(new Icons.Regular.Size20.Info())" IconColor="Color.Accent">About</FluentNavLink> </FluentNavMenu>
Todo リスト追加
-
Components/Pages/Home.razor
を開いて - 相対 URL
/
とともに @page ディレクティブを追加。 - そのまま静的にレンダリングされないように、ページの対話機能を有効にします。 対話型サーバー レンダリング モードにすると、コンポーネントでサーバーからの UI イベントを処理できます。
- ページに HTML 要素を追加することを可能にする PageTitle コンポーネントを使用してページ タイトルを追加します。
@page "/" @rendermode InteractiveServer <PageTitle>Todo</PageTitle> <h3>Todo</h3> @code { }
- プロジェクト(
FluentBlazorTodoList
)フォルダーを右クリックすると表示されるコンテキストメニューで Add > New Folder を選択
- モデル クラスの保持に使用するため、新しいフォルダーを「Models」に名付け
-
Models
フォルダーを右クリックすると表示されるコンテキストメニューで Add > New Item を選択
- Todo アイテムを表すクラスを保持するために、プロジェクト
Models
フォルダーにTodoItem.cs
ファイルを追加
- 次の C# コードを挿入して
TodoItem
クラスを肉付けnamespace Models; public class TodoItem { public string? Title { get; set; } public DateTime? DueDate { get; set; } public bool IsDone { get; set; } }
- Todo コンポーネントに戻り
-
@namespace でモデルの
TodoItem
クラスを使用可能にする - Todo アイテム用のフィールドを @code ブロックに追加(Todo コンポーネントでは、このフィールドを使って ToDo リストの状態を維持)
- リストを表す縦のスタック(
FluentStack
)を追加 - スタック(
FluentStack
)のなか、各 Todo アイテムを横のスタック(FluentStack
)を含むカード(FluentCard
)としてレンダリングするために、 foreach ループを追加@page "/" @rendermode InteractiveServer @namespace FluentBlazorTodoList.Models <PageTitle>Todo</PageTitle> <h3>Todo</h3> <FluentStack Orientation="Orientation.Vertical" HorizontalAlignment="HorizontalAlignment.Center" VerticalAlignment="VerticalAlignment.Top" VerticalGap="4"> @foreach (var todo in todos) { <FluentCard Height="64px"> <FluentStack Orientation="Orientation.Horizontal" HorizontalAlignment="HorizontalAlignment.Start" VerticalAlignment="VerticalAlignment.Center" HorizontalGap="4"> <FluentCheckbox @bind-Value="@todo.IsDone" Label="Is Done?" /> <FluentTextField @bind-Value="@todo.Title" Label="Title" Placeholder="title"></FluentTextField> <FluentDatePicker Label="Due" AriaLabel="To" @bind-Value="@todo.DueDate" /> </FluentStack> </FluentCard> } </FluentStack> @code { private List<TodoItem> todos = new(); }
- リストのスタック(
FluentStack
)を別の縦のスタック(FluentStack
)で囲み、その下のリストに新しい Todo 項目を追加する UI 要素を追加 - 追加 UI 要素をリストから視覚的に分離するため、
FluentDivider
を追加 - 追加 UI のコントロールを含む横のスタック(
FluentStack
)を追加 - 新しい Todo 項目のタイトル用に
FluentTextField
を追加 - 新しい Todo 項目の期限用の
FluentDatePicker
を追加 - 追加アクションを実行するための「Add Todo」ボタン(
FluentButton
)を追加@page "/" @rendermode InteractiveServer @namespace FluentBlazorTodoList.Models <PageTitle>Todo</PageTitle> <h3>Todo</h3> <FluentStack Orientation="Orientation.Vertical" HorizontalAlignment="HorizontalAlignment.Left" VerticalAlignment="VerticalAlignment.Top" VerticalGap="24"> <FluentStack Orientation="Orientation.Vertical" HorizontalAlignment="HorizontalAlignment.Center" VerticalAlignment="VerticalAlignment.Top" VerticalGap="4"> @foreach (var todo in todos) { <FluentCard Height="64px"> <FluentStack Orientation="Orientation.Horizontal" HorizontalAlignment="HorizontalAlignment.Start" VerticalAlignment="VerticalAlignment.Center" HorizontalGap="4"> <FluentCheckbox @bind-Value="@todo.IsDone" Label="Is Done?" /> <FluentTextField @bind-Value="@todo.Title" Label="Title" Placeholder="title"></FluentTextField> <FluentDatePicker Label="Due" AriaLabel="To" @bind-Value="@todo.DueDate" /> </FluentStack> </FluentCard> } </FluentStack> <FluentDivider Style="width: 100%;" Orientation=Orientation.Horizontal Role="DividerRole.Separator"></FluentDivider> <FluentStack Orientation="Orientation.Horizontal" HorizontalAlignment="HorizontalAlignment.Start" VerticalAlignment="VerticalAlignment.Center" HorizontalGap="4"> <FluentTextField Label="New Todo" Placeholder="Something todo" ></FluentTextField> <FluentDatePicker Label="Due" AriaLabel="To" /> <FluentButton Appearance="Appearance.Accent" aria-label="add-a-todo-item-to-the-list" Autofocus="true" IconStart="@(new Icons.Regular.Size20.AddCircle())" >Add todo</FluentButton> </FluentStack> </FluentStack> @code { private List<Models.TodoItem> todos = new(); }
現時点では、ボタンにイベント・ハンドラーがアタッチされていないため、「Add Todo」ボタン(FluentButton
)を押しても何も起こりません。
21. Todo アイテムの追加アクションを処理するには、@code ブロックに AddTodo メソッドを追加
@code {
private List<Models.TodoItem> todos = new();
private void AddTodo()
{
// // Todo: todo アイテムを追加
}
}
22 . 次に、@onclick 属性を使用して、AddTodo メソッドを「Add Todo」ボタンのクリック・イベント ハンドラーとして登録
<FluentButton Appearance="Appearance.Accent" aria-label="add-a-todo-item-to-the-list" Autofocus="true" IconStart="@(new Icons.Regular.Size20.AddCircle())" OnClick="@AddTodo">Add todo</FluentButton>
23 . 新しい Todo アイテムのタイトルを取得するため、@code ブロックの先頭に newTodoTitle
String フィールドを追加
24 . 新しい Todo アイテムの期限を取得するため、newTodoDueDate
DateTime フィールドを @code ブロックの先頭に追加
25 . AddTodo
メソッドを更新して、指定されたタイトルと期限を持つ TodoItem
をリストに追加
26 . newTodoTitle
を空の文字列に設定し、newTodoDueDate
を null に設定して、 FluentTextField
と FluentDatePicker
の値をクリア
@code {
string? newTodoTitle;
DateTime? newTodoDueDate;
private List<Models.TodoItem> todos = new();
private void AddTodo()
{
if (!string.IsNullOrWhiteSpace(newTodoTitle))
{
todos.Add(new TodoItem { Title = newTodoTitle, DueDate = newTodoDueDate });
newTodoTitle = string.Empty;
newTodoDueDate = null;
}
}
}
27 . @bind-Value 属性を使用して、newTodoTime
を FluentTextField
要素にバインド
28 . @bind-Value 属性を使用して、newTodoDueDate
を FluentDatePicker
要素にバインド
<FluentTextField @bind-Value=newTodoTitle Label="New Todo" Placeholder="Something todo"></FluentTextField>
<FluentDatePicker Label="Due" AriaLabel="To" @bind-Value=newTodoDueDate />
29 . Todo リストの前にカウンター・バッジ(FluentCounterBadge
)を追加して、完了していない Todo 項目の数を表示する (IsDone は false)。 Blazor がコンポーネントを再レンダリングするたびにその Razor 式が評価される
<FluentCounterBadge Count="@todos.Count(todo => !todo.IsDone)" Appearance="Appearance.Neutral">
<FluentButton Appearance="Appearance.Outline">
<FluentIcon Value="@(new Icons.Regular.Size24.CheckboxPerson())" Color="@Color.FillInverse" />
</FluentButton>
</FluentCounterBadge>
<FluentDivider Style="width: 100%;" Orientation=Orientation.Horizontal Role="DividerRole.Separator"></FluentDivider>
完成
```
@page "/"
@rendermode InteractiveServer
@namespace FluentBlazorTodoList.Models
<PageTitle>Todo</PageTitle>
<h3>Todo</h3>
<FluentStack Orientation="Orientation.Vertical"
HorizontalAlignment="HorizontalAlignment.Left"
VerticalAlignment="VerticalAlignment.Top"
VerticalGap="24">
<FluentCounterBadge Count="@todos.Count(todo => !todo.IsDone)" Appearance="Appearance.Neutral">
<FluentButton Appearance="Appearance.Outline">
<FluentIcon Value="@(new Icons.Regular.Size24.CheckboxPerson())" Color="@Color.FillInverse" />
</FluentButton>
</FluentCounterBadge>
<FluentDivider Style="width: 100%;" Orientation=Orientation.Horizontal Role="DividerRole.Separator"></FluentDivider>
<FluentStack Orientation="Orientation.Vertical"
HorizontalAlignment="HorizontalAlignment.Center"
VerticalAlignment="VerticalAlignment.Top"
VerticalGap="4">
@foreach (var todo in todos)
{
<FluentCard Height="64px">
<FluentStack Orientation="Orientation.Horizontal"
HorizontalAlignment="HorizontalAlignment.Start"
VerticalAlignment="VerticalAlignment.Center"
HorizontalGap="4">
<FluentCheckbox @bind-Value="@todo.IsDone" Label="Is Done?" />
<FluentTextField @bind-Value="@todo.Title" Label="Title" Placeholder="title"></FluentTextField>
<FluentDatePicker Label="Due" AriaLabel="To" @bind-Value="@todo.DueDate" />
</FluentStack>
</FluentCard>
}
</FluentStack>
<FluentDivider Style="width: 100%;" Orientation=Orientation.Horizontal Role="DividerRole.Separator"></FluentDivider>
<FluentStack Orientation="Orientation.Horizontal"
HorizontalAlignment="HorizontalAlignment.Start"
VerticalAlignment="VerticalAlignment.Center"
HorizontalGap="4">
<FluentTextField @bind-Value=newTodoTitle Label="New Todo" Placeholder="Something todo"></FluentTextField>
<FluentDatePicker Label="Due" AriaLabel="To" @bind-Value=newTodoDueDate />
<FluentButton Appearance="Appearance.Accent" aria-label="add-a-todo-item-to-the-list" Autofocus="true" IconStart="@(new Icons.Regular.Size20.AddCircle())" OnClick="@AddTodo">Add todo</FluentButton>
</FluentStack>
</FluentStack>
@code {
string? newTodoTitle;
DateTime? newTodoDueDate;
private List<Models.TodoItem> todos = new();
private void AddTodo()
{
if (!string.IsNullOrWhiteSpace(newTodoTitle))
{
todos.Add(new TodoItem { Title = newTodoTitle, DueDate = newTodoDueDate });
newTodoTitle = string.Empty;
newTodoDueDate = null;
}
}
}
```
About ページ追加
-
Components/Pages
フォルダーを右クリックすると表示されるコンテキストメニューで Add > New Component を選択
- 「About.razor」を名付けて
- 「追加」を押下
-
Components/Pages/About.razor
を開いて - 次のコードを挿入して「About」のページを実装してください
@page "/about" @rendermode InteractiveServer <PageTitle>About</PageTitle> <FluentBodyContent> <h3>About</h3> <FluentPersona Name="Meister 619" Status="PresenceStatus.Available" StatusSize="PresenceBadgeSize.Large" StatusTitle="He is available" Image="https://avatars.githubusercontent.com/u/3477572?v=4" ImageSize="64px"> </FluentPersona> <p> ... blah blah blah ...</p> </FluentBodyContent> @code { }
完成
参考
- MicrosoftのLearn/.NET/ASP.NET Core/Blazor Todo リスト アプリのチュートリアル
- Fluent UI Blazor コンポネント ライブラリー
- Fluent UI Blazor の GitHub レポジトリー