avalonia-api について
Avalonia UI を使って .NET アプリを開発する人向けの、AXAML 実践リファレンスです。コントロール、レイアウト、バインディング、MVVM、スタイルに加え、XerahS 固有の指針も扱います。
主な機能
- 01AXAML のコントロールとレイアウト
- 02コンパイル時バインディングと MVVM
- 03スタイル、テーマ、リソース
- 04カスタムプロパティと添付プロパティ
- 05レスポンシブおよびスクロール可能なレイアウト
活用シーン
- 01ビュー作成時の Avalonia UI パターンの確認
- 02レスポンシブ画面のレイアウト選択
- 03バインディングと MVVM 規約の適用
- 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/
目次
- AXAML の基礎
- コントロールと UI 要素
- レイアウトシステム
- データバインディング
- CommunityToolkit.Mvvm を使った MVVM パターン
- スタイルとテーマ
- 依存プロパティと添付プロパティ
- カスタムコントロール
- コントロールテンプレート
- リソースとコンバーター
- イベントとコマンド
- ナビゲーション
- クロスプラットフォームのパターン
- パフォーマンスとベストプラクティス
- 開発者ツール
- 避けるべきよくある間違い
- 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 列シェルとしての SplitView | ContentControl を継承し、既定の 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 パスのレイアウトシステムを使用します。
- Measure パス:各コントロールの希望サイズを決定します
- 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 つのスタイル機構があります。
- スタイル:CSS に似ており、型またはクラスでコントロールを対象にします
- コントロールテーマ:完全なビジュアルテンプレートです(WPF のスタイルに相当)
- コンテナークエリ:コンテナーのサイズに基づくレスポンシブスタイルです
基本的なスタイル
<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>
設計原則
- .NET Standard を使用する:ビジネスロジックは .NET Standard ライブラリに記述します
- MVVM パターン:UI とロジックを分離します
- Avalonia の描画:Avalonia の描画ベースの UI(ネイティブコントロールではなく)を活用します
- プラットフォーム抽象化:プラットフォーム固有の機能にはインターフェイスを使用します
- レスポンシブデザイン:コンテナークエリとアダプティブレイアウトを使用します
パフォーマンスとベストプラクティス
パフォーマンスのヒント
- 行や列が不要な場合は、
GridよりPanelを使用します - コンパイル済みバインディングをグローバルに有効にします
- 大きなリストには仮想化(
ItemsRepeater、VirtualizingStackPanel)を使用します - ビジュアルツリーの深いネストを避けます
- アニメーションでは
Marginの代わりにRenderTransformを使用します ItemsRepeaterで DataTemplate を再利用します- プロパティの変更をまとめて適用し、レイアウトパスを最小限に抑えます
メモリ管理
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 を使用する |
| 2 | WPF 名前空間を使用する | https://github.com/avaloniaui を使用する |
| 3 | Style.Triggers / DataTrigger / EventTrigger を使用する | 疑似クラスセレクターを使用する |
| 4 | DependencyProperty を使用する | StyledProperty または DirectProperty を使用する |
| 5 | Style x:Key="..." を使用する | スタイルクラスとセレクターを使用する |
| 6 | HierarchicalDataTemplate を使用する | TreeDataTemplate を使用する |
| 7 | pack://application:,,,/ URI を使用する | avares://AssemblyName/path を使用する |
| 8 | Visibility 列挙型を使用する | bool IsVisible を使用する(非表示でもスペースを確保する場合は Opacity="0") |
| 9 | x:DataType が設定されていない | コンパイル済みバインディングでは必ず設定する |
| 10 | デフォルトで {ReflectionBinding} を使用する | コンパイル済みバインディングをグローバルに有効にする |
| 11 | {ReflectionBinding #MyRoot.SomeCommand} を使用する | {Binding #MyRoot.SomeCommand}(またはコンパイル済みバインディングのスコープ)を使用する |
| 12 | Avalonia.Diagnostics を使用する | AvaloniaUI.DiagnosticsSupport と AvaloniaUI.DeveloperTools を使用する |
| 13 | ReactiveUI / Avalonia.ReactiveUI を使用する | CommunityToolkit.Mvvm を使用する |
| 14 | ContentControl.Content を手動で切り替えてページを表示する | NavigationPage / TabbedPage / DrawerPage を使用する |
| 15 | VisualStateManager を使用する | 疑似クラスセレクターまたはコンテナークエリを使用する |
| 16 | LayoutTransform を使用する | LayoutTransformControl でラップする |
| 17 | Dispatcher.Invoke() を使用する | Dispatcher.UIThread.InvokeAsync() を使用する |
| 18 | FluentAvalonia で ContextMenu を使用する | ContextFlyout と MenuFlyout を使用する |
| 19 | StackPanel の周囲に ScrollViewer Padding="N" を設定する | パディングを内側の要素に移動する:<StackPanel Margin="N"> — ScrollViewer.Padding が縮めるのはビューポートのみで、スクロール範囲は縮まりません。そのため、下端のコンテンツに永久に到達できなくなります |
| 20 | 2 列のペインなしシェルに SplitView を使用する | Grid ColumnDefinitions="auto,*" を使用する — SplitView は ContentControl を継承しており、既定の VerticalContentAlignment=Top によって子要素に高さ ∞ が渡され、ネストされた ScrollViewer が正しく動作しなくなります |
| 21 | ScrollViewer を含むページホストとして 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>
クイックリファレンス表
配置値
| プロパティ | 値 | デフォルト |
|---|---|---|
HorizontalAlignment | Left、Center、Right、Stretch | Stretch |
VerticalAlignment | Top、Center、Bottom、Stretch | Stretch |
バインディングモード
| モード | 方向 | 更新タイミング |
|---|---|---|
OneWay | ソース → ターゲット | ソースの変更時 |
TwoWay | ソース ↔ ターゲット | どちらかの変更時 |
OneTime | ソース → ターゲット | 初期化時に 1 回 |
OneWayToSource | ソース ← ターゲット | ターゲットの変更時 |
Grid のサイズ指定
| 種類 | 構文 | 動作 |
|---|---|---|
| 自動 | Auto | コンテンツに合わせたサイズ |
| ピクセル | 100 | 固定サイズ |
| スター | * または 2* | 比例配分で拡張 |
追加リソース
- 公式ドキュメント: https://docs.avaloniaui.net/
- GitHub: https://github.com/AvaloniaUI/Avalonia
- サンプル: https://github.com/AvaloniaUI/Avalonia/tree/master/samples
- CommunityToolkit.Mvvm: https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/
- FluentAvalonia: https://github.com/amwx/FluentAvalonia
- コミュニティ: https://avaloniaui.community/
新しいコントロール/ビューのチェックリスト
-
.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