mcpField
A
avalonia-api

avalonia-api

github.com/sharex開発34248 回インストール今日更新
リポジトリ

avalonia-api について

Avalonia UI を使って .NET アプリを開発する人向けの、AXAML 実践リファレンスです。コントロール、レイアウト、バインディング、MVVM、スタイルに加え、XerahS 固有の指針も扱います。

主な機能

  1. 01AXAML のコントロールとレイアウト
  2. 02コンパイル時バインディングと MVVM
  3. 03スタイル、テーマ、リソース
  4. 04カスタムプロパティと添付プロパティ
  5. 05レスポンシブおよびスクロール可能なレイアウト

活用シーン

  1. 01ビュー作成時の Avalonia UI パターンの確認
  2. 02レスポンシブ画面のレイアウト選択
  3. 03バインディングと MVVM 規約の適用
  4. 04スクロールやコンテキストメニューの問題調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/sharex/xerahs --skill avalonia-api

インストール後、Claude Code で/avalonia-apiを入力して読み込まれたか確認します。

詳細

Avalonia UI Framework - 完全な API とベストプラクティスガイド

対象フレームワーク:.NET 10.0+
ファイル拡張子:.axaml(Avalonia XAML)
公式ドキュメント:https://docs.avaloniaui.net/


目次

  1. AXAML の基礎
  2. コントロールと UI 要素
  3. レイアウトシステム
  4. データバインディング
  5. CommunityToolkit.Mvvm を使った MVVM パターン
  6. スタイルとテーマ
  7. 依存プロパティと添付プロパティ
  8. カスタムコントロール
  9. コントロールテンプレート
  10. リソースとコンバーター
  11. イベントとコマンド
  12. ナビゲーション
  13. クロスプラットフォームのパターン
  14. パフォーマンスとベストプラクティス
  15. 開発者ツール
  16. 避けるべきよくある間違い
  17. XerahS でよく使われるパターン

AXAML の基礎

ファイル構造

すべての .axaml ファイルは、次の標準的な構造に従います。

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:YourApp.ViewModels"
        x:Class="YourApp.Views.MainWindow"
        x:DataType="vm:MainViewModel"
        x:CompileBindings="True">
    
    
    
</Window>
必須の名前空間宣言
名前空間用途必須
xmlns="https://github.com/avaloniaui"Avalonia のコアコントロール✅ 常に
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"XAML 言語機能✅ 常に
xmlns:vm="using:YourNamespace.ViewModels"ViewModel 参照⚠️ MVVM の場合
xmlns:local="using:YourNamespace"ローカルの型/コントロール🔹 必要に応じて
カスタム名前空間の構文

<xmlns:myAlias1="using:AppNameSpace.MyNamespace">


<xmlns:myAlias2="clr-namespace:OtherAssembly.MyNameSpace;assembly=OtherAssembly">


<xmlns:controls="using:XerahS.UI.Controls">
コントロールのコンテンツと属性

<Button>Hello World!</Button>


<Button Content="Hello World!" />


<Button>
    <Button.Content>
        <StackPanel>
            <TextBlock Text="Complex" />
            <TextBlock Text="Content" />
        </StackPanel>
    </Button.Content>
</Button>

コントロールと UI 要素

よく使われる組み込みコントロール
入力コントロール
  • TextBox:単一行/複数行のテキスト入力
  • PasswordBox:マスクされたパスワード入力
  • NumericUpDown:増減操作が可能な数値入力
  • CheckBox:ブール値の切り替え
  • RadioButton:相互排他的な選択
  • Slider:連続範囲の選択
  • ComboBox:ドロップダウン選択
  • AutoCompleteBox:候補を表示するテキスト入力
  • DatePicker:日付の選択
  • TimePicker:時刻の選択
  • ColorPicker:色の選択
表示コントロール
  • TextBlock:読み取り専用のテキスト表示
  • Label:アクセスキーに対応したテキスト
  • Image:画像の表示
  • Border:コンテンツの周囲に表示する枠線
  • ContentControl:単一コンテンツのコンテナー
レイアウトパネル
  • Panel:基本的なコンテナー(利用可能な領域を埋めます)
  • StackPanel:縦方向/横方向のスタック
  • Grid:行/列のグリッドレイアウト
  • DockPanel:端にドッキングするレイアウト
  • Canvas:絶対位置指定
  • WrapPanel:折り返しフローレイアウト
  • RelativePanel:相対位置指定
  • UniformGrid:同じサイズのセル
リストとコレクション
  • ListBox:選択可能なリスト
  • ListView:表示をカスタマイズできるリスト
  • TreeView:階層型ツリー
  • DataGrid:列を含む表形式データ
  • ItemsControl:コレクション表示の基本コントロール
  • ItemsRepeater:仮想化コレクション
コンテナー
  • Window:最上位のウィンドウ
  • UserControl:再利用可能な UI コンポーネント
  • ScrollViewer:スクロール可能なコンテンツ
  • Expander:折りたたみ可能なコンテンツ
  • TabControl:タブ付きインターフェイス
  • SplitView:ハンバーガーメニューのパターン
