使用 Codon 为 UWP 创建设置屏幕





5.00/5 (4投票s)
为您的 UWP 应用创建一个动态的设置屏幕。只需一行代码即可添加一项设置。
目录
引言
几乎所有应用程序都需要一个设置屏幕。许多开发人员选择简单地构建静态 UI;将按钮和文本字段硬编码到设置后端存储中。但是,如果这样做,随着设置数量的增加,技术债务就会增加;使得将设置屏幕重构为类别,或更改设置的存储或显示方式,变得越来越困难。
这就是为什么我在 CodonFX 中构建了一个选项系统,它与设置系统和隔离存储后端存储集成,可以替换为 SQLite 后端存储。CodonFX 中的选项系统可能为我节省了数月的开发时间,并允许我轻松地完成一些非常好的事情,例如导出和导入选项。
添加选项
借助 Codon 选项系统,只需一行代码即可在选项屏幕上实现一个选项,该选项会自动将其写入后端存储。请参见以下内容
generalOptions.Add(new BooleanUserOption(() => "Boolean 1 title", "Boolean1Key", () => false));
在这里,我们创建了一个 BooleanUserOption
,它在选项屏幕上呈现为 ToggleSwitch
,并使用指定的字符串键自动将其值写入 Codon 的 ISettingsService
。选项的标题是一个 lambda 表达式,允许您轻松地对其进行本地化,因此如果 UI 语言发生变化,标题将以正确的语言显示。
有许多内置的选项类型,代表常见的设置类型,包括
BooleanUserOption
DoubleUserOption
IntUserOption
ObjectUserOption
StringUserOption
还有其他一些用于以不同方式呈现设置的选项,包括
CommandOption
CompositeOption
ListOption
RangeUserOption
CommandOption
用于显示一个按钮,该按钮在单击/点击时执行 ICommand
。
CompositeOption
允许您创建自己的自定义行为,其中可能包含在自定义 UI 中呈现的多个选项。
ListOption
允许您例如显示一个值枚举,该枚举呈现为下拉列表。
RangeUserOption
可用于向用户显示滑块,用于将 double 或 int 值写入后端存储。
当然,您可以创建自定义的 IUserOption
类,以满足您应用程序的需求。
示例概述
我为 UWP 准备了一个 小型示例 来在 UWP 应用中演示它。Codon 是跨平台的,您可以在 Surfy Browser for Android 等应用中看到选项系统的实际运行情况。
在示例中,您可以看到项目:一个 UWP 应用项目和一个 .NET Standard 类库。用户选项系统位于 Codon.Extras.Core NuGet 包中,该包由类库引用。UWP 应用项目引用了 Codon.Extras.Uwp NuGet 包。
探索 .NET Standard 库
类库包含各种类,包括 Bootstrapper
类,当应用程序启动时会调用其 Run
方法。
虽然不是绝对必要,但 AppSettings
类具有表示设置的强类型属性,这使您在编译时就能确信您的设置被正确引用。
位于 AppSettings.UserOptions.cs 的部分类负责注册将在 OptionsPage
上显示的选项。总的来说,用户选项代表了设置的一个子集。
AppSettings
类的 ConfigureUserOptions
方法需要 IUserOptionsService
。请参见清单 1。
userRoles
参数仅用于演示如何根据用户的权限显示不同的一组选项。这更适用于企业场景。我在我的一个应用程序中这样做,您可能不需要它。
IUserOptionsService
实现允许通过其 Register
方法注册多个选项类别。它接受一个 OptionCategory
方法和一列应显示在该类别中的选项。在我的一些应用程序中,我以选项卡或有时是可展开组的形式显示选项类别。
当您将选项添加到类别的选项集合时,它会自动显示在选项页面上。每个 IUserOption
对象都必须有一个唯一的键。lambda 表达式用于大多数属性,以允许您的应用程序在不重启的情况下切换语言。
您可以通过设置选项的 TemplateFunc
属性来指定选项的模板。
有关更多信息,请参阅 UserOptionBase
实现。
清单 1. AppSettings.ConfigureUserOptions
方法。
partial class AppSettings
{
public void ConfigureUserOptions(IUserOptionsService userOptionsService, UserRoles userRoles)
{
OptionCategory defaultCategory = new OptionCategory(OptionCategoryIds.General, () => "General");
var generalOptions = new List<IUserOption>
{
new StringUserOption(() => "String 1", String1Key, () => string1DefaultValue),
new BooleanUserOption(() => "Boolean 1", Boolean1Key, () => boolean1DefaultValue)
};
userOptionsService.Register(generalOptions, defaultCategory);
}
}
我提到 AppSettings
类并非真正必需。但我喜欢使用它,这样我就可以轻松地重构设置名称,而不用担心破坏某些东西。
为完整起见,我想提一下,我使用 ReSharper 的实时模板来定义一个设置,如清单 2 所示。
$SettingName$ 和 $Type$ 是唯二可编辑的值。$SettingName$ 在模板的属性网格中定义为“为变量建议名称”。$Type$ 设置为“猜测此处预期的类型”。
根据您定义 AppSettings
类的位置,您可能希望将设置名称和 setter 的可见性从 private
更改为 internal
或 public
。
清单 2. ReSharper 实时模板用于设置
public const string $SettingName$Key = "$SettingName$";
static $Type$ $SettingNameLower$DefaultValue = $DefaultValue$;
public $Type$ $SettingName$
{
get => settingsService.GetSetting($SettingName$Key, $SettingNameLower$DefaultValue);
private set => settingsService.SetSetting($SettingName$Key, value);
}
当应用程序启动时,Bootstrapper
类的 Run
方法通过 UWP 应用项目中的 App
类被调用,如以下摘录所示
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
...
if (!bootstrapperRan)
{
bootstrapperRan = true;
var bootstrapper = new Bootstrapper();
bootstrapper.Run();
}
...
}
有时,您的引导程序可能需要执行一些异步活动,在这种情况下,您需要将 Run
方法设为 async
,并妥善处理错误。
Bootstrapper
类将 AppSettings
类注册为单例。请参见清单 3。
提示: 根据您的应用程序需求,您可能需要为每个平台实现一个特定于平台的引导程序。我通常会这样做,并让特定于平台的引导程序调用非特定于平台的引导程序的 Run 方法。
您可能会注意到代码中 AppSettings
类需要一个 ISettingsService
实例作为构造函数参数。依赖注入用于解析默认实例,使用 Codon 框架的默认 IoC 容器 FrameworkContainer
类。
如果您对 Codon 框架的内部机制感兴趣,FrameworkContainer
使用接口属性来查找默认类型映射,使用 Codon 的 DefaultType
和 DefaultTypeName
属性;或者 .NET Standard 的 DefaultValueAttribute
。如果您查看 ISettingsService 源代码,您可以看到它是如何用 DefaultType
和 DefaultTypeName
属性进行装饰的。DefaultTypeName
具有优先权,用于查找接口的特定于平台的实现(如果存在)。如果找不到由 DefaultTypeName
标识的类型,容器将回退到 DefaultType
。
我们可以在实例化 AppSettings
类时配置用户选项,这样我们就可以通过 DI 传递 IUserOptionsService
,但我选择使用显式方法调用,因为我们可能希望推迟配置用户选项以优化应用程序启动时间。
清单 3. Bootstrapper 类。
public class Bootstrapper
{
public void Run()
{
Dependency.Register<AppSettings, AppSettings>(true);
var appSettings = Dependency.Resolve<AppSettings>();
appSettings.ConfigureUserOptions(Dependency.Resolve<IUserOptionsService>(), UserRoles.User);
}
}
在 UWP 中渲染选项
类库项目中的 OptionsViewModel
类包含一个 Groupings
属性,该属性从 IUserOptionsService
中检索 UserOptionGroupings
,如下所示
public IUserOptionGroupings Groupings =>
Dependency.Resolve<IUserOptionsService>().UserOptionGroupings;
OptionsPage
类通过 IoC 容器公开 OptionsViewModel
的实例,如清单 4 所示。
我们在 XAML 页面上同时使用 x:Bind
和 x:Binding
表达式,因此 OptionsViewModel
实例既作为属性公开,又设置为页面的 DataContext
。如果您不知道,x:Bind
的上下文是 Page 的一个属性,而传统的 x:Binding
表达式在使用属性时使用 Page 的 DataContext
。
清单 4. OptionsPage 类
public sealed partial class OptionsPage : Page
{
public OptionsPage()
{
this.InitializeComponent();
DataContext = Dependency.Resolve<OptionsViewModel, OptionsViewModel>(true);
}
public OptionsViewModel ViewModel
{
get => DataContext as OptionsViewModel;
set => DataContext = value;
}
}
在 OptionsPage.xaml 文件中,您会看到页面的资源包括一个 CollectionViewSource
声明,其 Source
属性绑定到视图模型的 Groupings
属性。请参见清单 5。
我们使用自定义模板选择器来确定用于 CollectionViewSource
中每个选项的 DataTemplate
。
清单 5. OptionsPage Resources 元素
<Page.Resources>
<CollectionViewSource x:Key="optionsViewSource"
IsSourceGrouped="True" Source="{x:Bind ViewModel.Groupings}" />
<local:OptionTemplateSelector
x:Key="optionTemplateSelector"
Templates="{StaticResource OptionTemplateCollection}">
</local:OptionTemplateSelector>
</Page.Resources>
OptionTemplateSelector
具有一个 Templates
属性,该属性绑定到 App.xaml 中找到的资源。请参见清单 6。
NamedTemplateCollection
包含用于显示每个用户选项的所有模板。有一个String 模板和一个Boolean 模板。String 和Boolean 的名称分别映射到 StringUserOption
和 BooleanUserOption
类的 TemplateName
属性。
注意:您可以通过设置用户选项的
TemplateName
属性来覆盖它使用的模板。
清单 6. NamedTemplateCollection 元素
<local:NamedTemplateCollection x:Key="OptionTemplateCollection">
<local:NamedTemplate Name="String">
<local:NamedTemplate.DataTemplate>
<DataTemplate>
<TextBox
Header="{Binding UserOption.Title, Mode=OneWay}"
Text="{Binding Setting, Mode=TwoWay}"
Style="{StaticResource OptionBox}" />
</DataTemplate>
</local:NamedTemplate.DataTemplate>
</local:NamedTemplate>
<local:NamedTemplate Name="Boolean">
<local:NamedTemplate.DataTemplate>
<DataTemplate>
<ToggleSwitch
Header="{Binding UserOption.Title, Mode=OneWay}"
IsOn="{Binding Setting, Mode=TwoWay}"
Margin="{StaticResource OptionItemMargin}" />
</DataTemplate>
</local:NamedTemplate.DataTemplate>
</local:NamedTemplate>
</local:NamedTemplateCollection>
自定义模板选择器名为 OptionTemplateSelector
,它继承自 Windows.UI.Xaml.Controls.DataTemplateSelector
。请参见清单 7。
SelectTemplateCore
方法尝试检索一个名称与 IUserOption
的 TemplateName
属性匹配的模板。使用一个缓存(一个 Dictionary<string, NamedTemplate>
)来高效地 O(1) 检索模板。
清单 7. OptionTemplateSelector 类
public class OptionTemplateSelector : DataTemplateSelector
{
public NamedTemplateCollection Templates { get; set; }
IDictionary<string, NamedTemplate> cache { get; set; }
void InitTemplateCollection()
{
cache = Templates?.ToDictionary(x => x.Name)
?? new Dictionary<string, NamedTemplate>();
}
protected override DataTemplate SelectTemplateCore(
object item, DependencyObject container)
{
if (cache == null)
{
InitTemplateCollection();
}
if (item != null)
{
var readerWriter = (IUserOptionReaderWriter)item;
var templateName = readerWriter.UserOption.TemplateName;
cache.TryGetValue(templateName, out NamedTemplate keyedTemplate);
if (keyedTemplate != null)
{
return keyedTemplate.DataTemplate;
}
}
DataTemplate result = base.SelectTemplateCore(item, container);
return result;
}
}
回到 OptionsPage.xml 文件,我们看到选项呈现在 ListView
中。请参见清单 8。ListView
绑定到 CollectionViewSource
以检索其选项分组,而 OptionTemplateSelector
为每个选项检索 DataTemplate
对象。
清单 8. 选项在 ListView 中呈现
<ListView ItemsSource="{Binding Source={StaticResource optionsViewSource}}"
ItemTemplateSelector="{StaticResource optionTemplateSelector}"
SelectionMode="None">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
示例应用程序显示其选项,如图所示
结论
应用程序的功能会随着时间的推移而增长和变化。在为应用程序构建设置屏幕时,明智的做法是将其设计成可以轻松地向屏幕添加设置,而无需花费时间重新进行用户界面设计。实现这一目标的一种方法是使用像 Codon FX 这样的第三方框架,它允许您只需一行代码即可向应用程序添加新的用户选项。
在本文中,您学习了如何配置 .NET Standard 项目和 UWP 应用以使用 Codon FX。您研究了定义包含应用程序中使用的设置的 AppSettings
类,以及如何公开其中一部分设置作为用户选项。您看到了如何为用户选项创建 DataTemplate
元素,以及如何使用数据模板集合在设置屏幕上呈现每个用户选项。
希望您觉得本文有用。如果是,那么如果您能评分或在下方留下反馈,我将不胜感激。
历史
2019 年 3 月
- 首次发布