WPF新手村教程(四)中Application类如何?
摘要:WPF个人文档(四)—— Application类 前言 这一块其实本来是不太想讲的,你不太了解,不影响你写代码,遇到不会的随用随查即可 但是,对这个类有一定的了解之后,会帮助我们更快的了解WPF应用程序的结构,对应理解架构会有很多帮助 但
WPF个人文档(四)—— Application类
前言
这一块其实本来是不太想讲的,你不太了解,不影响你写代码,遇到不会的随用随查即可
但是,对这个类有一定的了解之后,会帮助我们更快的了解WPF应用程序的结构,对应理解架构会有很多帮助
但是,即便如此,实际开发当中使用的并不会特别多,没人会去在乎你对这个类了解的有多好
没人会去在乎你这个小小的员工架构能力有多强,也没人去在乎你会的东西有多么多
公司只会在乎你能不能赚取到足够的价值
一.Application类定义
Application 位于命名空间:
System.Windows.Application;
它是一个 单例 类,一个 WPF 应用通常只有一个 Application 实例,可以通过 Application.Current 获取当前实例
该类跟踪在应用程序中打开的所有窗口,决定何时关闭应用程序,并引发可执行初始化和清除操作的应用程序事件
(出自《WPF编程宝典:使用C# 2012和.NET 4.5 第4版》)
其主要职责:
1.启动和退出应用程序
2.管理全局资源(资源字典)
3.管理主窗口和打开的所有窗口
4.处理全局异常
5.支持命令和消息循环
二.ShutdownMode —— Application 类控制程序关闭的方式
WPF 中 Application 类控制程序关闭的三种方式,也就是 ShutdownMode 属性的三种枚举值
1.OnMainWindowClose
当 Application.Current.MainWindow 被关闭时,应用程序退出
适合单窗口应用或有明确主窗口的多窗口应用
2.OnLastWindowClose
当应用程序打开的最后一个窗口被关闭时,应用程序退出
适合多窗口应用,不需要明确主窗口
3.OnExplicitShutdown
应用程序不会自动退出,必须手动调用 Application.Current.Shutdown() 才会结束
适合需要后台逻辑或延迟关闭的场景
示例代码:(C#或者xaml)
// 方式1:主窗口关闭时退出
Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
// 方式2:最后一个窗口关闭时退出
Application.Current.ShutdownMode = ShutdownMode.OnLastWindowClose;
// 方式3:手动关闭
Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
// ... 什么时候需要退出,再调用
Application.Current.Shutdown();
<Application x:Class="Routing.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Routing"
<!-- Application类关闭程序的三种方法 -->
ShutdownMode="OnExplicitShutdown"
<!--ShutdownMode="OnLastWindowClose"-->
<!--ShutdownMode="OnExplicitShutdown"-->
StartupUri="Route.xaml">
<Application.Resources>
</Application.Resources>
</Application>
[!TIP]
WPF中,程序关闭的几种方式:
1.Application.Currrent.Shutdown();
2.Close();
3.Environment.Exit(0);
4.Process对象实例.Kill();
5.Environment类的FailFast
// WPF中,程序关闭的几种方式:
// 1.WPF官方正途
Application.Currrent.Shutdown();
// 2.这只是关闭窗口,不一定能够关闭程序
Close();
// 3.已经跳出 WPF 了,进入 CLR 级别终止 —— 一般不推荐为正常退出方法
Environment.Exit(0);
// 4.操作系统级的强制终止 => 等价于任务管理器 → 结束任务
// Process对象实例.Kill();
Process.Kill();
// 5.一般是给"内存损坏 / 严重不可恢复错误"准备的,即异常终止层面
Environment.FailFast();
# 程序关闭从温柔乡到核弹打击的排行顺序(如果不是以为嫌麻烦,我应该会排一个从夯到拉的表......)
Shutdown()
↓
Close()
↓
Environment.Exit()
↓
Process.Kill()
↓
FailFast()
参考:52.第7章_应用程序关闭方式_哔哩哔哩_bilibili
三.事件
Application 类提供了丰富的全局事件
事件
说明
Startup
应用程序启动时触发
Exit
应用程序退出时触发
DispatcherUnhandledException
UI 线程未捕获异常处理
SessionEnding
用户注销或关机前触发
Activated / Deactivated
应用程序获得 / 失去焦点时触发
.......
......
代码示例:(事件订阅)
public App()
{
Startup += App_Startup;
// DispatcherUnhandledException += App_DispatcherUnhandledException;
// Exit += App_Exit;
}
private void App_Startup(object sender, StartupEventArgs e)
{
// 启动逻辑
}
[!TIP]
当然,你不一定非要使用上面的事件,也可以现在重写下面的事件(事件重写)
// 应用程序启动时调用,早于任何窗口显示
protected override void OnStartup(StartupEventArgs e)
// 应用程序即将退出时调用
protected override void OnExit(ExitEventArgs e)
// 当用户会话即将结束(注销或关机)时调用,可取消
protected override void OnSessionEnding(SessionEndingCancelEventArgs e)
// 当应用程序成为当前活动程序(获得焦点)时调用
protected override void OnActivated(EventArgs e)
// 当应用程序失去焦点时调用
protected override void OnDeactivated(EventArgs e)
// ......
四.程序集资源和资源字典
1.程序集资源 —— Assembly Resource
程序集资源:程序集资源,是指嵌入程序内部的二进制资源,如文本、图片、视频、声音和松散文件(Loose file)等等,
对于这些资源项我们可以将其存储为松散文件或者编译进程序集中
程序集资源 = 被嵌入到 .exe/.dll 内部,由 CLR 统一管理的数据文件
换句话说:程序集资源 = 被编译进 .exe 或 .dll 内部的文件数据
[!TIP]
如果你还是不太好理解,可以这样想象:
程序集 = 一个压缩包
资源 = 压缩包里的文件
从本质上讲一个 .NET 程序集内部包含:IL 代码、元数据(类型信息)、清单(Manifest)、资源(Resources)
资源本质上是“二进制数据块”,可以是:图片、音频、文本、json、xml、图标、任何文件
它们被打包在程序集内部,而不是放在外部目录
程序集资源文件使用示例:
1.首先,我们先在项目中建立一个资源文件夹Resources,然后添加目标资源文件(可以是图片,也可以是音频)
2.然后右击1.png文件,选择属性,在属性中找到生成操作,选择资源
3.这里示例一下程序集资源的使用,你可以在xaml代码中使用,也可以在C#代码中使用
ImageSource="/Resource/1.png"代表图片资源的目录(相对路径)
<Grid Margin="20">
<Grid.Background>
<ImageBrush ImageSource="/Resource/1.png"
Stretch="UniformToFill"/>
</Grid.Background>
</Grid>
[!IMPORTANT]
如何在C#代码中使用程序集资源:
先埋个雷,问:注释掉的代码和没注释掉的代码,哪一个可以正常使用?为什么?
namespace Routing
{
/// <summary>
/// Route.xaml 的交互逻辑
/// </summary>
public partial class Route : Window
{
public Route()
{
InitializeComponent();
// var image = new BitmapImage(
// new Uri("Resource/1.png", UriKind.Relative));
var image = new BitmapImage(new Uri("pack://application:,,,/Resource/1.png"));
// 创建了一个 URI 对象但没有用,也不会影响加载
new Uri("Resource/1.png", UriKind.Relative);
var brush = new ImageBrush(image);
brush.Stretch = Stretch.UniformToFill;
RootGrid.Background = brush;
}
}
}
答案:第一段代码将无法运行,第二段代码可以正常运行
1.第一段代码不能正常加载 Resource 类型的图片:
UriKind.Relative 表示相对路径
WPF 的相对路径是 相对于运行时当前工作目录,或者相对于 Pack URI 的默认规则
当图片 Build Action = Resource 时,WPF 不会把它放在 bin/Debug 目录
也就是说这个相对路径在运行时找不到文件 → 图片加载失败
2.第二段代码使用了 完整 Pack URI :
pack://application:,,,/Resource/1.png明确告诉 WPF 去 程序集内部查找资源
Build Action = Resource 的文件可以被找到
代码
能否找到资源
原因
new Uri("Resource/1.png", UriKind.Relative)
❌
Resource 类型文件在程序集内部,相对路径指向磁盘(相对于运行时当前工作目录)找不到
new Uri("pack://application:,,,/Resource/1.png")
✅
Pack URI 明确告诉 WPF 从程序集内部读取资源
2.资源字典 —— ResourceDictionary
字典:键值对 一开始从C/C++过来时,差点被这个词哄住,然后发现,不就是一个键值对吗
资源字典:就是一张键值表(Dictionary),用来存放可复用对象,并提供按作用域查找的机制
键(Key) → 对象名称(x:Key)
值(Value) → 任意对象(Brush、Style、Template、Converter、图片、甚至普通类)
[!IMPORTANT]
静态资源StaticResource和动态资源DynamicResource
StaticResource → 编译/加载时查找,之后不变
DynamicResource → 运行时查找,可响应资源变化(主题切换)
使用示例:
Route.xaml
局部资源字典:
# 它属于这个 Window 的作用域
# 查找链是:控件 → Window → Application → 系统
<Window.Resources>
<SolidColorBrush x:Key="PrimaryBrush" Color="DodgerBlue"/>
</Window.Resources>
<Window x:Class="Routing.Route"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Routing"
mc:Ignorable="d"
Title="Route" Height="450" Width="800">
<!--局部资源字典-->
<Window.Resources>
<SolidColorBrush x:Key="PrimaryBrush" Color="DodgerBlue"/>
</Window.Resources>
<Grid x:Name="RootGrid" Margin="20">
<StackPanel>
<!--全局资源字典引用-->
<Button Height="50" Width="50" Style="{StaticResource my_style1}"/>
<!--局部资源字典引用-->
<Button Height="50" Width="50" Background="{StaticResource PrimaryBrush}" />
</StackPanel>
</Grid>
</Window>
资源字典文件T_Dic.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!--我们现在写的这个只是一个"资源容器文件"-->
<!--如果你不在某处合并它,WPF 根本不会加载它-->
<Style x:Key="my_style1" TargetType="Button">
<Setter Property="Background" Value="Red"/>
<Setter Property="FontSize" Value="45"/>
</Style>
</ResourceDictionary>
App.xaml
<Application x:Class="Routing.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Routing"
StartupUri="Route.xaml">
<!-- 合并资源字典文件,在这里合并会成为全局资源字典 -->
<Application.Resources>
<ResourceDictionary Source="/T_Dic.xaml"/>
</Application.Resources>
</Application>
五.资源本地化
这东西完全就是痛苦面具,相当痛苦啊,这个本地化啊,那是一坨啊,谁负责本地化,谁就痛苦面具到项目结束吧
网上你可以查到一堆本地化的方式,但是一写一个不吱声,一写一堆错,写一行掉一行泪啊
然后就是,资源本地化压根就没有什么最优选的方案,你可能使用的每一种方法都有问题,使用的每一种本地化都是痛苦面具
而且官方推荐的本地化方式也是一坨啊,问题还不少.......甚至官方本地化工具还不是完全开放的,意义何在啊喂
本地化:什么是本地化?说白了就是语言适配版本设置,
你打开你的手机,主题语言切换到英文,这就是英文的本地化
再选择中文切回去,这就是中文的本地化
这里找的很多资料,但是感觉都有问题,最终在B站找到十月寒流大佬的教学视频,是目前我能找到的最好的教学了
所以这一小节基本上都是up的视频相关内容,有必要十分推荐去看看视频
参考视频:如何在WPF中实现支持运行时切换语言的本地化效果_哔哩哔哩_bilibili
up主页:十月的寒流的个人空间-十月的寒流个人主页-哔哩哔哩视频
教学视频中都是点到为止,简单讲解了几种不同的本地化方法,没有最好的方法,只有你用起来最顺手的方法
0.本地化前源代码
<Window x:Class="LocalizeDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:LocalizeDemo"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Padding" Value="8,5"/>
<Setter Property="FontSize" Value="18"/>
</Style>
</Window.Resources>
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="欢迎使用本示例软件" FontSize="32" />
<StackPanel Margin="0,16,0,0"
HorizontalAlignment="Center"
Orientation="Horizontal">
<Button Content="英文" />
<Separator Width="10" Opacity="0" />
<Button Content="中文" />
</StackPanel>
<Button Name="MessageButton" Margin="0,16,0,0"
HorizontalAlignment="Center"
Content="显示消息"
Click="ShowMessageButton_Click"/>
</StackPanel>
<TextBlock Margin="20"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Text="@十月的寒流"
FontSize="28"
Foreground="LightGray" />
</Grid>
</Window>
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace LocalizeDemo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ShowMessageButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("这是一条神秘的消息");
}
}
}
1.本地化方法一 :资源字典(动态资源)
该方法仅适合小型项目或演示项目,如果要适用于商业大型项目,你就偷着哭吧
优点🌱
说明
简单直观
使用 XAML ResourceDictionary,无需复杂的配置或第三方库
支持动态切换
运行时可即时切换语言,无需重启应用(DynamicResource)
集中管理
所有翻译文本在专属目录(I18N)集中存储,易于维护
类型安全减少
使用 Key-Value 方式,减少硬编码字符串
XAML 原生支持
{DynamicResource} 是 WPF 原生机制,无性能开销
易于扩展
增加新语言只需创建新的 ResourceDictionary 文件
缺点🌱
说明
文件数量多,维护困难
每种语言需要专用的 XAML 文件,维护成本随语言增加而增长
你也不想你有一万处代码,需要修改一万个地方吧
没有翻译管理工具,缺少语言回退
纯手动编辑 XAML,容易遗漏或出错,无法使用专业翻译工具
无法检测系统语言
应用启动时不能自动检测系统语言,需手动指定
不支持复杂数据
ResourceDictionary 只能存储简单字符串,无法处理复杂语法规则
版本控制不便
二进制 XAML (.baml) 编译难以进行文本对比
缺少代码提示以及编译时代码提示
大量代码处无法出现代码提示,仅能复制粘贴,容易出错
如果出现不存在的资源,它不会报错,只会给一个空的数据,排查困难
三方库或类库本地化难以进行本地化
如果你能用这个方法对他们进行本地化,一定记得私聊我,大佬,求教!
优化建议:
不一定非要在xaml代码文件中添加资源,也可以在例如App.xaml.cs中添加代码
或者使用Excel等文件在项目启动时,导入文件中的数据,生成资源字典
示例代码:
# 项目代码Tree状图
LocalizeDemo
├── App.xaml
├── App.xaml.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
└── I18N # I18N是指国际化的意思,但是I18N和本地化其实差不太多,别管定义怎么样,反正都得难受
├── String.en.xaml
└── String.zh.xaml
MainWindow.xaml
<Window x:Class="LocalizeDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:LocalizeDemo"
mc:Ignorable="d"
Title="{DynamicResource WindowTitle}"
Height="450" Width="800"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Padding" Value="8,5"/>
<Setter Property="FontSize" Value="18"/>
</Style>
</Window.Resources>
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{DynamicResource WelcomeMessage}" FontSize="32" />
<StackPanel Margin="0,16,0,0"
HorizontalAlignment="Center"
Orientation="Horizontal">
<Button Content="{DynamicResource EnglishButtonContent}" Tag="en" Click="ChangeLanguage_Click"/>
<Separator Width="10" Opacity="0" />
<Button Content="{DynamicResource ChineseButtonContent}" Tag="zh" Click="ChangeLanguage_Click"/>
</StackPanel>
<Button Name="MessageButton" Margin="0,16,0,0"
HorizontalAlignment="Center"
Content="{DynamicResource ShowMessageButtonContent}"
Click="ShowMessageButton_Click"/>
</StackPanel>
<TextBlock Margin="20"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Text="{DynamicResource LogoText}"
FontSize="28"
Foreground="LightGray" />
</Grid>
</Window>
MainWindow.xaml.cs
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace LocalizeDemo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ShowMessageButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(
Application.Current.TryFindResource("MessageContent").ToString(),
Application.Current.TryFindResource("MessageCaption").ToString(),
MessageBoxButton.OK
);
}
private void ChangeLanguage_Click(object sender, RoutedEventArgs e)
{
var culture = ((Button)sender).Tag.ToString();
var resourceDictionary = new ResourceDictionary
{
Source = new Uri($"I18N/String.{culture}.xaml", UriKind.Relative)
};
Application.Current.Resources.MergedDictionaries.Add(resourceDictionary);
}
}
}
String.en.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"> <!--assembly=System.Core-->
<sys:String x:Key="WindowTitle">WPF I18N Demo</sys:String>
<sys:String x:Key="WelcomeMessage">Welcome to this demo software</sys:String>
<sys:String x:Key="EnglishButtonContent">English</sys:String>
<sys:String x:Key="ChineseButtonContent">Chinese</sys:String>
<sys:String x:Key="ShowMessageButtonContent">Show Message</sys:String>
<sys:String x:Key="MessageCaption">Notice</sys:String>
<sys:String x:Key="MessageContent">This is a message from code behind.</sys:String>
<sys:String x:Key="LogoText">@ColdWind</sys:String>
</ResourceDictionary>
String.zh.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"> <!--assembly=System.Core-->
<sys:String x:Key="WindowTitle">WPF 本地化演示</sys:String>
<sys:String x:Key="WelcomeMessage">欢迎使用此演示软件</sys:String>
<sys:String x:Key="EnglishButtonContent">英文</sys:String>
<sys:String x:Key="ChineseButtonContent">中文</sys:String>
<sys:String x:Key="ShowMessageButtonContent">显示消息</sys:String>
<sys:String x:Key="MessageCaption">通知</sys:String>
<sys:String x:Key="MessageContent">这是来着后台的神秘消息.</sys:String>
<sys:String x:Key="LogoText">@十月的寒流</sys:String>
</ResourceDictionary>
App.xaml
<Application x:Class="LocalizeDemo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LocalizeDemo"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- 默认加载一种语言,这里是中文 -->
<!--<ResourceDictionary Source="/I18N/String.en.xaml"/>-->
<ResourceDictionary Source="/I18N/String.zh.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
2.🌱本地化方法二 :resx资源文件(官方推荐 )
[!IMPORTANT]
.resx文件和.Resource文件
在此之前,我们先来简单介绍一下.resx文件和.Resource文件
.resx文件
.resx 是 可读的 XML 资源文件,可用文本编辑器打开,看见 <data> 节点
它存在的目的,是让程序员方便编辑资源
典型用途:多语言字符串,小图标,配置文本,本地化内容
当编译项目时,.resx 会被编译成 .resources 二进制文件
# 它们的关系有点类似C#代码和DLL文件:
.cs → .dll
.resx → .resources
.Resource文件
.resources 是 二进制资源文件,给 CLR 使用的,也不会有人吃饱了没事干手动创建它
所以和我们就没有任何关系了,干脆一笔带过吧
它通常由.resx 编译生成或用 resgen.exe 工具生成
加载方式是通过ResourceManager加载的( .NET 标准资源系统)
换句话说:.resx 是“源代码”.resources 是其“编译后的产物”
项目
.resx 文件
.resources 文件
WPF Resource (Build Action)
文件性质
XML 文本文件
二进制文件
任意文件(图片/XAML等)
是否可直接编辑
可以
不可以
原文件可编辑
主要用途
本地化字符串、资源管理
编译后供 CLR 加载
UI 图片、样式、XAML 等
所属系统
.NET 标准资源系统
.NET 标准资源系统
WPF Pack URI 资源系统
加载方式
ResourceManager 自动加载
ResourceManager 加载
pack://application:,,,/ 访问
是否支持自动 Culture 匹配
支持
支持
不支持(需手动管理)
与其他的关系
编译后生成 .resources
由 .resx 生成
独立体系,不通过 .resx
典型使用场景
多语言文本
程序内部资源读取
界面图片、样式字典
优点
说明
Microsoft 官方推荐
是 .NET Framework/Core 的标准本地化方式,得到原生支持
强类型访问
自动生成 Lang.Designer.cs,编译时检查,避免魔法字符串,支持智能提示
文件编辑友好
.resx 是 XML,可用 Visual Studio 界面编辑器,直观易用
卫星程序集支持
可编译为独立的 .resources.dll,实现 DLL 级本地化,支持部署灵活性
自动化代码生成
PublicResXFileCodeGenerator 自动生成访问代码(Lang.WindowTitle 等)
工具链完整
与翻译管理工具(Resharper、LocBaml)集成良好
性能较优
编译时生成二进制资源,运行时加载速度快
文化fallback
自动支持文化回退链(如无 zh-CN,自动回退到 zh,再回退到默认)
缺点
说明
XAML 绑定困难
WPF XAML 中难以直接绑定 resx 资源(需要 Converter 或代码)
无动态切换
必须重启应用或重新加载程序集来切换语言,不支持实时切换
冗余文件
需要为每种语言创建独立的 .resx 文件,必须手义维护结构一致
复杂部署
使用卫星程序集时需配置特定的目录结构和文化标识符
二进制编译
.resx 编译后是二进制,版本控制和合并冲突处理困难
示例代码
但是,在我们写代码之前先创建两个resx文件
1.创建一个名叫Resources的文件夹(但是也不是必须叫这个),选择 添加 => 新建项 => 资源文件
2.我们这里的本地化有两种,一直是中文(默认),一种是英文,所以需要创建两个.resx资源文件
这里创建的两个资源文件分别叫:Lang.resx和Lang.zh-CN.resx
[!WARNING]
特别注意一下,如果另一个资源文件不是 规定的格式(这里xxx.yy-CN.resx中,CN代表的中文的意思)
在资源浏览器中,不会出现以下的格式(不会出现红色选框标记的表头),也就是不能快捷编辑本地化了
3.待我们将需要的资源全部编辑以后,才开始写我们的代码,以下代码均可复制粘贴直接使用
MainWindow.xaml
<Window x:Class="LocalizeDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:LocalizeDemo"
xmlns:lang="clr-namespace:LocalizeDemo.Resources"
mc:Ignorable="d"
Title="{x:Static lang:Lang.WindowTitle}"
Height="450" Width="800"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Padding" Value="8,5"/>
<Setter Property="FontSize" Value="18"/>
</Style>
</Window.Resources>
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{x:Static lang:Lang.WelcomeMessage}" FontSize="32" />
<StackPanel Margin="0,16,0,0"
HorizontalAlignment="Center"
Orientation="Horizontal">
<Button Content="{x:Static lang:Lang.EnglishButtonContent}" Tag="en" Click="ChangeLanguage_Click"/>
<Separator Width="10" Opacity="0" />
<Button Content="{x:Static lang:Lang.ChineseButtonContent}" Tag="zh-CN" Click="ChangeLanguage_Click"/>
</StackPanel>
<Button Margin="0,16,0,0"
HorizontalAlignment="Center"
Content="{x:Static lang:Lang.ShowMessageButtonContent}"
Click="ShowMessageButton_Click"/>
</StackPanel>
<TextBlock Margin="20"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Text="{x:Static lang:Lang.LogoText}"
FontSize="28"
Foreground="LightGray" />
</Grid>
</Window
MainWindow.xaml.cs
using LocalizeDemo.Resources;
using System.Globalization;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace LocalizeDemo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ShowMessageButton_Click(object sender, RoutedEventArgs e)
{
//MessageBox.Show(
// Application.Current.TryFindResource("MessageContent").ToString(),
// Application.Current.TryFindResource("MessageCaption").ToString(),
// MessageBoxButton.OK
//);
MessageBox.Show(Lang.MessageContent, Lang.MessageCaption, MessageBoxButton.OK);
}
// 切换语言
private void ChangeLanguage_Click(object sender, RoutedEventArgs e)
{
var culture = ((Button)sender).Tag.ToString();
//var resourceDictionary = new ResourceDictionary
//{
// Source = new Uri($"I18N/String.{culture}.xaml", UriKind.Relative)
//};
//Application.Current.Resources.MergedDictionaries.Add(resourceDictionary);
var cultureInfo = new CultureInfo(culture!);
Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;
var newWindow = new MainWindow();
newWindow.Show();
Application.Current.MainWindow = newWindow;
this.Close();
}
}
}
App.xaml.cs
using System.Configuration;
using System.Data;
using System.Globalization;
using System.Windows;
namespace LocalizeDemo
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public App()
{
var culture = new CultureInfo("en");
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
}
}
}
虽然但是啊,这里up给的代码中有一个非常大的问题,new一个新窗口,打开新窗口,关闭旧窗口,这样开销会非常大
但是如果你选择实时刷新的话,又要将对应的UI文本全部列出来,虽然可以实现文字瞬间切换,无延迟和闪烁,且状态保留
但是,总感觉和第一种本地化的方法非常像
// MainWindow.xaml.cs
var newWindow = new MainWindow();
newWindow.Show();
Application.Current.MainWindow = newWindow;
this.Close();
如果实现UI实时刷新,那么还需要给每一个需要变化的控件命名,还是挺麻烦的,下面是需要更改的代码文件
MainWindow.xaml.cs
using LocalizeDemo.Resources;
using System.Globalization;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace LocalizeDemo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ShowMessageButton_Click(object sender, RoutedEventArgs e)
{
//MessageBox.Show(
// Application.Current.TryFindResource("MessageContent").ToString(),
// Application.Current.TryFindResource("MessageCaption").ToString(),
// MessageBoxButton.OK
//);
MessageBox.Show(Lang.MessageContent, Lang.MessageCaption, MessageBoxButton.OK);
}
// 切换语言 - 只需6行代码
private void ChangeLanguage_Click(object sender, RoutedEventArgs e)
{
var culture = ((Button)sender).Tag.ToString()!;
Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
// 实时刷新 UI 文本
WelcomeText.Text = Lang.WelcomeMessage;
EnBtn.Content = Lang.EnglishButtonContent;
ZhBtn.Content = Lang.ChineseButtonContent;
ShowBtn.Content = Lang.ShowMessageButtonContent;
LogoText.Text = Lang.LogoText;
Title = Lang.WindowTitle;
}
}
}
MainWindow.xaml
<Window x:Class="LocalizeDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:LocalizeDemo"
mc:Ignorable="d"
Title="{local:Localize WindowTitle}"
Height="450" Width="800"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Padding" Value="8,5"/>
<Setter Property="FontSize" Value="18"/>
</Style>
</Window.Resources>
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock x:Name="WelcomeText" Text="{local:Localize WelcomeMessage}" FontSize="32" />
<StackPanel Margin="0,16,0,0"
HorizontalAlignment="Center"
Orientation="Horizontal">
<Button x:Name="EnBtn" Content="{local:Localize EnglishButtonContent}" Tag="en" Click="ChangeLanguage_Click"/>
<Separator Width="10" Opacity="0" />
<Button x:Name="ZhBtn" Content="{local:Localize ChineseButtonContent}" Tag="zh-CN" Click="ChangeLanguage_Click"/>
</StackPanel>
<Button x:Name="ShowBtn" Margin="0,16,0,0"
HorizontalAlignment="Center"
Content="{local:Localize ShowMessageButtonContent}"
Click="ShowMessageButton_Click"/>
</StackPanel>
<TextBlock x:Name="LogoText" Margin="20"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Text="{local:Localize LogoText}"
FontSize="28"
Foreground="LightGray" />
</Grid>
</Window>
LocalizeExtension.cs
using System.Globalization;
using System.Windows.Markup;
namespace LocalizeDemo
{
/// <summary>
/// 本地化标记扩展 - 提供初始文本
/// 用法: {local:Localize WelcomeMessage}
/// </summary>
public class LocalizeExtension : MarkupExtension
{
// 资源键
private string _key;
// 接收资源键
public LocalizeExtension(string key)
{
_key = key;
}
// 通过资源管理器获取当前 UI 文化的字符串值
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Resources.Lang.ResourceManager.GetString(_key, Thread.CurrentThread.CurrentUICulture) ?? _key;
}
}
}
3.🍁地化方法三:NetGet顶级包 —— WPFLocalizeExtension(非常好用,但是只能用于WPF)
1.打开NetGet包管理器,搜索WPFLocalizeExtension,选择下载
2.在本地化方案二中第一套代码的基础上修改:
MainWindow.xaml.cs
using LocalizeDemo.Resources;
using System.Globalization;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using WPFLocalizeExtension.Engine;
namespace LocalizeDemo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ShowMessageButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Lang.MessageContent, Lang.MessageCaption, MessageBoxButton.OK);
}
// 切换语言
private void ChangeLanguage_Click(object sender, RoutedEventArgs e)
{
var culture = ((Button)sender).Tag.ToString();
var cultureInfo = new CultureInfo(culture!);
Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;
LocalizeDictionary.Instance.Culture = cultureInfo;
}
}
}
MainWindow.xaml
<Window x:Class="LocalizeDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:LocalizeDemo"
xmlns:lang="clr-namespace:LocalizeDemo.Resources"
xmlns:lex="http://wpflocalizeextension.codeplex.com"
lex:ResxLocalizationProvider.DefaultAssembly="LocalizeDemo"
lex:ResxLocalizationProvider.DefaultDictionary="Lang"
lex:LocalizeDictionary.DesignCulture="zh-CN"
Title="{lex:Loc WindowTitle}"
mc:Ignorable="d"
Height="450" Width="800"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Padding" Value="8,5"/>
<Setter Property="FontSize" Value="18"/>
</Style>
</Window.Resources>
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{lex:Loc WelcomeMessage}" FontSize="32" />
<StackPanel Margin="0,16,0,0"
HorizontalAlignment="Center"
Orientation="Horizontal">
<!--<Button Content="{lex:Loc EnglishButtonContent}" Tag="en" Click="ChangeLanguage_Click"/>
<Separator Width="10" Opacity="0" />
<Button Content="{lex:Loc ChineseButtonContent}" Tag="zh-CN" Click="ChangeLanguage_Click"/>-->
<!-- SetCultureCommand代替Click -->
<Button Content="{lex:Loc EnglishButtonContent}" Tag="en"
Command="{Binding Source={x:Static lex:LocalizeDictionary.Instance}, Path=SetCultureCommand}"
CommandParameter="en"/>
<Separator Width="10" Opacity="0" />
<Button Content="{lex:Loc ChineseButtonContent}" Tag="zh-CN"
Command="{Binding Source={x:Static lex:LocalizeDictionary.Instance}, Path=SetCultureCommand}"
CommandParameter="zh-CN"/>
</StackPanel>
<Button Margin="0,16,0,0"
HorizontalAlignment="Center"
Content="{lex:Loc ShowMessageButtonContent}"
Click="ShowMessageButton_Click"/>
</StackPanel>
<TextBlock Margin="20"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Text="{lex:Loc LogoText}"
FontSize="28"
Foreground="LightGray" />
</Grid>
</Window>
这里,我们所使用的NetGet包为我们提供了很多方便的功能,这里仅简单说一两个
1.这里给我们提供了一些特定的语法
lex:ResxLocalizationProvider.DefaultAssembly="LocalizeDemo"
lex:ResxLocalizationProvider.DefaultDictionary="Lang"
lex:LocalizeDictionary.DesignCulture="zh-CN"
Title="{lex:Loc WindowTitle}"
# 默认从 LocalizeDemo.dll(或者你项目里的 LocalizeDemo 项目)里去找资源文件(.resx)
# 如果你的资源在主程序里,也可以写成 DefaultAssembly="{x:Type local:App}" 形式
lex:ResxLocalizationProvider.DefaultAssembly="LocalizeDemo"
# 指定默认资源字典名,这里 Lang 就是字典名,省去每个控件都写完整路径
lex:ResxLocalizationProvider.DefaultDictionary="Lang"
# 初始显示的语言
# 设置了 zh-CN,在 Visual Studio Designer 里就能看到中文界面
lex:LocalizeDictionary.DesignCulture="zh-CN"
# lex:Loc 是 WPFLocalizeExtension 的标记扩展(MarkupExtension),用于从资源文件中取值
# WindowTitle 是资源键(Key),对应你 Lang.resx 中的项
Title="{lex:Loc WindowTitle}"
# 如果续写上面的代码,我们就需要写:
Title="{x:Static lang:Lang.WindowTitle}"
<StackPanel Margin="0,16,0,0"
HorizontalAlignment="Center"
Orientation="Horizontal">
<!--<Button Content="{lex:Loc EnglishButtonContent}" Tag="en" Click="ChangeLanguage_Click"/>
<Separator Width="10" Opacity="0" />
<Button Content="{lex:Loc ChineseButtonContent}" Tag="zh-CN" Click="ChangeLanguage_Click"/>-->
<!-- SetCultureCommand代替Click -->
<Button Content="{lex:Loc EnglishButtonContent}" Tag="en"
Command="{Binding Source={x:Static lex:LocalizeDictionary.Instance}, Path=SetCultureCommand}"
CommandParameter="en"/>
<Separator Width="10" Opacity="0" />
<Button Content="{lex:Loc ChineseButtonContent}" Tag="zh-CN"
Command="{Binding Source={x:Static lex:LocalizeDictionary.Instance}, Path=SetCultureCommand}"
CommandParameter="zh-CN"/>
</StackPanel>
4.🍁地化方法三:NetGet顶级包 —— Antelcat.I18N.WPF(国人出品,必属精品,I18N有多套框架版本)
I18N包作者B站主页:羚宴的个人空间-羚宴个人主页-哔哩哔哩视频
项目Github地址:GitHub - Antelcat/I18N: Reactive language support for WPF/Avalonia applications when using .resx file. · GitHub
虽然看起来非常好用,
但是,这个up的视频中会的代码会出现WPFLocalizeExtension和Antelcat.I18N.WPF代码冲突,
然后无法使用......我太会解决NetGet包的冲突,
再然后网上对于这个的教程视频非常少,然后我去GitHub上面找了源代码和示例代码
发现Github上的有版本限制:C# >= 9.0,嗯.......如果以后会用到再查查资料吧
随笔参考:
1.WPF 程序集资源 - Min.Xiaoshuang - 博客园
2.17. 程序集_03_资源和附属程序集 - 知乎
3.创建资源文件 - .NET | Microsoft Learn
4.C#(99):资源与本地化 System.Resources - springsnow - 博客园
5.56.第7章_自定义本地化_哔哩哔哩_bilibili
6.57.第7章_微软推荐的本地化_哔哩哔哩_bilibili
7.如何在WPF中实现支持运行时切换语言的本地化效果_哔哩哔哩_bilibili