ボタン
  • Button:標準ボタン
  • ToggleButton:2 状態のボタン
  • RepeatButton:自動で繰り返すボタン
  • RadioButton:相互排他的なボタン
  • SplitButton:ドロップダウン付きボタン
  • DropDownButton:ドロップダウンメニューボタン
高度なコントロール
  • Carousel:コンテンツを循環表示します
  • MenuFlyout:フライアウトベースの最新のコンテキストメニュー(⚠️ FluentAvalonia と併用してください)
  • ContextFlyout:右クリックメニューのコンテナー(⚠️ ContextMenu より推奨)
  • ContextMenu:従来の右クリックメニュー(⚠️ FluentAvalonia テーマでは使用しないでください)
  • Menu:メニューバー
  • ToolTip:ホバー時の情報表示
  • Flyout:ポップアップオーバーレイ
  • Calendar:カレンダー表示

レイアウトシステム

ScrollViewer の有効化要件

ScrollViewer は、Measure パス中に親から有限の(上限のある)高さ制約を受け取った場合にのみ有効になり、スクロールバーが表示されてスクロール可能になります。祖先コントロールのいずれかがチェーンの下位に ∞(無限大)を渡すと、ScrollViewer はスクロールできません。

XerahS のレイアウトで無限の高さが発生する一般的な原因と解決策:

根本原因スクロールが機能しなくなる理由解決策
2 列シェルとしての SplitViewContentControl を継承し、既定の VerticalContentAlignment=Top により ContentPresenter が高さ ∞ を渡すGrid ColumnDefinitions="auto,*" に置き換える
ページホストとしての TransitioningContentControl内部のアニメーション Panel が Measure 中に高さ ∞ を渡すContentControl HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" に置き換える
VerticalContentAlignment="Stretch" を指定していない TabControl内部の ContentPresenter はテンプレートで {TemplateBinding VerticalContentAlignment} を使用し、既定の Top によりタブ本体へ ∞ が渡されるTabControl に VerticalContentAlignment="Stretch" を追加する
ScrollViewer Padding="N"ビューポート が狭くなる一方、スクロール範囲には N が加算されないため、コンテンツ下部の N ピクセルに永久にアクセスできないScrollViewer の Padding を削除し、代わりに内側の StackPanel/Grid に Margin="N" を追加する

スクロール可能な設定ページの標準パターン:


<TabControl VerticalContentAlignment="Stretch">
    <TabItem Header="General">
        <ScrollViewer>
            <StackPanel Spacing="24" Margin="24">
                
            </StackPanel>
        </ScrollViewer>
    </TabItem>
</TabControl>


<TabControl>  
    <TabItem Header="General">
        <ScrollViewer Padding="24">  
            <StackPanel Spacing="24">
            </StackPanel>
        </ScrollViewer>
    </TabItem>
</TabControl>
レイアウトの処理

Avalonia は 2 パスのレイアウトシステムを使用します。

  1. Measure パス:各コントロールの希望サイズを決定します
  2. Arrange パス:利用可能な領域内にコントロールを配置します
Control → Measure → MeasureOverride → DesiredSize
       → Arrange → ArrangeOverride → FinalSize
パネルの比較
パネル用途パフォーマンス複雑さ
Panel利用可能な領域を埋める⚡ 最良シンプル
StackPanel直線的に積み重ねる⚡ 良好シンプル
Canvas絶対位置指定⚡ 良好シンプル
DockPanel端にドッキングする✅ 良好中程度
Grid複雑なレイアウト⚠️ 普通複雑
RelativePanel相対的な制約⚠️ 普通複雑

推奨事項:パフォーマンスを向上させるには、行や列のない Grid の代わりに Panel を使用してください。

一般的なレイアウトプロパティ
<Control Width="100"                    
         Height="50"                     
         MinWidth="50"                   
         MaxWidth="200"                  
         Margin="10,5,10,5"              
         Padding="5"                     
         HorizontalAlignment="Stretch"   
         VerticalAlignment="Center"      
         HorizontalContentAlignment="Center"  
         VerticalContentAlignment="Center" />
Grid レイアウト
<Grid RowDefinitions="Auto,*,50"           
      ColumnDefinitions="200,*,Auto">      
    
    <TextBlock Grid.Row="0" Grid.Column="0" Text="Header" />
    <Border Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" />
    
    
    <Grid ColumnDefinitions="*,2*,*">  
        
    </Grid>
</Grid>
DockPanel レイアウト
<DockPanel LastChildFill="True">
    <Menu DockPanel.Dock="Top" />
    <StatusBar DockPanel.Dock="Bottom" />
    <TreeView DockPanel.Dock="Left" Width="200" />
    
    
    <ContentControl Content="{Binding CurrentView}" />
</DockPanel>
StackPanel レイアウト
<StackPanel Orientation="Vertical"    
            Spacing="10">              
    <TextBlock Text="Item 1" />
    <TextBlock Text="Item 2" />
    <TextBlock Text="Item 3" />
</StackPanel>
GridSplitter(サイズ変更可能なペイン)

Grid に異なるコンテンツ領域(サイドバーとメイン領域、上下の分割など)がある場合は、ユーザーがペインのサイズを変更できるように GridSplitter を追加します。スプリッター用に、幅の狭い列または行(3~6 px)を確保してください。


