Avaloniaにおけるメインウィンドウへの子ウィンドウ埋め込み実装

まず、Mainクラスに以下のコードを追加します。

ウィンドウデザイナーのコードは以下の通りです。

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:vm="clr-namespace:AvaloniaApplication.ViewModels"
             mc:Ignorable="d" d:DesignWidth="1920" d:DesignHeight="1020"
               Width="1920" Height="1020"
             x:Class="AvaloniaApplication.Views.MainShell"
               xmlns:conv="clr-namespace:AvaloniaApplication.Infrastructure"
             x:DataType="vm:PrimaryViewModel">

    <Design.DataContext>
        <vm:PrimaryViewModel />
    </Design.DataContext>


    <!-- ウィンドウコントロールコード開始-->
    <DockPanel>
        <!-- 左側ナビゲーションペイン-->
        <Border DockPanel.Dock="Left" Width="300" Background="Transparent">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="100"/>
                    <RowDefinition Height="*" />
                    <RowDefinition Height="100"/>
                </Grid.RowDefinitions>
                
                <!-- ロゴエリア -->
                <StackPanel Grid.Row="0" Orientation="Horizontal" Background="Transparent" Height="64" VerticalAlignment="Center">
                    <Image Source="/Assets/app-icon.ico" Height="45" Margin="10,0,8,0"/>
                    <TextBlock FontSize="24" Text="ナビゲーションメニュー" Foreground="Black" VerticalAlignment="Center" />
                </StackPanel>

                <!-- メニューエリア -->
                <ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
                    <StackPanel Margin="0,10,0,0" VerticalAlignment="Top" Orientation="Vertical">
                        <!-- コマンドバインディングとパラメータ渡し -->
                        <Button Command="{Binding NavigateCommand}" CommandParameter="Dashboard"
                                Content="&#xe88f; ダッシュボード" Margin="0,2,0,0" Classes="menu-btn"/>
                        <Button Command="{Binding NavigateCommand}" CommandParameter="Devices"
                                Content="&#xe893; デバイス" Margin="0,2,0,0" Classes="menu-btn"/>
                        <Button Command="{Binding NavigateCommand}" CommandParameter="Experiments"
                                Content="&#xe899; 実験" Margin="0,2,0,0" Classes="menu-btn"/>
                        <Button Command="{Binding NavigateCommand}" CommandParameter="Configuration"
                                Content="&#xe884; 設定" Margin="0,2,0,0" Classes="menu-btn"/>
                        <Button Command="{Binding NavigateCommand}" CommandParameter="Tags"
                                Content="&#xe882; タグ" Margin="0,2,0,0" Classes="menu-btn"/>
                        <Button Command="{Binding NavigateCommand}" CommandParameter="Tasks"
                                Content="&#xe875; タスク" Margin="0,2,0,0" Classes="menu-btn"/>
                        <Button Command="{Binding NavigateCommand}" CommandParameter="Users"
                                Content="&#xe895; ユーザー" Margin="0,2,0,0" Classes="menu-btn"/>
                        <Button Command="{Binding NavigateCommand}" CommandParameter="Miscellaneous"
                                Content="&#xe871; その他" Margin="0,2,0,0" Classes="menu-btn"/>
                    </StackPanel>
                </ScrollViewer>

                <!-- ユーザー情報エリア -->
                <StackPanel Grid.Row="2" Margin="10,0,0,20" Orientation="Horizontal" Background="Transparent" 
                            Height="64" VerticalAlignment="Bottom">
                    <Image Source="avares://AvaloniaApplication/Assets/Images/user.png" Height="45" Margin="20,0,8,0"/>
                    <TextBlock FontSize="24" Text="ユーザー名" Foreground="Black" VerticalAlignment="Center" />
                </StackPanel>
            </Grid>
        </Border>

        <!-- メインコンテンツエリア -->
        <Grid Background="Transparent" >
            <ContentControl
              Margin="0,0,0,0"
              Content="{Binding ActiveView}">
                <ContentControl.ContentTemplate>
                    <conv:ViewResolver />
                </ContentControl.ContentTemplate>
            </ContentControl>
        </Grid>
    </DockPanel>
</UserControl>

ウィンドウビューモデルのコードは以下の通りです。

using System;
using System.Windows.Input;
using AvaloniaApplication.ViewModels.Pages;
using AvaloniaApplication.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;

namespace AvaloniaApplication.ViewModels;

public partial class PrimaryViewModel : ObservableObject
{
    public ICommand NavigateCommand { get; }

    [ObservableProperty]
    private object? _activeView;

    public PrimaryViewModel()
    {
        NavigateCommand = new RelayCommand<string>(ExecuteNavigation);
        WeakReferenceMessenger.Default.Register<PrimaryViewModel, string>(this, HandleNavigationRequest);
    }

