从零到多页复用:我的WPF MVVM国际化实践

[复制链接]
发表于 2025-10-19 17:34:57 | 显示全部楼层 |阅读模式



作为一名WPF开辟者,我迩来在一个设置工具项目中遇到了国际化需求。早先,我只是想让按钮文本支持英文和中文切换,但随着页面增多,需求复杂化,我渐渐探索出一套从简朴到专业化的实现方案。这篇文章记录了我的探索过程,从最底子的资源文件开始,到多页面复用的优化,渴望能给有雷同需求的开辟者一些开导。
第一步:底子实现,资源文件入门

我的项目是一个基于WPF和MVVM的设置工具,界面上有“Save”和“Refresh”两个按钮,须要支持英文和中文切换。WPF的国际化通常从资源文件(.resx)入手,于是我先实行了最简朴的方法。
在项目中,我创建了一个Resources文件夹,添加了两个资源文件:


  • Resources.resx(默认英文):

    • save: Save
    • refresh: Refresh

  • Resources.zh-CN.resx(中文):

    • save: 生存
    • refresh: 革新

在XAML中,我实行直接绑定到资源:
  1. <Button Content="{Binding Source={x:Static local:Resources.save}}" />
复制代码
但很快发现,这种方式在运行时切换语言时不会更新UI,由于静态绑定无法相应动态厘革。于是,我转向代码隐蔽文件,在UserControl中界说属性:
  1. public partial class PageTemplate : UserControl
  2. {
  3.     public string Save => Resources.ResourceManager.GetString("save");
  4.     public string Refresh => Resources.ResourceManager.GetString("refresh");
  5.     public PageTemplate()
  6.     {
  7.         InitializeComponent();
  8.         DataContext = this;
  9.     }
  10. }
复制代码
XAML改为:
  1. <Button Content="{Binding Save}" />
复制代码
这时间,按钮表现了英文,但点击“中文”按钮后,文本没变。我意识到,语言切换须要更新CultureInfo,于是引入了一个单例类LanguageManager:
  1. public class LanguageManager
  2. {
  3.     private static readonly Lazy<LanguageManager> _instance = new Lazy<LanguageManager>(() => new LanguageManager());
  4.     public static LanguageManager Instance => _instance.Value;
  5.     private CultureInfo _currentCulture = new CultureInfo("en-US");
  6.     public CultureInfo CurrentCulture
  7.     {
  8.         get => _currentCulture;
  9.         set
  10.         {
  11.             _currentCulture = value;
  12.             Thread.CurrentThread.CurrentUICulture = value;
  13.         }
  14.     }
  15.     public string GetString(string key) => Resources.ResourceManager.GetString(key, _currentCulture) ?? $"[{key}]";
  16. }
复制代码
在PageTemplate中使用:
  1. private readonly LanguageManager _languageManager = LanguageManager.Instance;
  2. public string Save => _languageManager.GetString("save");
复制代码
加上切换下令:
  1. <Button CommandParameter="zh-CN" Command="{Binding SwitchLanguageCommand}" Content="中文" />
复制代码
  1. public ICommand SwitchLanguageCommand => new RelayCommand<string>(lang =>
  2. {
  3.     _languageManager.CurrentCulture = new CultureInfo(lang);
  4. });
复制代码
然而,切换后UI还是没更新。我调试发现,固然CultureInfo变了,但绑定没有革新。加上INotifyPropertyChanged后标题办理:
  1. public partial class PageTemplate : UserControl, INotifyPropertyChanged
  2. {
  3.     private readonly LanguageManager _languageManager = LanguageManager.Instance;
  4.     public string Save => _languageManager.GetString("save");
  5.     public PageTemplate()
  6.     {
  7.         InitializeComponent();
  8.         DataContext = this;
  9.         _languageManager.PropertyChanged += (s, e) => OnPropertyChanged(null);
  10.     }
  11.     public event PropertyChangedEventHandler PropertyChanged;
  12.     protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  13. }
复制代码
终于,切换语言时按钮文本正常更新了!
第二步:依赖属性,提升WPF体验

固然底子功能实现了,但这种只读属性方式让我以为不敷“WPF”。在WPF中,依赖属性更恰当绑定场景。于是我改用依赖属性:
  1. public static readonly DependencyProperty SaveProperty = DependencyProperty.Register(
  2.     nameof(Save), typeof(string), typeof(PageTemplate), new PropertyMetadata(string.Empty));
  3. public string Save
  4. {
  5.     get => (string)GetValue(SaveProperty);
  6.     set => SetValue(SaveProperty, value);
  7. }
  8. public PageTemplate()
  9. {
  10.     InitializeComponent();
  11.     DataContext = this;
  12.     UpdateLocalizedStrings();
  13.     _languageManager.PropertyChanged += (s, e) => UpdateLocalizedStrings();
  14. }
  15. private void UpdateLocalizedStrings()
  16. {
  17.     Save = _languageManager.GetString("save");
  18.     Refresh = _languageManager.GetString("refresh");
  19. }
复制代码
如许,绑定更符合WPF的风俗,而且UI更新更可靠。下一步,我把语言切换按钮改成了下拉框:
  1. <ComboBox ItemsSource="{Binding Languages}"
  2.           DisplayMemberPath="DisplayName"
  3.           SelectedValuePath="CultureName"
  4.           SelectedValue="{Binding SelectedLanguage, Mode=TwoWay}"/>