<Grid ColumnDefinitions="250, 4, *">
    <TreeView Grid.Column="0" />
    <GridSplitter Grid.Column="1" ResizeDirection="Columns" />
    <ContentControl Grid.Column="2" Content="{Binding Detail}" />
</Grid>

主なルール:

  • ResizeDirection は軸に合わせます。列のスプリッターには Columns、行のスプリッターには Rows を指定します。
  • 隣接するセルに MinWidth/MaxWidth(または MinHeight/MaxHeight)を設定し、幅または高さが 0 になるのを防ぎます。
  • Grid にサイズ変更可能なコンテンツ領域が 2 つ以上ある場合は、必ず GridSplitter を使用してください。
レスポンシブレイアウト

Avalonia には 4 つのアプローチがあります。固定ピクセルレイアウトより、これらを優先してください。

コンテナークエリ(再利用可能なコンポーネントに推奨)

ウィンドウではなく、祖先コントロールのサイズに応じて動作します。コントロールのサイズ変更にリアルタイムで対応します。

<Border Container.Name="main" Container.Sizing="Width">
    <Panel.Styles>
        <ContainerQuery Name="main" Query="max-width:600">
            <Style Selector="StackPanel#sidebar">
                <Setter Property="IsVisible" Value="False" />
            </Style>
        </ContainerQuery>
    </Panel.Styles>
    
</Border>
  • 条件を組み合わせます:Query="min-width:400 and max-width:800"
  • Container.Sizing:Width、Height、または Width Height
OnFormFactor(静的なプラットフォーム検出)

起動時に一度だけ解決されます。ウィンドウのサイズ変更には対応しない、デスクトップとモバイルの違いに使用します。

<Grid ColumnDefinitions="{OnFormFactor Desktop='250,*', Mobile='*'}">
    <Border IsVisible="{OnFormFactor Desktop=True, Mobile=False}" />
</Grid>
リフローするパネル(自動適応)
<ItemsRepeater ItemsSource="{Binding Items}">
    <ItemsRepeater.Layout>
        <UniformGridLayout MinItemWidth="200" MinItemHeight="150" />
    </ItemsRepeater.Layout>
</ItemsRepeater>
用途の使い分け
シナリオアプローチ
再利用可能なコンポーネントが自身のサイズに応じて調整されるコンテナークエリ
デスクトップとモバイルのレイアウト(静的)OnFormFactor
折り返して配置されるカード/タイルWrapPanel または UniformGridLayout
ブレークポイントで複数のプロパティを複雑に変更するブレークポイント ViewModel(ウィンドウサイズを監視し、bool プロパティを公開する)

主なルール:

  • 手動でサイズ変更イベントを処理するより、コンテナークエリを優先してください。
  • Grid の定義では、必ず星サイズ(*)と Auto を使用し、コンテンツ領域に固定ピクセル幅を指定しないでください。
  • Visibility 列挙型は bool IsVisible に置き換えられました。非表示でも領域を占有させるには Opacity="0" を使用してください。
  • VisualStateManager は使用しないでください。代わりに疑似クラスセレクターまたはコンテナークエリを使用してください。

データバインディング

バインディング構文

<TextBlock Text="{Binding PropertyName}" />


<TextBlock Text="{Binding Person.Name}" />


<TextBox Text="{Binding Name, Mode=TwoWay}" />



<TextBlock x:Name="MyText" Text="Hello" />
<TextBox Text="{Binding #MyText.Text}" />


<TextBlock Text="{Binding $parent[Window].DataContext.Title}" />


<TextBlock Text="{Binding Name, FallbackValue='Unknown'}" />


<TextBlock Text="{Binding Price, StringFormat='${0:F2}'}" />


<TextBlock Text="{Binding IsEnabled, Converter={StaticResource BoolToStringConverter}}" />

重要:

  • #ElementName.Property は Avalonia のバインディングパス拡張機能であり、Avalonia の Binding/コンパイル済みバインディングと併用してください。
  • {ReflectionBinding #SomeElement.SomeCommand} と記述しないでください。ReflectionBinding は #... トークンを通常のパスセグメントとして扱うため、実行時にコマンド/プロパティの検索に失敗する場合があります。
コンパイル済みバインディング(推奨)

コンパイル済みバインディングは、コンパイル時の安全性と優れたパフォーマンスを提供します。


<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>


<Window x:DataType="vm:MainViewModel"
        x:CompileBindings="True">
    
    
    <TextBox Text="{Binding FirstName}" />
    <TextBox Text="{Binding LastName}" />
    
    
    <Button Command="{ReflectionBinding DynamicCommandName}" />
</Window>


<TextBox Text="{CompiledBinding FirstName}" />

ベストプラクティス:型安全性とパフォーマンスのため、常にコンパイル済みバインディングを使用してください。ReflectionBinding は、型指定できない真に動的なパスに限って使用し、#ElementName 構文とは決して併用しないでください。

DataContext の型推論(v11.3+)
<Window x:Name="MyWindow"
        x:DataType="vm:TestDataContext">
    
    
    <TextBlock Text="{Binding #MyWindow.DataContext.StringProperty}" />
    <TextBlock Text="{Binding $parent[Window].DataContext.StringProperty}" />
    
    
</Window>
マルチバインディング
<TextBlock>
    <TextBlock.Text>
        <MultiBinding StringFormat="{}{0} {1}">
            <Binding Path="FirstName" />
            <Binding Path="LastName" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>
要素バインディング

<Slider x:Name="volumeSlider" Minimum="0" Maximum="100" Value="50" />
<TextBlock Text="{Binding #volumeSlider.Value}" />


<Border BorderThickness="{Binding $parent.IsMouseOver, 
                                  Converter={StaticResource BoolToThicknessConverter}}" />

