
WinUI3基础
WinUI3 简介
WinUI3 是微软推出的又一个 Windows UI 框架
微软推出的 UI 框架按时间顺序如下:WinForm
-> WPF
-> UWP
-> WinUI3
WinUI3 使用 xmal
编写界面,使用 C#
编写逻辑
一个很无语的点是… WinUI3 到现在都没有设!计!器!! 微软画的饼,官方文档中提到 xaml 热重载可以一定程度上代替设计器,但是部分情况下无法热重载…
基础使用
WinUI3 Gallery, 这是一个所有控件与样式的演示 APP
控件
数据绑定
绑定就是一根连接 UI 和数据的绳索,每个绑定由绑定源和绑定目标组成
- 绑定源:类示例(ViewModel)的属性
- 绑定目标:UI 中元素属性
这部分我们会实现一个歌曲选择功能。
界面上有一个歌曲列表,点击对应歌曲能显示出歌曲详细信息
一种实现绑定的方式是将 ViewModel
作为窗口类的成员添加,并在 UI 的构造函数中初始化
// Model 与 ViewModel 定义
namespace DataBinding
{
public class Recording
{
public string ArtistName { get; set; }
public string CompisitionName { get; set; }
public DateTime ReleaseDateTime { get; set; }
public Recording()
{
ArtistName = "Supercell(ryo)";
CompisitionName = "メルト";
ReleaseDateTime = new DateTime(2007, 12, 7);
}
public string OneLineSummary => $"{CompisitionName} by {ArtistName}, released: {ReleaseDateTime.ToString("d")}";
}
public class RecordingViewModel
{
private Recording defaultRecording = new Recording();
public Recording DefaultRecording => defaultRecording;
}
}
// ViewModel 添加到窗口
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
ViewModel = new RecordingViewModel(); // 初始化
}
public RecordingViewModel ViewModel { get; set; } // 添加为成员
}
<!-- 窗口中绑定到 ViewModel -->
<Window
x:Class="DataBinding.MainWindow" ...>
<!-- 这是 xaml CodeBehind 的类名,如果没有的话会识别不到下面 Bind 的 ViewModel -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<!-- 绑定内容 -->
<TextBlock Text="{x:Bind ViewModel.DefaultRecording.OneLineSummary}"/>
</StackPanel>
</Window>
结果如下:
上面绑定的是单个数据,如果我有一组数据呢?
使用 ObservableCollection<T>
// 数据集合的容器
public class RecordingViewModel
{
...
private ObservableCollection<Recording> recordings = new ObservableCollection<Recording>();
public ObservableCollection<Recording> Recordings => recordings;
public RecordingViewModel()
{
recordings.Add(new Recording() { ProducerName = "ika", SongName = "君のことミクミクにしてあげる", ReleaseDateTime = new DateTime(2007, 09, 20) });
recordings.Add(new Recording() { ProducerName = "cosMo@暴走P", SongName = "初音ミクの消失", ReleaseDateTime = new DateTime(2008, 04, 08) });
recordings.Add(new Recording() { ProducerName = "ryo(supercell)", SongName = "World is Mine", ReleaseDateTime = new DateTime(2008, 05, 31) });
}
}
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{x:Bind ViewModel.DefaultRecording.OneLineSummary}"/>
<!-- 数据集合绑定方式 -->
<ListView ItemsSource="{x:Bind ViewModel.Recordings}"/>
</StackPanel>
结果如下:
因为我们还没有给 Recording
类提供数据模板,所以没有内容显示
数据模板如下:
<ListView ItemsSource="{x:Bind ViewModel.Recordings}">
<!-- 数据模板定义 -->
<ListView.ItemTemplate>
<!-- 此处的 local 在 Window 中的xmlns:local定义 -->
<DataTemplate x:DataType="local:Recording">
<!-- 数据模板相当于将单个数据从数据集合中抽出来,自然就能使用单个数据的属性 -->
<TextBlock Text="{x:Bind OneLineSummary}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
结果如下:
这种一股脑把数据全部丢出来显示的方式会占用很多屏幕空间
一种更好的方式是点击哪一条就显示哪个的信息
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView x:Name="recordingsListView" ItemsSource="{x:Bind ViewModel.Recordings}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Recording">
<StackPanel Orientation="Horizontal" Margin="6">
<SymbolIcon Symbol="Audio" Margin="0,0,12,0"/>
<StackPanel>
<TextBlock Text="{x:Bind SongName}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- 上面还是一个 ListView,只显示曲名 -->
<!-- 下面设置 StackPanel 的 DataContext为 SelectedItem,显示被选择对象的属性 -->
<StackPanel DataContext="{Binding SelectedItem, ElementName=recordingsListView}"
Margin="0,24,0,0">
<TextBlock Text="{Binding ProducerName}"/>
<TextBlock Text="{Binding SongName}"/>
<TextBlock Text="{Binding ReleaseDateTime}"/>
</StackPanel>
</StackPanel>
</Grid>
注意上面 selectedItem
使用了 Binding
而不是 x:Bind
,后面会有对这两种方式的分析
x:Bind
会有对类属性的自动补全Binging
没有自动补全,性能比x:Bind
低,但是更灵活
刚刚我们使用的是 SelectedItem
,还可以将 ViewModel.Recordings
注册为资源进行绑定
<Grid>
<!-- 将 ViewModel.Recordings 注册为 Grid 静态资源 -->
<!-- 由于 Window 没有 Resources 属性,所以此处资源是注册到顶级 Grid 中 -->
<!-- Page 有 Resoueces 属性,资源可以直接写 Page 里面 -->
<Grid.Resources>
<CollectionViewSource x:Name="Recordingscollection" Source="{x:Bind ViewModel.Recordings}"/>
</Grid.Resources>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView ItemsSource="{Binding Source={StaticResource Recordingscollection}}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Recording">
<StackPanel Orientation="Horizontal" Margin="6">
<SymbolIcon Symbol="Audio" Margin="0,0,12,0"/>
<StackPanel>
<TextBlock Text="{x:Bind SongName}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- 使用静态资源作为 DataContext -->
<!-- 也就是说 SatckPanel 内部所有绑定都会使用 Recordingscollection 作为数据源 -->
<StackPanel DataContext="{Binding Source={StaticResource Recordingscollection}}"
Margin="0,24,0,0">
<TextBlock Text="{Binding ProducerName}"/>
<TextBlock Text="{Binding SongName}"/>
<TextBlock Text="{Binding ReleaseDateTime}"/>
</StackPanel>
</StackPanel>
</Grid>
两种方法的外观都是一样的
但是时间这一栏还是一个 DateTime, 显示的过于精确。我们不想显示秒数
你可以在 Recordings
类中将 ReleaseDateTime
的 get
属性返回ToString("d")
但是这可能会带来别的麻烦。如果其他地方需要秒数那就不行了
你还可以写两个属性… 一个给 UI, 另一个给其他地方。但是这太 SB 了
更灵活的解决方案是使用值转换器:
public class StringFormatter : Microsoft.UI.Xaml.Data.IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
// value 是绑定传来的数据 (RealeaseDateTime: 2007/9/20 0:00:00)
// parameter 是 xaml 里面传的参数 (Released:{0:d})
string? formatString = parameter as string;
if (!string.IsNullOrEmpty(formatString))
{
return string.Format(formatString, value);
}
return value.ToString();
}
// 单向绑定不需要实现这个
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
``` xml
...
<Grid.Resources>
<CollectionViewSource x:Name="RecordingsCollection" Source="{x:Bind ViewModel.Recordings}"/>
<local:StringFormatter x:Key="StringFormatterValueConverter"/>
</Grid.Resources>
...
<TextBlock Text="{Binding ReleaseDateTime, Converter={StaticResource StringFormatterValueConverter}, ConverterParameter=Released:\{0:d\}}"/>
...
结果如下:
以上就是数据绑定的基本使用,在这一部分我们提到了
- 绑定基本方式:
ViewModel
作为成员添加到 UI - 单个数据的绑定:
x:Bind
- 数据集合的绑定:
ObservableCollection<>
添加到Binding
的Source
更多内容: