学习笔记(Maui 08 懒式初始化和事件转命令)
DailyPoetryM 项目(对应 P15)
本节通过 DailyPoetryM 项目讲解懒式初始化和事件转命令
1 异步实现 RelayCommand 方法
1.1 老版本
private RelayCommand _CommandNavigatedTo;
public RelayCommand CommandNavigatedTo => _CommandNavigatedTo ??= new RelayCommand(async () =>
{
await CommandNaviagedToFunction();
});
1.2 新版本
private AsyncRelayCommand _CommandNavigatedTo;
public AsyncRelayCommand CommandNavigatedTo => _CommandNavigatedTo ??= new AsyncRelayCommand(async () =>
{
await CommandNaviagedToFunction();
});
新版本使用 AsyncRelayCommand 代替 RelayCommand,直接指明异步操作。
private AsyncRelayCommand _CommandNavigatedTo;
public AsyncRelayCommand CommandNavigatedTo => _CommandNavigatedTo ??= new AsyncRelayCommand(() =>
{
return CommandNaviagedToFunction();
});
这样似乎也可以。可以简化写法。
private AsyncRelayCommand _CommandNavigatedTo;
public AsyncRelayCommand CommandNavigatedTo => _CommandNavigatedTo ??= new AsyncRelayCommand(CommandNaviagedToFunction);
新版本的测试有空引用错误,未解决。
2 懒式初始化
占用大量资源的变量过早初始化,会占用大量内存,而这些变量未必会使用。这种资源占用的情况会影响程序执行。为了解决这种问题,这些变量定义时暂时不进行初始化,而是在这些变量使用时才进行初始化。这种初始化方式称为懒式初始化。懒式初始化体现了按需加载的工程思想。
private RelayCommand _CommandNavigatedTo;
public RelayCommand CommandNavigatedTo => _CommandNavigatedTo ??= new RelayCommand(async () =>
{
await CommandNaviagedToFunction();
});
以上 RelayCommand 使用符号 ??= 是懒式初始化,即在使用时才赋值,??= 符号无法确定原子性。微软提供了标准化的懒式初始化,能够保证线程安全。
private Lazy<AsyncRelayCommand> _LazyCommandNavigatedTo;
public AsyncRelayCommand CommandNavigatedTo => _LazyCommandNavigatedTo.Value;
标准懒式初始化需要在构造函数里赋值。
_LazyCommandNavigatedTo = new Lazy<AsyncRelayCommand>(() => new AsyncRelayCommand(CommandNaviagedToFunction));
首次使用 AsyncRelayCommand 时,_LazyCommandNavigatedTo 调用 Value,从而执行 new AsyncRelayCommand(CommandNaviagedToFunction),创建 AsyncRelayCommand。
3 事件转命令
3.1 项目整理
删除被测试 Maui 项目 DailyPoetryM 的文件夹 Models、文件夹 ViewModels 和文件夹 Services 及其中所有内容。在被测试 Maui 项目 DailyPoetryM 的依赖性中引用中间库项目 DailyPoetryM.Library。
在被测试 Maui 项目 DailyPoetryM 中添加文件夹 Services,添加并实现 StoragePreference 类。
internal class StoragePreference : IStoragePreference
{
public int Get(string key, int defaultValue) => Preferences.Get(key, defaultValue);
public void Set(string key, int value) => Preferences.Set(key, value);
}
中间库项目 DailyPoetryM.Library 定义了接口 IStoragePreference,在被测试 Maui 项目 DailyPoetryM 中实现。
在被测试项目中添加类 ServiceLocator。资源添加和依赖注入与之前操作相同。
internal class ServiceLocator
{
private IServiceProvider _ServiceProvider;
public ViewModelResultPage ViewResultPage => _ServiceProvider.GetService<ViewModelResultPage>();
public ServiceLocator()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<IStoragePoetry, StoragePoetry>();
serviceCollection.AddSingleton<IStoragePreference, StoragePreference>();
serviceCollection.AddSingleton<ViewModelResultPage>();
_ServiceProvider = serviceCollection.BuildServiceProvider();
}
}
注册接口 IStoragePoetry 的实现类为 StoragePoetry。
3.2 PageResult 设计(View 设计)
在被测试 Maui 项目 DailyPoetryM 中建立文件夹 Views,并在其中新建项 PageResult,类型为 .Net Maui ContentPage(Xaml)。
Maui 页面自动依赖注入,作为 BindingContext 的 ViewModel 自行完成依赖注入,两套系统。
希望在页面进入 PageResult 时执行命令 CommandNavigatedTo,有页面进入事件 NavigatedTo,但是没有对应命令。使用把事件转换成命令的机制。用张引老师的工具 TheSalLab.MauiBehaviors 时,出现错误提示,无法解决。
错误 XLS0502 类型“MauiActionCollection”不支持直接内容。
Nuget 安装 CommunityToolkit.Maui 支持包。将命名空间引入。
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
添加属性 Behaviors
<ContentPage.Behaviors>
<toolkit:EventToCommandBehavior
EventName="NavigatedTo"
Command="{Binding CommandNavigatedTo}"/>
</ContentPage.Behaviors>
属性 Behaviors 是微软为控件提供的一种机制,用以扩展控件的功能。放置 ListView 用以显示诗词名字。
<ListView ItemsSource="{Binding Poetries}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Name}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
在 AppShell.xaml.cs 中增加导航页面
Items.Add(new FlyoutItem
{
Title = nameof(PageResult),
Route = nameof(PageResult),
Items =
{
new ShellContent
{
ContentTemplate = new DataTemplate(typeof(PageResult))
}
}
});
在 AppShell.xaml 中修改属性,显示导航页面
Shell.FlyoutBehavior="Flyout"
运行失败,原因是变量 Where 没有赋值,为空引用。解决方案为在中间库项目 DailyPoetryM.Library 中类 ViewModelResultPage 的构造函数中添加语句,给变量 Where 赋值。
// ToDo 测试用 正式版需要删除
Where = Expression.Lambda<Func<Poetry, bool>>(Expression.Constant(true), Expression.Parameter(typeof(Poetry), "p"));
运行仍然失败,原因是 storagePoetry 没有进行初始化,由于初始化函数 InitializeAsync() 是异步函数,不能在构造函数中完成初始化,未解决。
4 其他内容
4.1 xmlns 引入格式
xmlns:b="clr-namespace:TheSalLab.MauiBehaviors;assembly=TheSalLab.MauiBehaviors"
表示组件 TheSalLab.MauiBehaviors 的命名空间 TheSalLab.MauiBehaviors 被标识为 b。
4.2 命名规范
https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/general-naming-conventions
4.3 问题
程序调试时出现问题
Error: Converter failed to convert value of type 'Windows.Foundation.IReference`1<Microsoft.UI.Xaml.GridLength>' to type 'Double'; BindingExpression: Path='TemplateSettings.CompactPaneGridLength' DataItem='Microsoft.UI.Xaml.Controls.SplitView'; target element is 'Microsoft.UI.Xaml.Media.Animation.SplineDoubleKeyFrame' (Name='null'); target property is 'Value' (type 'Double').
该问题不出现于视频中,可能是由于.net 版本造成的(视频用.net6.0 ,目前实际版本是.net8.0),网上有相关问题说明。但是并没有解决问题。
4.4 问题解决
问题是由于 .net 8.0 对 Flyout 支持的问题造成,为了解决问题将版本退回 .net 7.0,节 4.3 的问题解决了。
StoragePoetry 没有初始化的问题,虽然初始化函数不能加到构造函数里,但是可以加到函数 CommandNaviagedToFunction 内。
public async Task CommandNaviagedToFunction()
{
await _StoragePoetry.InitializeAsync();
Poetries.Clear();
await Poetries.LoadMoreAsync();
}
问题解决,可以正常获得页面内诗词的名字。