CommunityToolkit.Mvvm を使った MVVM パターン

⚠️ XerahS は ReactiveUI ではなく、CommunityToolkit.Mvvm を使用します。 ReactiveUI または Avalonia.ReactiveUI パッケージを追加したり、参照したりしないでください。

パッケージのインストール
dotnet add package CommunityToolkit.Mvvm
ViewModel の基本パターン
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

public partial class MainViewModel : ObservableObject
{
    [ObservableProperty]
    private string _firstName = string.Empty;

    [ObservableProperty]
    private bool _isLoading;

    [RelayCommand(CanExecute = nameof(CanSave))]
    private async Task SaveAsync()
    {
        IsLoading = true;
        try
        {
            await Task.Delay(1000); // Simulate save
        }
        finally
        {
            IsLoading = false;
        }
    }

    private bool CanSave() => !string.IsNullOrWhiteSpace(FirstName);
}

ソースジェネレーターに関する注意事項:

  • private フィールドに [ObservableProperty] を付けると、パブリックな PascalCase プロパティと INotifyPropertyChanged 通知が生成されます(_firstName → FirstName)。
  • [RelayCommand] を付けると、SaveAsyncCommand(IAsyncRelayCommand)が自動的に生成されます。
  • ソースジェネレーターを機能させるには、クラスを partial にする必要があります。
  • [RelayCommand(CanExecute = nameof(...))] は実行可否を自動的に連携します。条件が変化したときは、SaveAsyncCommand.NotifyCanExecuteChanged() を呼び出してください。
アーキテクチャのレイヤー構成
  • ビュー(AXAML):ビジュアルの構成のみを担当します。コードビハインドに記述するビジネスロジックは InitializeComponent() 以外にありません。
  • ViewModel:状態、コマンド、処理の調整を担当します。UI フレームワークに依存せず、単体テストが可能です。DI を介してサービスを連携させます。
  • サービス/ドメイン:ビジネスロジックとデータアクセスを担当し、Avalonia の型を参照しません。
ビューの設定(コードビハインド)
public partial class MainView : UserControl
{
    public MainView()
    {
        InitializeComponent();
        DataContext = new MainViewModel(); // Or resolve via ViewLocator / DI
    }
}
XAML でのコマンドバインディング

<Button Content="Save" Command="{Binding SaveAsyncCommand}" />


<Button Content="Delete"
        Command="{Binding DeleteCommand}"
        CommandParameter="{Binding SelectedItem}" />
プロパティ変更時のコールバック
[ObservableProperty]
private string _name = string.Empty;

// Source-generated partial method — called automatically when Name changes
partial void OnNameChanged(string value)
{
    // React to change
}
手動でのプロパティ通知(ソースジェネレーターを利用できない場合)
public partial class MyViewModel : ObservableObject
{
    private string _title = string.Empty;

    public string Title
    {
        get => _title;
        set => SetProperty(ref _title, value);
    }
}

スタイルとテーマ

スタイルの種類

Avalonia には、次の 3 つのスタイル機構があります。

  1. スタイル:CSS に似ており、型またはクラスでコントロールを対象にします
  2. コントロールテーマ:完全なビジュアルテンプレートです(WPF のスタイルに相当)
  3. コンテナークエリ:コンテナーのサイズに基づくレスポンシブスタイルです
基本的なスタイル
<Window.Styles>
    
    <Style Selector="TextBlock">
        <Setter Property="Foreground" Value="White" />
        <Setter Property="FontSize" Value="14" />
    </Style>

    
    <Style Selector="TextBlock.header">
        <Setter Property="FontSize" Value="24" />
        <Setter Property="FontWeight" Value="Bold" />
    </Style>

    
    <Style Selector="Button:pointerover">
        <Setter Property="Background" Value="LightBlue" />
    </Style>

    
    <Style Selector="StackPanel > Button">
        <Setter Property="Margin" Value="5" />
    </Style>
</Window.Styles>


<TextBlock Classes="header" Text="Title" />
疑似クラス
<Style Selector="Button:pointerover">         
<Style Selector="Button:pressed">             
<Style Selector="Button:disabled">            
<Style Selector="ListBoxItem:selected">       
<Style Selector="TextBox:focus">              
<Style Selector="CheckBox:checked">           
<Style Selector="ToggleButton:unchecked">     
スタイルセレクター

<Style Selector="StackPanel TextBlock">


<Style Selector="StackPanel > TextBlock">


<Style Selector="Button.primary:pointerover">


<Style Selector="Button, ToggleButton">


<Style Selector="Button:not(.primary)">


<Style Selector="Button /template/ ContentPresenter">
リソース
<Window.Resources>
    
    <SolidColorBrush x:Key="PrimaryBrush" Color="#007ACC" />
    
    
    <x:Double x:Key="StandardSpacing">10</x:Double>
    
    
    <LinearGradientBrush x:Key="GradientBrush" StartPoint="0%,0%" EndPoint="0%,100%">
        <GradientStop Color="#FF0000" Offset="0" />
        <GradientStop Color="#00FF00" Offset="1" />
    </LinearGradientBrush>
</Window.Resources>


<Button Background="{StaticResource PrimaryBrush}" 
        Margin="{StaticResource StandardSpacing}" />


<Button Background="{DynamicResource PrimaryBrush}" />
テーマ

<Application.Styles>
    
    <FluentTheme />
    
    
    <SimpleTheme />
    
    
    <StyleInclude Source="/Styles/CustomStyles.axaml" />
</Application.Styles>

依存プロパティと添付プロパティ

StyledProperty(依存プロパティ)
public class MyControl : ContentControl
{
    // Define the property
    public static readonly StyledProperty<string> TitleProperty =
        AvaloniaProperty.Register<MyControl, string>(
            nameof(Title), 
            defaultValue: string.Empty);

    // CLR wrapper
    public string Title
    {
        get => GetValue(TitleProperty);
        set => SetValue(TitleProperty, value);
    }

    // React to property changes
    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
    {
        base.OnPropertyChanged(change);

        if (change.Property == TitleProperty)
        {
            // Handle change
            var oldValue = (string?)change.OldValue;
            var newValue = (string?)change.NewValue;
        }
    }
}
添付プロパティ
public class MyPanel : Panel
{
    // Define attached property
    public static readonly AttachedProperty<int> ColumnProperty =
        AvaloniaProperty.RegisterAttached<MyPanel, Control, int>(
            "Column",
            defaultValue: 0);

    // Getters/Setters
    public static int GetColumn(Control element)
        => element.GetValue(ColumnProperty);

    public static void SetColumn(Control element, int value)
        => element.SetValue(ColumnProperty, value);
}

<local:MyPanel>
    <Button local:MyPanel.Column="0" Content="First" />
    <Button local:MyPanel.Column="1" Content="Second" />
</local:MyPanel>
一般的な添付プロパティ

<Button Grid.Row="0" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="3" />


<Menu DockPanel.Dock="Top" />


<Rectangle Canvas.Left="50" Canvas.Top="100" />


<Button ToolTip.Tip="Click me!" />


<Border>
    <Border.ContextFlyout>
        <MenuFlyout>
            <MenuItem Header="Copy" />
            <MenuItem Header="Paste" />
        </MenuFlyout>
    </Border.ContextFlyout>
</Border>

⚠️ XerahS 固有:ContextMenu と ContextFlyout

FluentAvalonia Theme に関する重大な問題

問題:標準の ContextMenu コントロールは、FluentAvaloniaTheme では正しく描画されません。レガシーな Popup ウィンドウを使用するため、スタイルが完全には適用されず、スタイルが適用されない、または表示されないことがあります。

解決策:✅ ContextMenu ではなく、必ず MenuFlyout を使った ContextFlyout を使用してください。


<Border.ContextMenu>
    <ContextMenu>
        <MenuItem Header="Action" Command="{Binding MyCommand}"/>
    </ContextMenu>
</Border.ContextMenu>


<Border.ContextFlyout>
    <MenuFlyout>
        <MenuItem Header="Action" Command="{Binding MyCommand}"/>
    </MenuFlyout>
</Border.ContextFlyout>
DataTemplate 内のフライアウトでのバインディング

問題:DataTemplate 内で ContextFlyout または ContextMenu を使用する場合、親の ViewModel へのバインディングが機能しません。Popup/Flyout は 別のビジュアルツリーに存在し、DataTemplate の階層から切り離されているためです。

解決策:$parent[UserControl].DataContext を使用して、メインビューの DataContext にアクセスします。

<DataTemplate x:DataType="local:MyItem">
    <Border>
        <Border.ContextFlyout>
            <MenuFlyout>
                
                <MenuItem Header="Edit" 
                          Command="{Binding $parent[UserControl].DataContext.EditCommand}"
                          CommandParameter="{Binding}"/>
            </MenuFlyout>
        </Border.ContextFlyout>
        
        <TextBlock Text="{Binding Name}" />
    </Border>
</DataTemplate>

主なポイント:

  • $parent[UserControl].DataContext を使用すると、フライアウト内から View の ViewModel にアクセスできます
  • CommandParameter="{Binding}" は、現在のデータ項目(DataTemplate の DataContext)を渡します
  • 共通のフライアウトは UserControl.Resources で定義し、{StaticResource} を使って参照します

カスタムコントロール

カスタムコントロール(自ら描画するもの)
public class CircleControl : Control
{
    public static readonly StyledProperty<IBrush?> FillProperty =
        AvaloniaProperty.Register<CircleControl, IBrush?>(nameof(Fill));

    public IBrush? Fill
    {
        get => GetValue(FillProperty);
        set => SetValue(FillProperty, value);
    }

