1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

2024: Microsoft のチュートリアルの Blazor Todo リスト アプリを Fluent UI に変換

Last updated at Posted at 2024-01-24

テンプレートの FluentUI Blazor プロジェクトの新規作成

  1. Visual Studio 2022を開いて
  2. プロジェクトの新規作成を押下
    project_create.png
  3. Fluent Blazorのウエブアプリテンプレートを選択
  4. 「次」を押下
    fluent_create_1.png
  5. プロジェクト名を入力
  6. 「次」を押下
    ffb_1.png
  7. 「作成」を押下
    fluent_create_3.png

デフォルトのアプリの作り直し

image.png

デフォルトのアプリは 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>

まとめ

  1. FluentLayoutFluentMainLayout に置き換え
  2. FluentHeaderHeader に置き換え
  3. 後で不要になるため、FluentFooter を削除
  4. 「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>

まとめ

  1. サイド・メニューの折りたたみはメイン・レイアウトによって管理されるため、FluentNavMenu タグとそのコンテンツだけ必要です。
  2. 不要になった Collapsible 属性を削除。
  3. Width 属性も不要で残すと Body の内容のレイアウトが崩れる。

結果

ご覧のとおり、マークアップは簡素化されたのに、書き換えの外観はデフォルトのアプリケーションの外観とほぼ同じです
todo_4.png

Todo リスト アプリを構築する

このアプリを、概要ページを備えた ToDo リスト アプリに変換しましょう。

不要機能の削除

まず、「Counter(カウンター)」と「Weather(天気)」のページを削除しましょう。

ファイル削除

  1. Components/Pages/Counter.razorComponents/Pages/Weather.razorのファイルを選択
  2. 右クリックすると表示されるコンテキストメニューで Delete を選択
    image.png

リンク処理

Components/Layout/NavMenu.razorファイルの中で:

  1. 1番目の「Home(ホーム)」のリンクの名前を「Todo リスト」に変更
  2. 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>
    
  3. 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>
    
  4. 「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 リスト追加

  1. Components/Pages/Home.razorを開いて
  2. 相対 URL / とともに @page ディレクティブを追加。
  3. そのまま静的にレンダリングされないように、ページの対話機能を有効にします。 対話型サーバー レンダリング モードにすると、コンポーネントでサーバーからの UI イベントを処理できます。
  4. ページに HTML 要素を追加することを可能にする PageTitle コンポーネントを使用してページ タイトルを追加します。
    @page "/"
    @rendermode InteractiveServer
    
    <PageTitle>Todo</PageTitle>
    
    <h3>Todo</h3>
    
    @code {
    
    }
    
  5. プロジェクト(FluentBlazorTodoList)フォルダーを右クリックすると表示されるコンテキストメニューで Add > New Folder を選択
    todo_9.png
  6. モデル クラスの保持に使用するため、新しいフォルダーを「Models」に名付け
    todo_10.png
  7. Modelsフォルダーを右クリックすると表示されるコンテキストメニューで Add > New Item を選択
    todo_12.png
  8. Todo アイテムを表すクラスを保持するために、プロジェクト Modelsフォルダーに TodoItem.cs ファイルを追加
    todo_13.png
  9. 次の C# コードを挿入して TodoItem クラスを肉付け
    namespace Models;
    
    public class TodoItem
    {
        public string? Title { get; set; }
        public DateTime? DueDate { get; set; }
        public bool IsDone { get; set; }
    }
    
  10. Todo コンポーネントに戻り
  11. @namespace でモデルの TodoItem クラスを使用可能にする
  12. Todo アイテム用のフィールドを @code ブロックに追加(Todo コンポーネントでは、このフィールドを使って ToDo リストの状態を維持)
  13. リストを表す縦のスタック(FluentStack)を追加
  14. スタック(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();
    }
    
  15. リストのスタック(FluentStack)を別の縦のスタック(FluentStack)で囲み、その下のリストに新しい Todo 項目を追加する UI 要素を追加
  16. 追加 UI 要素をリストから視覚的に分離するため、 FluentDivider を追加
  17. 追加 UI のコントロールを含む横のスタック(FluentStack)を追加
  18. 新しい Todo 項目のタイトル用に FluentTextField を追加
  19. 新しい Todo 項目の期限用の FluentDatePicker を追加
  20. 追加アクションを実行するための「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 を空の文字列に設定し、newTodoDueDatenull に設定して、 FluentTextFieldFluentDatePicker の値をクリア

@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 属性を使用して、newTodoTimeFluentTextField 要素にバインド
28 . @bind-Value 属性を使用して、newTodoDueDateFluentDatePicker 要素にバインド

<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;
        }
    }
}
```

todo_14.png

About ページ追加

  1. Components/Pagesフォルダーを右クリックすると表示されるコンテキストメニューで Add > New Component を選択
    todo_7.png
  2. 「About.razor」を名付けて
  3. 「追加」を押下
    todo_8.png
  4. Components/Pages/About.razorを開いて
  5. 次のコードを挿入して「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 {
    
    }
    

完成

todo_15.png

参考

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?