    private void HandleNavigationRequest(PrimaryViewModel recipient, string message)
    {
        ActiveView = message switch
        {
            NavigationKeys.Dashboard => new DashboardViewModel(),
            NavigationKeys.Devices => new DevicesViewModel(),
            NavigationKeys.Experiments => new ExperimentsViewModel(),
            NavigationKeys.Configuration => new ConfigurationViewModel(),
            NavigationKeys.Tags => new TagsViewModel(),
            NavigationKeys.Tasks => new TasksViewModel(),
            NavigationKeys.Users => new UsersViewModel(),
            NavigationKeys.Miscellaneous => new MiscellaneousViewModel(),
            _ => throw new ArgumentException($"不明なナビゲーションキー: {message}")
        };
    }

    private void ExecuteNavigation(string? destination)
    {
        if (string.IsNullOrEmpty(destination)) return;
        HandleNavigationRequest(this, destination);
    }
}
using System;
using Avalonia.Controls;
using Avalonia.Controls.Templates;

namespace AvaloniaApplication.Infrastructure;

/// <summary>
/// 埋め込み子ビューをメインウィンドウに統合するためのビューリゾルバー
/// </summary>
public class ViewResolver : IDataTemplate
{
    public Control? Build(object? data)
    {
        if (data is null) return null;
        
        var viewModelTypeName = data.GetType().Name;
        var viewName = viewModelTypeName.Replace("ViewModel", "");
        
        var fullTypeName = $"AvaloniaApplication.Views.{viewName}";
        var type = Type.GetType(fullTypeName);
        
        if (type != null)
        {
            return (Control)Activator.CreateInstance(type)!;
        }
        
        return new TextBlock { Text = $"ビューが見つかりません: {viewName}" };
    }

    public bool Match(object? data)
    {
        return data is not null;
    }
}
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace AvaloniaApplication.Models.Navigation;

public enum ModuleState
{
    Development,
    Testing,
    Production,
}

public partial class NavigationItem : ObservableObject
{
    [ObservableProperty]
    private string? _title;

    [ObservableProperty]
    private string? _iconGlyph;

    [ObservableProperty]
    private string? _navigationKey;

    [ObservableProperty]
    private ModuleState _state;

    [ObservableProperty]
    private bool _isDivider;

    public ObservableCollection<NavigationItem> SubItems { get; } = new();

    public RelayCommand? SelectCommand { get; set; }

    public NavigationItem()
    {
        SelectCommand = new RelayCommand(OnItemSelected);
    }

    private void OnItemSelected()
    {
        if (IsDivider || string.IsNullOrEmpty(NavigationKey)) return;
        
        // メッセンジャーを使用してナビゲーションリクエストを送信
        WeakReferenceMessenger.Default.Send(NavigationKey!);
    }
}
using System.Collections.ObjectModel;
using AvaloniaApplication.Models.Navigation;

namespace AvaloniaApplication.ViewModels.Navigation;

public class NavigationViewModel
{
    public ObservableCollection<NavigationItem> MenuItems { get; }

    public NavigationViewModel()
    {
        MenuItems = new ObservableCollection<NavigationItem>
        {
            new() { Title = "ダッシュボード", NavigationKey = NavigationKeys.Dashboard, State = ModuleState.Production },
            new() { Title = "デバイス", NavigationKey = NavigationKeys.Devices, State = ModuleState.Production },
            new() { Title = "実験", NavigationKey = NavigationKeys.Experiments, State = ModuleState.Production },
            new() { Title = "設定", NavigationKey = NavigationKeys.Configuration, State = ModuleState.Production },
            new() { Title = "タグ", NavigationKey = NavigationKeys.Tags, State = ModuleState.Production },
            new() { Title = "タスク", NavigationKey = NavigationKeys.Tasks, State = ModuleState.Production },
            new() { Title = "ユーザー", NavigationKey = NavigationKeys.Users, State = ModuleState.Production },
            new() { Title = "その他", NavigationKey = NavigationKeys.Miscellaneous, State = ModuleState.Development },
        };
    }
}

public static class NavigationKeys
{
    public const string Dashboard = "Dashboard";
    public const string Devices = "Devices";
    public const string Experiments = "Experiments";
    public const string Configuration = "Configuration";
    public const string Tags = "Tags";
    public const string Tasks = "Tasks";
    public const string Users = "Users";
    public const string Miscellaneous = "Miscellaneous";
}

実装結果の表示例は以下の通りです。

(本記事はメインウィンドウへの子ウィンドウ埋め込みについて解説しました。次回はユーザー認証管理について説明します。)

タグ: Avalonia XAML C# MVVM UIデザイン

6月18日 22:05 投稿