    public override void Render(DrawingContext context)
    {
        var renderSize = Bounds.Size;
        var center = new Point(renderSize.Width / 2, renderSize.Height / 2);
        var radius = Math.Min(renderSize.Width, renderSize.Height) / 2;

        context.DrawEllipse(Fill, null, center, radius, radius);
    }
}
テンプレート化コントロール(外観を持たないもの)
public class MyButton : TemplatedControl
{
    public static readonly StyledProperty<string> TextProperty =
        AvaloniaProperty.Register<MyButton, string>(nameof(Text));

    public string Text
    {
        get => GetValue(TextProperty);
        set => SetValue(TextProperty, value);
    }

    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
    {
        base.OnApplyTemplate(e);
        
        // Find template parts
        var presenter = e.NameScope.Find<ContentPresenter>("PART_ContentPresenter");
    }
}
UserControl(複合コントロール)

<UserControl xmlns="https://github.com/avaloniaui"
             x:Class="MyApp.Controls.MyUserControl">
    <StackPanel>
        <TextBlock Text="{Binding Title}" />
        <Button Content="Click Me" />
    </StackPanel>
</UserControl>
// MyUserControl.axaml.cs
public partial class MyUserControl : UserControl
{
    public MyUserControl()
    {
        InitializeComponent();
    }
}

コントロールテンプレート

ControlTheme を定義する
<ControlTheme x:Key="CustomButtonTheme" TargetType="Button">
    <Setter Property="Background" Value="Blue" />
    <Setter Property="Foreground" Value="White" />
    <Setter Property="Padding" Value="10,5" />
    <Setter Property="Template">
        <ControlTemplate>
            <Border Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    CornerRadius="5">
                <ContentPresenter Name="PART_ContentPresenter"
                                  Content="{TemplateBinding Content}"
                                  Padding="{TemplateBinding Padding}"
                                  HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                                  VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
            </Border>
        </ControlTemplate>
    </Setter>
    
    
    <Style Selector="^:pointerover /template/ Border">
        <Setter Property="Background" Value="LightBlue" />
    </Style>
    
    <Style Selector="^:pressed /template/ Border">
        <Setter Property="Background" Value="DarkBlue" />
    </Style>
</ControlTheme>


<Button Theme="{StaticResource CustomButtonTheme}" Content="Custom" />
テンプレートパーツ
[TemplatePart("PART_ContentPresenter", typeof(ContentPresenter))]
public class MyTemplatedControl : TemplatedControl
{
    private ContentPresenter? _presenter;

    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
    {
        base.OnApplyTemplate(e);
        _presenter = e.NameScope.Find<ContentPresenter>("PART_ContentPresenter");
    }
}

リソースとコンバーター

値コンバーター
public class BoolToVisibilityConverter : IValueConverter
{
    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        if (value is bool boolValue)
            return boolValue ? true : false; // Or specific logic
        return false;
    }

    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        if (value is bool visible)
            return visible;
        return false;
    }
}
<Window.Resources>
    <local:BoolToVisibilityConverter x:Key="BoolToVisConverter" />
</Window.Resources>

<Border IsVisible="{Binding IsActive, Converter={StaticResource BoolToVisConverter}}" />
組み込みコンバーター

<Button IsEnabled="{Binding !IsLoading}" />


<TextBlock IsVisible="{Binding MyObject, Converter={x:Static ObjectConverters.IsNotNull}}" />


<TextBlock Text="{Binding Count, StringFormat='Items: {0}'}" />

イベントとコマンド

イベントハンドラー
<Button Click="OnButtonClick" Content="Click" />
private void OnButtonClick(object? sender, RoutedEventArgs e)
{
    // Handle event
}
コマンド(MVVM)
<Button Command="{Binding SaveCommand}" 
        CommandParameter="{Binding CurrentItem}"
        Content="Save" />
// Using CommunityToolkit.Mvvm
[RelayCommand]
private void Save(object? parameter)
{
    // Execute command
}

// Async command
[RelayCommand]
private async Task SaveAsync()
{
    await Task.Delay(100);
}

// Command with can-execute
[RelayCommand(CanExecute = nameof(CanDelete))]
private void Delete() { }

private bool CanDelete() => SelectedItem is not null;
ルーティングイベント
public static readonly RoutedEvent<RoutedEventArgs> MyEvent =
    RoutedEvent.Register<MyControl, RoutedEventArgs>(
        nameof(MyEvent), 
        RoutingStrategies.Bubble);

public event EventHandler<RoutedEventArgs> MyEvent
{
    add => AddHandler(MyEvent, value);
    remove => RemoveHandler(MyEvent, value);
}

// Raise event
RaiseEvent(new RoutedEventArgs(MyEvent));

クロスプラットフォームのパターン

プラットフォームの検出
if (OperatingSystem.IsWindows())
{
    // Windows-specific code
}
else if (OperatingSystem.IsMacOS())
{
    // macOS-specific code
}
else if (OperatingSystem.IsLinux())
{
    // Linux-specific code
}
プラットフォーム固有のリソース
<Application.Styles>
    <StyleInclude Source="/Styles/Common.axaml" />
    
    
    <OnPlatform>
        <On Options="Windows">
            <StyleInclude Source="/Styles/Windows.axaml" />
        </On>
        <On Options="macOS">
            <StyleInclude Source="/Styles/macOS.axaml" />
        </On>
    </OnPlatform>