复制代码
  1. public List<LanguageOption> Languages { get; } = new List<LanguageOption>
  2. {
  3.     new LanguageOption("English", "en-US"),
  4.     new LanguageOption("中文", "zh-CN")
  5. };
  6. public static readonly DependencyProperty SelectedLanguageProperty = DependencyProperty.Register(
  7.     nameof(SelectedLanguage), typeof(string), typeof(PageTemplate),
  8.     new PropertyMetadata("en-US", OnSelectedLanguageChanged));
  9. public string SelectedLanguage
  10. {
  11.     get => (string)GetValue(SelectedLanguageProperty);
  12.     set => SetValue(SelectedLanguageProperty, value);
  13. }
  14. private static void OnSelectedLanguageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  15. {
  16.     var page = (PageTemplate)d;
  17.     page._languageManager.CurrentCulture = new CultureInfo((string)e.NewValue);
  18. }
复制代码
这让界面更友好,用户体验也提升了。
第三步:多页面复用,淘汰重复代码

项目发展到有多个页面时,我发现每个页面都重复界说Save、Refresh和语言切换逻辑,太繁琐了。我决定把国际化会合化,先创建了一个基类:
  1. public class BaseViewModel : INotifyPropertyChanged
  2. {
  3.     protected readonly LanguageService _languageService = LanguageService.Instance;
  4.     public string Save => _languageService.GetString("save");
  5.     public string Refresh => _languageService.GetString("refresh");
  6.     public event PropertyChangedEventHandler PropertyChanged;
  7.     protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  8. }
复制代码
LanguageService继续了语言管理:
  1. public class LanguageService : INotifyPropertyChanged
  2. {
  3.     private static readonly Lazy<LanguageService> _instance = new Lazy<LanguageService>(() => new LanguageService());
  4.     public static LanguageService Instance => _instance.Value;
  5.     private CultureInfo _currentCulture = new CultureInfo("en-US");
  6.     public CultureInfo CurrentCulture
  7.     {
  8.         get => _currentCulture;
  9.         set
  10.         {
  11.             _currentCulture = value;
  12.             Thread.CurrentThread.CurrentUICulture = value;
  13.             OnPropertyChanged(null);
  14.         }
  15.     }
  16.     public string GetString(string key) => Resources.ResourceManager.GetString(key, _currentCulture) ?? $"[{key}]";
  17.     public List<LanguageOption> Languages { get; } = new List<LanguageOption>
  18.     {
  19.         new LanguageOption("English", "en-US"),
  20.         new LanguageOption("中文", "zh-CN")
  21.     };
  22.     public string SelectedLanguage
  23.     {
  24.         get => _currentCulture.Name;
  25.         set => CurrentCulture = new CultureInfo(value);
  26.     }
  27.     public event PropertyChangedEventHandler PropertyChanged;
  28.     protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  29. }
复制代码
页面只需继续BaseViewModel:
  1. public class PageTemplateViewModel : BaseViewModel { }
  2. public partial class PageTemplate : UserControl
  3. {
  4.     public PageTemplate()
  5.     {
  6.         InitializeComponent();
  7.         DataContext = new PageTemplateViewModel();
  8.     }
  9. }
复制代码
XAML绑定到全局服务:
  1. <ComboBox ItemsSource="{Binding Languages, Source={x:Static services:LanguageService.Instance}}"
  2.           SelectedValue="{Binding SelectedLanguage, Source={x:Static services:LanguageService.Instance}, Mode=TwoWay}"/>
复制代码
第四步:动态化,应对更多字符串

页面越来越多,字符串也从save、refresh扩展到title、user等十几个。我不想在BaseViewModel中为每个字符串写属性,于是实行了动态方案:
  1. public class BaseViewModel : INotifyPropertyChanged
  2. {
  3.     protected readonly LanguageService _languageService = LanguageService.Instance;
  4.     public dynamic Strings => new LocalizedStrings(_languageService);
  5.     public BaseViewModel()
  6.     {
  7.         _languageService.PropertyChanged += (s, e) => OnPropertyChanged(nameof(Strings));
  8.     }
  9.     public event PropertyChangedEventHandler PropertyChanged;
  10.     protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  11. }
  12. public class LocalizedStrings : DynamicObject
  13. {
  14.     private readonly LanguageService _languageService;
  15.     public LocalizedStrings(LanguageService languageService)
  16.     {
  17.         _languageService = languageService;
  18.     }
  19.     public override bool TryGetMember(GetMemberBinder binder, out object result)
  20.     {
  21.         result = _languageService.GetString(binder.Name.ToLower());
  22.         return true;
  23.     }
  24. }
复制代码
XAML改为:
  1. <Button Content="{Binding Strings.save}" />
  2. <TextBlock Text="{Binding Strings.title}" />
复制代码
现在,无论有多少字符串,我只需在资源文件里添加键值对,代码完全不消改动。这种方式让我从繁琐的属性界说中解放出来。
总结与反思

从最初的简朴资源文件,到依赖属性,再到多页面复用,末了用动态对象优化,我的国际化之旅走了不少弯路,但每一步都让我更明白WPF和MVVM的精华:


  • 起步简朴:资源文件和根本绑定能快速实现单页面国际化。
  • 提升体验:依赖属性和下拉框让切换更自然。
  • 复用为王:会合化管理克制重复劳动。
  • 动态扩展:用动态对象应对将来需求。
如果你的项目也有国际化需求,不妨从底子开始,根据规模徐徐优化。你遇到过哪些国际化困难?接待留言分享!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表