AvaloniaUI/Avalonia.Markup.Declarative là dự án mã nguồn mở trên GitHub với 499 sao, viết chủ yếu bằng C#. Provides helpers for declarative ui in C#
Tóm tắt dựng từ metadata GitHub của chính dự án — chưa có bài review TopGit. Trang sẽ tự động cập nhật khi bài review đầy đủ được xuất bản.
VÌ SAO CHƯA CÓ REVIEW
TopGit viết bài đầy đủ cho repo có nhiều sao nhất và được yêu cầu nhiều nhất. Trang này là snapshot trong thời gian chờ — xem README gốc ở tab READ ME.
Important: This repository is community driven, not officially supported by avalonia team and is not part of the official Avalonia project—it's only a proof of concept demonstrating how markup can be written purely in C#. For real-world projects use Avalonia's supported XAML approach.
Avalonia.Markup.Declarative
Write Avalonia UI with C# like a Boss 🗿
Avalonia.Markup.Declarative is a C#-first authoring layer over Avalonia controls. The current API is compiled-binding-first and source-generator-driven, with public patterns intentionally aligned with Avalonia's DataContext, binding, style, and selector model.
real projects that using Avalonia.Markup.Declarative
https://github.com/gritsenko/pix2d - Pix2d graphic editor for indie developers
feel free to add your project
Installation
Add the Avalonia.Markup.Declarative NuGet package to your project
Project Template
You can easily create a new project from the command line using the official template:
dotnet new install Declarative.Avalonia.Templates
dotnet new avalonia-declarative -n MyApp
Use a self-contained declarative component when the view and its reactive state belong to the same feature. Add CommunityToolkit.Mvvm to the app project and keep the component-local state in a nested ObservableObject.
using Avalonia.Data;
using CommunityToolkit.Mvvm.ComponentModel;
public class CounterComponent() : ViewBase<CounterComponent.State>(new State())
{
public sealed partial class State : ObservableObject
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CounterLabel))]
public partial decimal? Counter { get; set; } = 0;
[ObservableProperty]
public partial string StatusText { get; set; } = "Hello world";
public string CounterLabel => $"Counter: {Counter}";
}
protected override object Build(State state) =>
new StackPanel()
.Children(
new TextBlock()
.Text(state, x => x.StatusText),
new TextBlock()
.Text(state, x => x.CounterLabel),
new NumericUpDown()
.Value(state, x => x.Counter, BindingMode.TwoWay),
new Button()
.Content("Increment")
.OnClick(_ => state.Counter++)
);
}
To compose constructor-injected views, prefer ViewFactory.Create<T>(). If you use DI, register UseComponentControlFactory(...) on AppBuilder.
MVVM Pattern implementation
Use ViewBase<TViewModel> when you want a classic Avalonia or WPF-style view model. Generated setters expose compiled-binding overloads, so the binding syntax stays close to native Avalonia.
using Avalonia.Data;
public class MainView() : ViewBase<MainViewModel>(new MainViewModel())
{
protected override object Build(MainViewModel vm) =>
new StackPanel()
.Children(
new TextBox()
.Text(vm, x => x.Message, BindingMode.TwoWay),
new TextBlock()
.Text(vm, x => x.Message),
new Button()
.Content("Reset")
.OnClick(_ => vm.Message = string.Empty)
);
}
If you assign DataContext from the outside, the same generated setters also support DataContext-relative compiled bindings such as new TextBlock().Text<MainViewModel>(x => x.Message);.
Generated compiled-binding setters also apply automatic conversion for common primitive and nullable mismatches, so bindings like new Slider().Value(vm, x => x.Counter, BindingMode.TwoWay) work when Counter is int, and new CheckBox().IsChecked(vm, x => x.Enabled) work when Enabled is bool. Prefer plain member access such as x => x.Counter: a numeric-conversion cast like x => (double)x.Counter is both unnecessary (the auto-converter handles it) and unsupported — Avalonia's expression parser rejects value-converting Convert nodes. Type casts that navigate to a member of a derived type, such as x => ((DerivedType)x).Property, are supported. For lossy numeric TwoWay conversions, convert-back truncates toward zero.
Passing a ready-made binding
When you need the full binding feature set — reflection bindings (Binding), a pre-built compiled binding, a TemplateBinding, a MultiBinding, or a relative-source/element-name binding — every generated property, attached-property and style setter also exposes an overload that accepts a BindingBase directly:
using Avalonia.Data;
new TextBlock()
.Text(new Binding("ReflectionProperty")) // DataContext-relative reflection binding
.Foreground(new Binding("Theme.Accent") { Source = appState }); // explicit source
new TextBlock()
.Text(CompiledBinding.Create<MyViewModel, string>(x => x.Title, source: vm));
// attached properties and styles get the same overload
new Border().Grid_Row(new Binding(nameof(vm.Row)) { Source = vm });
new Style<TextBlock>().Text(new Binding(nameof(vm.Name)));
This is the escape hatch for anything the strongly-typed x => x.Member expression overloads don't cover (custom converters passed on the binding, RelativeSource, ElementName, string format, multi-value bindings, etc.). The same thing is available on any AvaloniaProperty via control.BindValue(TextBlock.TextProperty, binding).
Hot reload support
ViewBase supports .NET 6.0+ hot reload.
Keeping declarative views in an assembly without XAML can still produce the smoothest hot reload experience.
Current Avalonia and .NET toolchains are much better at mixing AXAML and C# markup in the same application, so the limitation is much smaller than it used to be.
The optional, dev-onlyDeclarative.Avalonia.AgentTools package runs an in-process MCP (Model
Context Protocol) server on loopback in debug builds, so an AI agent iterating on your UI can see and
drive the running app — closing the edit → hot-reload → verify loop without a human relaying what the
window looks like.
using Declarative.Avalonia.AgentTools; // under #if DEBUG
AppBuilder.Configure<App>()
.UsePlatformDetect()
#if DEBUG
.UseAgentInspector() // loopback MCP server on 127.0.0.1:5599
#endif
.SetupWithLifetime(lifetime);
It exposes screenshots (with before/after pixel-diffing), the visual tree with bounds and a single
absolute client-DIP coordinate frame (abs/center) shared by every tool, per-control layout reports,
an automated layout audit, property / property-source / view-model inspection, pixel↔control hit-testing
(with a "how to drive this control" hint), and recent build/binding/runtime errors. An opt-in tier
(EnableInteraction) also drives the app — real synthesized pointer/keyboard input (tap, drag,
pointer_*, that work even on custom controls with no automation peer), click, type, select, resize,
switch theme, open a closed popup — plus an escape hatch (with structured, actionable errors) to set a
view-model property or run a command directly to reach awkward states.
Keep the call under #if DEBUG: the package pulls in a web stack and a remote-control surface and must not
ship in Release; it binds to loopback only. See docs/agent-tools.md for the full
guide.
Enable the MCP in your agent
The inspector is a streamable-HTTP MCP server on http://127.0.0.1:5599, so every agent points at the
same URL. Run the app under dotnet watch so the agent's edits hot-reload into the process it inspects.
Claude Code — claude mcp add --transport http avalonia-agent-inspector http://127.0.0.1:5599, or a
project .mcp.json:
Codex — an [mcp_servers.*] table in ~/.codex/config.toml (a url makes it streamable-HTTP; enable
the RMCP client once with [features] → experimental_use_rmcp_client = true):
Make sure that the path to the source generator project is correct relative to your project.
Note: If you are using this library as a NuGet package, the source generator is included automatically.
External libraries support
Framework extensions are generated automatically for the supported Avalonia assemblies that your project references. To generate extensions for a third-party library, add an assembly attribute that points to any type from that assembly:
using Avalonia.Markup.Declarative;
using ReactiveUI.Avalonia;
[assembly: GenerateMarkupExtensionsForAssembly(typeof(RoutedViewHost))]
No standalone tool installation or manual avalonia-amd-gen step is required anymore.
AvaloniaUI/Avalonia.Markup.Declarative có bao nhiêu sao?
AvaloniaUI/Avalonia.Markup.Declarative có 499 sao GitHub — tải lại trang để xem số mới nhất, hoặc xem trực tiếp github.com/AvaloniaUI/Avalonia.Markup.Declarative. TopGit phản chiếu số sao của GitHub nhưng không cam kết đến từng phút.
AvaloniaUI/Avalonia.Markup.Declarative có tag gì không?
Bản đồng bộ chưa ghi nhận topic GitHub nào cho AvaloniaUI/Avalonia.Markup.Declarative. GitHub topics hiển thị ở thanh bên phải trang repo — đó là nơi đáng kiểm tra nhất.
AvaloniaUI/Avalonia.Markup.Declarative còn đang phát triển không?
Commit gần nhất trên AvaloniaUI/Avalonia.Markup.Declarative là 15 ngày trước (theo timestamp GitHub). Repo có 27 fork — một chỉ báo về mức độ quan tâm của cộng đồng.
AvaloniaUI/Avalonia.Markup.Declarative viết bằng ngôn ngữ gì?
AvaloniaUI/Avalonia.Markup.Declarative chủ yếu viết bằng C#. Trường "language" của GitHub dựa trên phần lớn byte ở nhánh mặc định.
Đọc thêm về AvaloniaUI/Avalonia.Markup.Declarative ở đâu?
Trang TopGit này là một snapshot — tab "Readme" hiển thị nguyên văn README của repo (đã bỏ link, giữ ảnh). Repo GitHub ở github.com/AvaloniaUI/Avalonia.Markup.Declarative là nguồn chính thức.
Đọc đầy đủ README ở tab phía trên.
Chưa chắc Avalonia.Markup.Declarative có hợp với bạn?
Để ChatGPT, Claude hoặc Perplexity tìm hiểu giúp — bấm bên dưới và xem AI nói gì về Avalonia.Markup.Declarative.