</Application.Styles>
設計原則
  1. .NET Standard を使用する:ビジネスロジックは .NET Standard ライブラリに記述します
  2. MVVM パターン:UI とロジックを分離します
  3. Avalonia の描画:Avalonia の描画ベースの UI(ネイティブコントロールではなく)を活用します
  4. プラットフォーム抽象化:プラットフォーム固有の機能にはインターフェイスを使用します
  5. レスポンシブデザイン:コンテナークエリとアダプティブレイアウトを使用します

パフォーマンスとベストプラクティス

パフォーマンスのヒント
  1. 行や列が不要な場合は、Grid より Panel を使用します
  2. コンパイル済みバインディングをグローバルに有効にします
  3. 大きなリストには仮想化(ItemsRepeater、VirtualizingStackPanel)を使用します
  4. ビジュアルツリーの深いネストを避けます
  5. アニメーションでは Margin の代わりに RenderTransform を使用します
  6. ItemsRepeater で DataTemplate を再利用します
  7. プロパティの変更をまとめて適用し、レイアウトパスを最小限に抑えます
メモリ管理

CommunityToolkit.Mvvm のソース生成プロパティは PropertyChanged を自動的に発生させます。そのため、[ObservableProperty] フィールドのサブスクリプションを手動で破棄する必要はありません。外部のオブザーバブルやイベントをサブスクライブする場合は、IDisposable を実装します。

public partial class MyViewModel : ObservableObject, IDisposable
{
    private readonly IDisposable _subscription;

    public MyViewModel(IMyService service)
    {
        _subscription = service.ValueStream.Subscribe(OnValue);
    }

    private void OnValue(string v) { }

    public void Dispose() => _subscription.Dispose();
}
null 安全性

XerahS では 厳格な nullable 参照型を使用します。常に次の点を守ってください。

// Enable in .csproj
<Nullable>enable</Nullable>

// Handle nullability properly
public string? Title { get; set; }  // Nullable
public string Name { get; set; } = string.Empty;  // Non-nullable with default
AOT とトリミングへの配慮

モバイル(iOS/Android)、WebAssembly、または NativeAOT を対象とするアプリでは、次の点に注意してください。

  • コンパイル済みバインディングを優先します(すでにデフォルトです)。リフレクションを回避できます。
  • ViewModel/サービスの解決に Activator.CreateInstance を使用しないでください。AOT をサポートする DI コンテナー(例:ソース生成された登録を備えた Microsoft.Extensions.DependencyInjection)を使用します。
  • ホットパスで Type.GetType()、PropertyInfo.SetValue()、または実行時リフレクションを使用しないでください。
  • リフレクションが避けられない場合は、[DynamicallyAccessedMembers] 注釈を使用します。
  • {ReflectionBinding} は必要な場合に限り使用してください。これはトリミングに安全ではなく、Avalonia の #ElementName パスと組み合わせないでください。

開発者ツール

⚠️ 旧式の Avalonia.Diagnostics パッケージは 非推奨です。絶対に使用しないでください。

migrate_diagnostics MCP ツールを使用して、任意のプロジェクトで開発者ツールをセットアップまたは移行します。次の処理を行います。

  • Avalonia.Diagnostics の削除
  • AvaloniaUI.DiagnosticsSupport のインストール
  • Program.cs と App.axaml.cs の設定
  • 古い API 呼び出しの置き換え
# Install the global developer tools CLI
dotnet tool install --global AvaloniaUI.DeveloperTools

ルール:必ず AvaloniaUI.DiagnosticsSupport と AvaloniaUI.DeveloperTools .NET グローバルツールを使用してください。


避けるべきよくある間違い

