目录
介绍
本地化、国际化和主题化
运行高级演示
什么是Avalonia
在多个平台上运行示例
主题/本地化代码位置
Nuget包位置
主题/本地化代码示例
示例代码位置
简单的主题示例
带有样式更改的简单主题
更改主题和语言示例
高级演示代码
结论
多年前,我遇到了一个用于本地化/国际化WPF应用程序的出色软件包。我成功地使用它来启用我构建的WPF应用程序,以便在英语和德语版本之间切换。
这个WPF包是由Tomer Shamam开发的,从那时起就从他以前的博客中删除了。他将代码发送给我,并在他的许可下,我将其发布在Github WPFLocalizationPackage存储库中。
最近,我再次需要国际化一个应用程序。最初,我想将该WPF包移植到Avalonia(WPF的一个出色的多平台开源后代)。最终,我决定从Tomer的WPF包中借鉴一些想法,从头开始构建一个新功能。
这个决定的主要原因是Avalonia的DynamicResource标记扩展和绑定工作得更好,并且没有困扰WPF同行的怪癖。因此,我没有创建自定义标记扩展,而是简单地利用了 Avalonia的DynamicResource。
我仍然使用Tomer的想法,即C#对象包含不同的本地化词典,可以在不同的语言环境之间轻松切换。同样重要的是,我创建了一个与他的演示非常相似的演示应用程序,以确保我的实现涵盖了他的所有功能等等。
这两个包——Tomer的原始包和我的Avalonia包——可用于更改任何控件上的任何Dependency(或Avalonia)属性,而不仅仅是与本地化相关的属性。因此,相同的功能可用于主题化或蒙皮——完全改变应用程序的外观。
我的本地化/国际化包的优点是:
为了展示新主题和L10N(本地化)包的强大功能,我将从一个高级示例开始。此时不要看代码(后面会解释)。从NP.Demos.ThemingAndLocalizationDemo下载并运行演示。
这是您将看到的内容:
右上角的两个组合框允许选择语言(“英语”、“希伯来语”或“俄语”)和颜色主题(“深色”或“浅色”)。上图显示了英语和深色主题下的应用程序。
这是希伯来语和浅色主题下的应用程序视图:
这是俄罗斯/深色组合:
Avalonia是一个出色的多平台开源UI框架,用于开发跨Windows、Mac和Linux运行的桌面解决方案。
Avalonia的功能类似于WPF,但除了多平台之外,Avalonia已经比WPF更强大且错误更少。
浏览器中的Avalonia也将于今年年底推出(请参阅Avalonia in Browser Demo中的一个小但非常令人印象深刻的演示)。
移动版Avalonia也即将推出。
一旦浏览器中的Avalonia和移动版Avalonia发布,它将比用于构建任何类型的多平台应用程序的任何竞争框架好几英里:桌面、浏览器和移动。
Avalonia教程和有关这个精彩软件包的更多信息可以在我的文章中找到:
本文中的所有示例均已在Windows 10、Mac Catalina和Ubuntu 20.04上进行了测试
新的主题/L10N功能是NP.Avalonia.Visuals开源包的一部分。这个包也可以有其他用途,我计划写另一篇文章来解释它最重要的功能。
还有另一个项目NP.ViewModelInterfaces包含由一些Visual对象实现的非可视界面。此代码的目的是在非可视项目中使用,例如,在不引用Avalonia代码的View Model项目中控制和查询某些可视对象。到目前为止,NP.ViewModelInterfaces仅包含与主题和本地化功能相关的接口:IThemeLoader.cs和ThemeInfo.cs
Nuget包可从nuget.org的NP.Avalonia.Visuals获得。它取决于将自动安装的其他几个软件包。
您不需要单独引用Avalonia软件包,因为它们将通过安装NP.Avalonia.Visuals来安装。
如果您想从非可视(视图模型)项目中对主题/l10n功能进行一些控制,您可以只安装NP.ViewModelInterfaces也可从nuget.org获得。
所有Theming/L10N演示代码都可以在NP.Demos.ThemingAndL10N获得。
此示例位于NP.Demos.SimpleThemingSample下
下载它,编译并运行。这是您将看到的内容:
按顶部的“深色主题”按钮。您会看到背景变为黑色,而文本颜色(前景)变为白色:
只有顶部的两个按钮不会改变。
查看主项目中的文件:
ColorThemes文件夹下有两个XAML文件,名为DarkResources.axaml和LightResources.axaml。让我们来看看它们。这是DarkResources.axaml的内容:
这是LightResources.axaml:
它们包含具有相同键和相反颜色的Avalonia资源——在DarkResources.axaml中 BackgroundBrush是'Black'而ForegroundBrush是白色——而在LightResources.axaml中则相反。
在通常的主要解决方案文件中,只有App.axaml、MainWindow.axaml和MainWindow.axaml.cs有一些不平凡的变化。
查看App.axaml的内容:
完成定义ThemeLoader的技巧的重要代码包含在ResourceDictionary.MergeDictionaries标记中:
ThemeLoader本质上是一个可以交换其内容的智能ResourceDictionary。
我们使用ThemeInfo对象定义了两个ThemeLoader主题。第一个ThemeInfo对象指定了深色主题——它的Id是"Dark"并且它的ResourceUrl="/ColorThemes/DarkResources.axaml"被设置为指向上述DarkResources.axaml文件。第二个ThemeInfo对象通过ResourceUrl指向LightResource.asaml文件来指定浅色主题。它的Id是"Light"。
主题加载器可以通过更改其SelectedThemeId来交换其资源内容。最初,它被设置为"Light"第二个ThemeInfo对象的Id,所以它在应用程序启动时加载,我们让应用程序在浅色主题下运行。
我们将我们的ThemeLoader属性Name设置为“ColorThemeLoader”。通过这个名称,我们将能够在后面的MainWindow.axaml.cs代码中找到这个加载器。
按下按钮“Dark Theme”将导致后面的代码(在MainWindow.axaml.cs文件中)将我们ThemeLoader的“SelectedThemeId”更改为“ Dark”,以便应用程序更改其颜色。
MainWindow.axaml文件非常简单:
MainWindow's Background属性使用DynamicResource标记扩展连接到BackgroundBrush资源(在深色主题下指向黑色,在浅色主题下指向白色):Background="{DynamicResource BackgroundBrush}"
TextBlock's Foreground使用相同的方法来获取ForegroundBrush资源的值:Foreground="{DynamicResource ForegroundBrush}".
以下是MainWindow.axaml.cs文件内容的高亮代码:
public partial class MainWindow : Window
{
// reference to ThemeLoader object defined
// in XAML
private ThemeLoader _themeLoader;
public MainWindow()
{
InitializeComponent();
...
// find the theme loader by its name
_themeLoader =
Application.Current.Resources.GetThemeLoader("ColorThemeLoader")!;
// set the handler for lightButton's click event
Button lightButton = this.FindControl
首先,我们通过将其名称传递给GetThemeLoader扩展方法来获取在XAML中定义的ThemeLoader对象:
// find the theme loader by its name
_themeLoader =
Application.Current.Resources.GetThemeLoader("ColorThemeLoader")!;
接下来,我们得到XAML中定义的LightButton和DarkButton的引用,并为它们Click事件设置处理程序:
// set the handler for lightButton's click event
Button lightButton = this.FindControl("LightButton");
lightButton.Click += LightButton_Click;
// set the handler for darkButton's click event
Button darkButton = this.FindControl("DarkButton");
darkButton.Click += DarkButton_Click;
在每个处理程序中,我们相应地为亮和暗按钮设置SelectedThemeId为string "Light"或"Dark":
private void LightButton_Click(object? sender, RoutedEventArgs e)
{
// set the theme to Light
_themeLoader.SelectedThemeId = "Light";
}
private void DarkButton_Click(object? sender, RoutedEventArgs e)
{
// set the theme to Dark
_themeLoader.SelectedThemeId = "Dark";
}
前面的示例展示了如何更改背景和文本颜色。然而,顶部的“Light Theme”和“Dark Theme”按钮并没有改变——它们仍然是深色的,因为在我们定义
此示例的目的是展示当我们更改主题时如何更改对BaseLight.axaml文件的引用,该文件也可从Avalonia获得。
此演示的代码可在NP.Demos.SimpleThemingSampleWithStyleChange获得。
编译并运行演示——这是您将看到的:
请注意,按钮是轻的。然后按“深色主题”按钮——主题改变,按钮也改变:
与上一个示例的唯一代码差异位于App.asaml文件中:
请注意,每个ThemeInfo对象都在前面的示例中解释了顶部Id和ResourceUrl属性上定义了StyleUrl:
“Dark”主题对应的ThemeInfo对象指向“BaseDark.xaml”文件,而定义“Light”主题的另一个ThemeInfo对象指向BaseLight.xaml。
主题加载器现在定义了一个属性StyleResourceName:StyleResourceName="ColorLoaderStyles">。选择此属性值时应避免与ThemeLoader中包含的资源的任何资源键发生冲突。StyleReference对象使用此值来引用我们ThemeLoader选择的依赖于主题的Style:
...
此示例的目的是演示一个支持独立更改其颜色主题和语言的应用程序,以便支持颜色主题和语言的每种组合。
此演示的代码可在以下URL中找到:NP.Demos.SimpleThemingAndL10NSample。
以下是下载、编译和运行示例后您将看到的内容:
按下“深色主题”按钮会将颜色主题更改为“深色”:
按“希伯来语”按钮会将文本更改为希伯来语:
按下“Light Theme”按钮将再次将颜色主题更改为“Light ”:
查看演示应用程序中的项目文件:
此示例有两组字典:
颜色主题文件——DarkResources.axaml和LightResources.axaml与前面的示例完全相同。
看看EnglishDictionary.axaml:
Theming Demo
Hello World from Avalonia !!!
Window Title is '{0}'
它是一个非常简单的字典文件,它定义了三种 string资源——“WindowTitle”、“WelcomeText”和“WindowTitleText”。
HebrewDictionary.axaml定义了相同的资源,但它们的值被翻译成希伯来语。
WindowTitle控制Window的标题,WelcomeText是窗口内显示的文本,WindowTitleText也作为第二行显示在窗口内。我添加它以显示使用Avalonia绑定的文本的动态替换。请注意,WindowTitleText的文本“Window Title is '{0}'”具有“{0}”部分,该部分将替换为窗口的标题。下面将解释它是如何实现的。
打开App.axaml文件。它定义了两个ThemeLoaders——一个用于颜色主题,另一个用于语言:
ColorThemeLoader默认选择"Light"和LanguageLoader "English"(SelectedThemeId="English")。
现在打开MainWindow.asaml文件:
Window标签将其Title设置为WindowTitle, Background 设置为来自主题/本地化字典的BackgroundBrush:
Title="{DynamicResource WindowTitle}"
Background="{DynamicResource BackgroundBrush}"
顶部定义了四个按钮:两个按钮用于深色和浅色主题,两个按钮用于英语和希伯来语。
TextBlocks将它们Text和Foreground属性设置为动态设置为语言和颜色字典中定义的资源,例如,
Text="{DynamicResource WelcomeText}"
Foreground="{DynamicResource ForegroundBrush}"
第二个TextBlock有一种巧妙的方式使用MultiBinding来绑定其文本——将单个目标绑定到多个源。这样做是为了扩展string自动插入的Window's Title属性值:
我们使用NP.Avalonia.Visuals包中StringFormatConverter定义的。它将第一个string作为格式,其余作为参数并调用它们的string.Format(string format, params object[] args)方法。多重绑定中的第一个绑定由DynamicResourceExtension提供(是的,在Avalonia中——DynamicResource只是一个绑定,因此它可以插入到MultiBinding中作为它的Binding子元素之一)。此绑定返回string格式(对于英语,应该是"Window Title is '{0}'")。
第二个绑定返回要插入第一个string的窗口的标题(代替“{0}”)。
现在看一下MainWindow.axaml.cs文件,它与上一个示例的同名文件非常相似,只是在这里,我们定义了两个ThemeLoader对象(_colorThemeLoader和_languageThemeLoader)并将处理程序分配给4个而不是2个按钮的Click事件:
public partial class MainWindow : Window
{
private ThemeLoader _colorThemeLoader;
private ThemeLoader _languageThemeLoader;
public MainWindow()
{
InitializeComponent();
...
// find the color theme loader by name
_colorThemeLoader =
Application.Current.Resources.GetThemeLoader("ColorThemeLoader")!;
// find the language theme loader by name
_languageThemeLoader =
Application.Current.Resources.GetThemeLoader("LanguageLoader")!;
Button lightButton = this.FindControl("LightButton");
lightButton.Click += LightButton_Click;
Button darkButton = this.FindControl("DarkButton");
darkButton.Click += DarkButton_Click;
Button englishButton = this.FindControl("EnglishButton");
englishButton.Click += EnglishButton_Click;
Button hebrewButton = this.FindControl("HebrewButton");
hebrewButton.Click += HebrewButton_Click;
}
private void LightButton_Click(object? sender, RoutedEventArgs e)
{
// set the theme to Light
_colorThemeLoader.SelectedThemeId = "Light";
}
private void DarkButton_Click(object? sender, RoutedEventArgs e)
{
// set the theme to Dark
_colorThemeLoader.SelectedThemeId = "Dark";
}
private void EnglishButton_Click(object? sender, RoutedEventArgs e)
{
// set language to English
_languageThemeLoader.SelectedThemeId = "English";
}
private void HebrewButton_Click(object? sender, RoutedEventArgs e)
{
// set language to Hebrew
_languageThemeLoader.SelectedThemeId = "Hebrew";
}
...
}
我们已经在本文的介绍部分展示了高级演示,所以这里我们只讨论代码(位于NP.Demos.ThemingAndLocalizationDemo)。
从概念上讲,高级演示的代码除了上面简单示例中讨论的内容之外并没有包含太多新内容。更多属性被本地化,包括窗口和控件大小、应用程序流程(希伯来语是从右到左书写和查看的)、水平对齐等。此外,RussianResources.asaml文件已添加到LanguageDictionaries中。
此演示中使用的一个功能值得特别讨论。该演示使用DynamicResourceBinding对象,例如,
DynamicResourceBinding结合了Binding和DynamicResource的功能。它绑定到一个string或一个对象,然后将其用作DynamicResource的资源键。当提供资源键的属性或资源键指向的动态资源改变它们的值时,DynamicResourceBinding返回值将会改变。
我创建DynamicResourceBinding是为了匹配Tomer的自定义标记扩展提供的一些功能,但它对于自定义应用程序也非常有用。
我将在以后专门讨论NP.Avalonia.Visuals包功能的文章中详细介绍DynamicResourceBinding。
本文解释并提供了我在多平台桌面应用程序中构建和使用的基于Avalonia的主题化/本地化功能的详细示例。
此功能的灵感来自于Tomer Shamam编写的旧WPF包。
此功能是作为NP.Avalonia.Visuals nuget包和Github存储库的一部分在最简单和最宽松的MIT许可证下发布的——这本质上意味着您可以在任何应用程序中使用它,无论是否商业,只要您不责怪作者(我)可能的错误并提供简短的归因。
此处描述的所有示例均在Windows 10、Mac Catalina和Ubuntu 20.04机器上进行了测试。
https://www.codeproject.com/Articles/5317972/Theming-and-Localization-Functionality-for-Multipl