C# Markup是.NET MAUI官方支持的一种UI编写方式,它允许开发者完全用C#代码来声明界面,而不需要写一行XAML。对于熟悉C#但不习惯XAML语法的开发者来说,这条路径上手更快,代码提示和重构支持也更完善。本文将从环境搭建开始,一步步带你掌握C# Markup的核心用法。

一、C# Markup是什么,为什么值得尝试
传统的MAUI应用默认使用XAML来描述界面,XAML本质上是一种XML方言,通过标签和属性的组合来声明控件树。这种方式虽然结构清晰,但存在一些固有的痛点:属性名没有编译期检查、重构控件名需要手动同步、复杂的绑定表达式调试困难、代码与界面分离导致频繁切换文件等。
C# Markup的出现就是为了解决这些问题。它通过一组扩展方法,让C#代码能够以声明式的风格描述UI结构。由于整个界面都是C#代码,编译器可以在构建阶段发现拼写错误和类型不匹配,智能提示也能覆盖所有属性和方法。此外,界面逻辑和界面结构在同一个文件中,来回切换的次数大大减少。
从底层实现来看,C# Markup本质上还是构建了和XAML相同的控件对象树,运行时行为完全一致,只是描述方式从XML换成了C#的链式调用。这意味着XAML能实现的布局、绑定、样式,C# Markup都能做到。
二、环境搭建与基础示例
使用C# Markup需要先安装官方的NuGet包。在创建好MAUI项目后,通过命令行或者Visual Studio的包管理器安装CommunityToolkit.Maui.Markup包:
dotnet add package CommunityToolkit.Maui.Markup
安装完成后,还需要在MauiApp.CreateBuilder中注册Markup的支持服务。打开MauiProgram.cs文件,添加如下代码:
using CommunityToolkit.Maui.Markup;
namespace MauiMarkupDemo;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiCommunityToolkitMarkup(); // 注册Markup服务
return builder.Build();
}
}接下来创建一个纯C#的页面。在项目中新建一个类MainPage,继承自ContentPage,然后在构造函数中用C# Markup声明界面:
using CommunityToolkit.Maui.Markup;
using Microsoft.Maui.Controls;
namespace MauiMarkupDemo;
public class MainPage : ContentPage
{
public MainPage()
{
Content = new VerticalStackLayout
{
Spacing = 20,
Padding = 30,
Children =
{
new Label
{
Text = "Hello, C# Markup!",
FontSize = 28,
FontAttributes = FontAttributes.Bold
},
new Entry
{
Placeholder = "请输入内容"
},
new Button
{
Text = "点击我"
}.Invoke(button => button.Clicked += OnButtonClicked)
}
};
}
private void OnButtonClicked(object? sender, EventArgs e)
{
DisplayAlert("提示", "按钮被点击了", "确定");
}
}最后别忘了修改App.xaml.cs中的逻辑,让应用启动时加载这个页面。把MainPage属性改为new MainPage()即可。运行程序,一个简单的界面就完成了,整个过程没有触碰任何XAML文件。
三、核心API与常用控件用法详解
C# Markup的核心是一系列扩展方法,掌握它们可以大幅提升代码的简洁度。最常用的是各类属性设置扩展,比如Text()、FontSize()、TextColor()等,它们通过链式调用避免初始化器写法中的冗长代码。
new Label()
.Text("用户名")
.Font(size: 16, bold: true)
.TextColor(Colors.DimGray)
.Margin(0, 0, 0, 8);除了属性设置,布局类的扩展方法也很实用。FillExpand()对应布局选项LayoutOptions.FillAndExpand,可以简化水平或垂直方向的排布设置:
new Grid
{
ColumnDefinitions = Columns.Define(Star, Star, Star),
RowDefinitions = Rows.Define(Auto, Star),
Children =
{
new Label().Text("第一列").Row(0).Column(0),
new Label().Text("第二列").Row(0).Column(1),
new Entry().Row(1).ColumnSpan(3).FillExpand()
}
};上面的代码中,Columns.Define和Rows.Define用简洁的语法定义网格结构,Row()和Column()方法指定子元素的位置,ColumnSpan()处理跨列。这些辅助方法让Grid布局的代码量比初始化器写法减少了一半以上。
事件绑定方面,除了前面用到的Invoke,Markup还提供了更直接的方式。以按钮点击为例:
new Button()
.Text("提交")
.Invoke(button => button.Clicked += async (s, e) =>
{
await DisplayAlert("提示", "提交成功", "确定");
});四、数据绑定与MVVM模式的配合
C# Markup对MVVM模式的支持同样完善。虽然没有了XAML中的Binding标记扩展,但可以用Bind()方法实现同样的效果。首先定义一个简单的ViewModel:
public class CounterViewModel : INotifyPropertyChanged
{
private int count;
public int Count
{
get => count;
set
{
count = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
}
}
public Command IncrementCommand { get; }
public CounterViewModel()
{
IncrementCommand = new Command(() => Count++);
}
public event PropertyChangedEventHandler? PropertyChanged;
}然后在页面中通过Bind()方法建立视图与ViewModel的关联:
public class CounterPage : ContentPage
{
public CounterPage()
{
var vm = new CounterViewModel();
BindingContext = vm;
Content = new VerticalStackLayout
{
Padding = 30,
Spacing = 15,
Children =
{
new Label()
.Bind(Label.TextProperty, nameof(CounterViewModel.Count),
convert: (int count) => $"当前计数:{count}"),
new Button()
.Text("增加")
.Bind(Button.CommandProperty, nameof(CounterViewModel.IncrementCommand))
}
};
}
}注意Bind()方法第一个参数指定目标依赖属性,第二个参数是绑定路径,convert参数支持内联的值转换,这比XAML中单独定义转换器类的做法灵活不少。命令绑定也通过一行Bind调用完成,整体代码保持了声明式的风格。
五、C# Markup与XAML的对比与选型建议
两种方案各有侧重,可以参考下表来权衡:
| 对比维度 | C# Markup | XAML |
|---|---|---|
| 编译期检查 | 完整,属性错误直接报错 | 属性名部分检查,绑定路径运行时才验证 |
| 热重载支持 | XAML热重载不可用,需依赖C#热重载 | XAML热重载成熟稳定 |
| 代码复用 | 可用方法、循环动态生成控件 | 需要模板、样式等间接手段 |
| 设计师协作 | 难以让设计师直接编辑 | 工具链支持较好 |
| 重构友好度 | 重命名自动同步 | 字符串绑定路径需手动修改 |
如果你的团队以C#开发者为主,界面复杂度高且包含大量动态生成逻辑,C# Markup是更合适的选择;如果团队有专门的设计或前端人员参与界面调整,或者非常依赖XAML热重载的即时预览体验,那么保留XAML也完全合理。
实际上两种方式可以在同一个项目中混用,简单的工具页面用C# Markup快速实现,主界面继续用XAML维护,渐进式地评估哪种方式更适合项目,是比较稳妥的落地策略。