#間違い正しい方法
1.xaml 拡張子を使用する.axaml を使用する
2WPF 名前空間を使用するhttps://github.com/avaloniaui を使用する
3Style.Triggers / DataTrigger / EventTrigger を使用する疑似クラスセレクターを使用する
4DependencyProperty を使用するStyledProperty または DirectProperty を使用する
5Style x:Key="..." を使用するスタイルクラスとセレクターを使用する
6HierarchicalDataTemplate を使用するTreeDataTemplate を使用する
7pack://application:,,,/ URI を使用するavares://AssemblyName/path を使用する
8Visibility 列挙型を使用するbool IsVisible を使用する(非表示でもスペースを確保する場合は Opacity="0")
9x:DataType が設定されていないコンパイル済みバインディングでは必ず設定する
10デフォルトで {ReflectionBinding} を使用するコンパイル済みバインディングをグローバルに有効にする
11{ReflectionBinding #MyRoot.SomeCommand} を使用する{Binding #MyRoot.SomeCommand}(またはコンパイル済みバインディングのスコープ)を使用する
12Avalonia.Diagnostics を使用するAvaloniaUI.DiagnosticsSupport と AvaloniaUI.DeveloperTools を使用する
13ReactiveUI / Avalonia.ReactiveUI を使用するCommunityToolkit.Mvvm を使用する
14ContentControl.Content を手動で切り替えてページを表示するNavigationPage / TabbedPage / DrawerPage を使用する
15VisualStateManager を使用する疑似クラスセレクターまたはコンテナークエリを使用する
16LayoutTransform を使用するLayoutTransformControl でラップする
17Dispatcher.Invoke() を使用するDispatcher.UIThread.InvokeAsync() を使用する
18FluentAvalonia で ContextMenu を使用するContextFlyout と MenuFlyout を使用する
19StackPanel の周囲に ScrollViewer Padding="N" を設定するパディングを内側の要素に移動する:<StackPanel Margin="N"> — ScrollViewer.Padding が縮めるのはビューポートのみで、スクロール範囲は縮まりません。そのため、下端のコンテンツに永久に到達できなくなります
202 列のペインなしシェルに SplitView を使用するGrid ColumnDefinitions="auto,*" を使用する — SplitView は ContentControl を継承しており、既定の VerticalContentAlignment=Top によって子要素に高さ ∞ が渡され、ネストされた ScrollViewer が正しく動作しなくなります
21ScrollViewer を含むページホストとして TransitioningContentControl を使用する通常の ContentControl HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" を使用する — TransitioningContentControl のアニメーションパネルは、測定時に高さ ∞ を渡します
22スクロール可能なタブを、既定の VerticalContentAlignment のまま TabControl で囲むTabControl に VerticalContentAlignment="Stretch" を設定する — 内側の ContentPresenter のテンプレートは {TemplateBinding VerticalContentAlignment} を使用し、既定値は Top のため、タブのコンテンツに高さ ∞ が渡されます

XerahS でよく使われるパターン

StyledProperty パターン
public static readonly StyledProperty<object?> SelectedObjectProperty =
    AvaloniaProperty.Register<PropertyGrid, object?>(nameof(SelectedObject));

public object? SelectedObject
{
    get => GetValue(SelectedObjectProperty);
    set => SetValue(SelectedObjectProperty, value);
}
添付プロパティパターン(監査)
public static readonly AttachedProperty<bool> IsUnwiredProperty =
    AvaloniaProperty.RegisterAttached<UiAudit, Control, bool>("IsUnwired");

public static bool GetIsUnwired(Control control)
    => control.GetValue(IsUnwiredProperty);

public static void SetIsUnwired(Control control, bool value)
    => control.SetValue(IsUnwiredProperty, value);
ウィンドウの構造
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:XerahS.ViewModels"
        x:Class="XerahS.UI.Views.MainWindow"
        x:DataType="vm:MainViewModel"
        x:CompileBindings="True"
        Title="{Binding Title}"
        Width="1000" Height="700">
    
    <Window.Styles>
        
    </Window.Styles>
    
    <DockPanel>
        
    </DockPanel>
</Window>

クイックリファレンス表

配置値
プロパティ値デフォルト
HorizontalAlignmentLeft、Center、Right、StretchStretch
VerticalAlignmentTop、Center、Bottom、StretchStretch
バインディングモード
モード方向更新タイミング
OneWayソース → ターゲットソースの変更時
TwoWayソース ↔ ターゲットどちらかの変更時
OneTimeソース → ターゲット初期化時に 1 回
OneWayToSourceソース ← ターゲットターゲットの変更時
Grid のサイズ指定
種類構文動作
自動Autoコンテンツに合わせたサイズ
ピクセル100固定サイズ
スター* または 2*比例配分で拡張

追加リソース


新しいコントロール/ビューのチェックリスト

  • .axaml ファイル拡張子を使用する
  • x:Class 属性を設定する
  • コンパイル済みバインディング用に x:DataType を設定する
  • x:CompileBindings="True" を設定する(または .csproj でグローバルに有効にする)
  • 適切な名前空間を定義する
  • スタイル設定可能なカスタムプロパティには StyledProperty を、スタイル設定不可またはパフォーマンスが重要なプロパティには DirectProperty を使用する
  • null 許容参照型のルールに従う
  • MVVM には CommunityToolkit.Mvvm(ObservableObject、[ObservableProperty]、[RelayCommand])を使用する — ReactiveUI は使用しない
  • ソースジェネレーター用に ViewModel クラスを partial として宣言する
  • 一貫したスタイル設定/テーマ設定を適用する
  • ⚠️ ContextMenu ではなく ContextFlyout + MenuFlyout を使用する(FluentAvalonia との互換性)
  • DataTemplate 内のフライアウトのバインディングには $parent[UserControl].DataContext を使用する
  • #ElementName パスには Avalonia の Binding を使用する。{ReflectionBinding #...} は使用しない
  • 複数ページのアプリには NavigationPage/TabbedPage/DrawerPage を使用する — 手動で ContentControl を切り替えない
  • AOT/トリミング:コンパイル済みバインディングを優先し、実行時リフレクションを避ける
  • AvaloniaUI.DiagnosticsSupport を使用する — Avalonia.Diagnostics は使用しない
  • アクセシビリティに対応する(タブ順序、アクセシブルな名前)
  • すべての対象プラットフォームでテストする

最終更新日:2026 年 3 月 17 日
バージョン:1.3.1
メンテナンス担当:XerahS Development Team

コメント