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

文章目录
- 第一步:基础实现,资源文件入门
- 第二步:依赖属性,提升WPF体验
- 第三步:多页面复用,减少重复代码
- 第四步:动态化,应对更多字符串
- 总结与反思
作为一名WPF开发者,我最近在一个配置工具项目中遇到了国际化需求。起初,我只是想让按钮文本支持英文和中文切换,但随着页面增多,需求复杂化,我逐渐摸索出一套从简单到专业化的实现方案。这篇文章记录了我的探索过程,从最基础的资源文件开始,到多页面复用的优化,希望能给有类似需求的开发者一些启发。
第一步:基础实现,资源文件入门
我的项目是一个基于WPF和MVVM的配置工具,界面上有“Save”和“Refresh”两个按钮,需要支持英文和中文切换。WPF的国际化通常从资源文件(.resx)入手,于是我先尝试了最简单的方法。
在项目中,我创建了一个Resources文件夹,添加了两个资源文件:
- Resources.resx(默认英文):
- save: Save
- refresh: Refresh
- Resources.zh-CN.resx(中文):
- save: 保存
- refresh: 刷新
在XAML中,我尝试直接绑定到资源:
<Button Content="{Binding Source={x:Static local:Resources.save}}" />
但很快发现,这种方式在运行时切换语言时不会更新UI,因为静态绑定无法响应动态变化。于是,我转向代码隐藏文件,在UserControl中定义属性:
public partial class PageTemplate : UserControl
{public string Save => Resources.ResourceManager.GetString("save");public string Refresh => Resources.ResourceManager.GetString("refresh");public PageTemplate(){InitializeComponent();DataContext = this;}
}
XAML改为:
<Button Content="{Binding Save}" />
这时候,按钮显示了英文,但点击“中文”按钮后,文本没变。我意识到,语言切换需要更新CultureInfo,于是引入了一个单例类LanguageManager:
public class LanguageManager
{private static readonly Lazy<LanguageManager> _instance = new Lazy<LanguageManager>(() => new LanguageManager());public static LanguageManager Instance => _instance.Value;private CultureInfo _currentCulture = new CultureInfo("en-US");public CultureInfo CurrentCulture{get => _currentCulture;set{_currentCulture = value;Thread.CurrentThread.CurrentUICulture = value;}}public string GetString(string key) => Resources.ResourceManager.GetString(key, _currentCulture) ?? $"[{key}]";
}
在PageTemplate中使用:
private readonly LanguageManager _languageManager = LanguageManager.Instance;
public string Save => _languageManager.GetString("save");
加上切换命令:
<Button CommandParameter="zh-CN" Command="{Binding SwitchLanguageCommand}" Content="中文" />
public ICommand SwitchLanguageCommand => new RelayCommand<string>(lang =>
{_languageManager.CurrentCulture = new CultureInfo(lang);
});
然而,切换后UI还是没更新。我调试发现,虽然CultureInfo变了,但绑定没有刷新。加上INotifyPropertyChanged后问题解决:
public partial class PageTemplate : UserControl, INotifyPropertyChanged
{private readonly LanguageManager _languageManager = LanguageManager.Instance;public string Save => _languageManager.GetString("save");public PageTemplate(){InitializeComponent();DataContext = this;_languageManager.PropertyChanged += (s, e) => OnPropertyChanged(null);}public event PropertyChangedEventHandler PropertyChanged;protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
终于,切换语言时按钮文本正常更新了!
第二步:依赖属性,提升WPF体验
虽然基础功能实现了,但这种只读属性方式让我觉得不够“WPF”。在WPF中,依赖属性更适合绑定场景。于是我改用依赖属性:
public static readonly DependencyProperty SaveProperty = DependencyProperty.Register(nameof(Save), typeof(string), typeof(PageTemplate), new PropertyMetadata(string.Empty));public string Save
{get => (string)GetValue(SaveProperty);set => SetValue(SaveProperty, value);
}public PageTemplate()
{InitializeComponent();DataContext = this;UpdateLocalizedStrings();_languageManager.PropertyChanged += (s, e) => UpdateLocalizedStrings();
}private void UpdateLocalizedStrings()
{Save = _languageManager.GetString("save");Refresh = _languageManager.GetString("refresh");
}
这样,绑定更符合WPF的习惯,而且UI更新更可靠。下一步,我把语言切换按钮改成了下拉框:
<ComboBox ItemsSource="{Binding Languages}"DisplayMemberPath="DisplayName"SelectedValuePath="CultureName"SelectedValue="{Binding SelectedLanguage, Mode=TwoWay}"/>
public List<LanguageOption> Languages { get; } = new List<LanguageOption>
{new LanguageOption("English", "en-US"),new LanguageOption("中文", "zh-CN")
};public static readonly DependencyProperty SelectedLanguageProperty = DependencyProperty.Register(nameof(SelectedLanguage), typeof(string), typeof(PageTemplate),new PropertyMetadata("en-US", OnSelectedLanguageChanged));public string SelectedLanguage
{get => (string)GetValue(SelectedLanguageProperty);set => SetValue(SelectedLanguageProperty, value);
}private static void OnSelectedLanguageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{var page = (PageTemplate)d;page._languageManager.CurrentCulture = new CultureInfo((string)e.NewValue);
}
这让界面更友好,用户体验也提升了。
第三步:多页面复用,减少重复代码
项目发展到有多个页面时,我发现每个页面都重复定义Save、Refresh和语言切换逻辑,太繁琐了。我决定把国际化集中化,先创建了一个基类:
public class BaseViewModel : INotifyPropertyChanged
{protected readonly LanguageService _languageService = LanguageService.Instance;public string Save => _languageService.GetString("save");public string Refresh => _languageService.GetString("refresh");public event PropertyChangedEventHandler PropertyChanged;protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
LanguageService接管了语言管理:
public class LanguageService : INotifyPropertyChanged
{private static readonly Lazy<LanguageService> _instance = new Lazy<LanguageService>(() => new LanguageService());public static LanguageService Instance => _instance.Value;private CultureInfo _currentCulture = new CultureInfo("en-US");public CultureInfo CurrentCulture{get => _currentCulture;set{_currentCulture = value;Thread.CurrentThread.CurrentUICulture = value;OnPropertyChanged(null);}}public string GetString(string key) => Resources.ResourceManager.GetString(key, _currentCulture) ?? $"[{key}]";public List<LanguageOption> Languages { get; } = new List<LanguageOption>{new LanguageOption("English", "en-US"),new LanguageOption("中文", "zh-CN")};public string SelectedLanguage{get => _currentCulture.Name;set => CurrentCulture = new CultureInfo(value);}public event PropertyChangedEventHandler PropertyChanged;protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
页面只需继承BaseViewModel:
public class PageTemplateViewModel : BaseViewModel { }
public partial class PageTemplate : UserControl
{public PageTemplate(){InitializeComponent();DataContext = new PageTemplateViewModel();}
}
XAML绑定到全局服务:
<ComboBox ItemsSource="{Binding Languages, Source={x:Static services:LanguageService.Instance}}"SelectedValue="{Binding SelectedLanguage, Source={x:Static services:LanguageService.Instance}, Mode=TwoWay}"/>
第四步:动态化,应对更多字符串
页面越来越多,字符串也从save、refresh扩展到title、user等十几个。我不想在BaseViewModel中为每个字符串写属性,于是尝试了动态方案:
public class BaseViewModel : INotifyPropertyChanged
{protected readonly LanguageService _languageService = LanguageService.Instance;public dynamic Strings => new LocalizedStrings(_languageService);public BaseViewModel(){_languageService.PropertyChanged += (s, e) => OnPropertyChanged(nameof(Strings));}public event PropertyChangedEventHandler PropertyChanged;protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}public class LocalizedStrings : DynamicObject
{private readonly LanguageService _languageService;public LocalizedStrings(LanguageService languageService){_languageService = languageService;}public override bool TryGetMember(GetMemberBinder binder, out object result){result = _languageService.GetString(binder.Name.ToLower());return true;}
}
XAML改为:
<Button Content="{Binding Strings.save}" />
<TextBlock Text="{Binding Strings.title}" />
现在,无论有多少字符串,我只需在资源文件里添加键值对,代码完全不用改动。这种方式让我从繁琐的属性定义中解放出来。
总结与反思
从最初的简单资源文件,到依赖属性,再到多页面复用,最后用动态对象优化,我的国际化之旅走了不少弯路,但每一步都让我更理解WPF和MVVM的精髓:
- 起步简单:资源文件和基本绑定能快速实现单页面国际化。
- 提升体验:依赖属性和下拉框让切换更自然。
- 复用为王:集中化管理避免重复劳动。
- 动态扩展:用动态对象应对未来需求。
如果你的项目也有国际化需求,不妨从基础开始,根据规模逐步优化。你遇到过哪些国际化难题?欢迎留言分享!
相关文章:
从零到多页复用:我的WPF MVVM国际化实践
文章目录 第一步:基础实现,资源文件入门第二步:依赖属性,提升WPF体验第三步:多页面复用,减少重复代码第四步:动态化,应对更多字符串总结与反思 作为一名WPF开发者,我最近…...
uniapp 常用 UI 组件库
1. uView UI 特点: 组件丰富:提供覆盖按钮、表单、图标、表格、导航、图表等场景的内置组件。跨平台支持:兼容 App、H5、小程序等多端。高度可定制:支持主题定制,组件样式灵活。实用工具类:提供时间、数组操…...
C++编写Redis客户端
目录 安装redis-plus-plus库 编辑 编译Credis客户端 redis的通用命令使用 get/set exists del keys expire /ttl type string类型核心操作 set和get set带有超时时间 set带有NX string带有XX mset mget getrange和setrange incr和decr list类型核心操作…...
基于大模型预测的急性横贯性脊髓炎诊疗方案研究报告
目录 一、引言 1.1 研究背景与意义 1.2 研究目的与方法 1.3 国内外研究现状 二、急性横贯性脊髓炎概述 2.1 疾病定义与分类 2.2 病因与发病机制 2.3 临床表现与诊断标准 三、大模型在急性横贯性脊髓炎预测中的应用 3.1 大模型介绍与原理 3.2 数据收集与预处理 3.3 …...
nature genetics | SCENT:单细胞多模态数据揭示组织特异性增强子基因图谱,并可识别致病等位基因
–https://doi.org/10.1038/s41588-024-01682-1 Tissue-specific enhancer–gene maps from multimodal single-cell data identify causal disease alleles 研究团队和单位 Alkes L. Price–Broad Institute of MIT and Harvard Soumya Raychaudhuri–Harvard Medical S…...
【C语言】指针篇
目录 C 语言指针概述指针的声明和初始化声明指针初始化指针指针的操作解引用操作指针算术运算指针的用途动态内存分配作为函数参数指针与数组数组名作为指针通过指针访问数组元素指针算术和数组数组作为函数参数指针数组和数组指针指针数组数组指针函数指针函数指针的定义和声明…...
DeepSeek:如何通过自然语言生成HTML文件与原型图?
在当今快节奏的开发与设计环境中,快速生成HTML文件或原型图是每个开发者与设计师的迫切需求。虽然DeepSeek无法直接生成图片,但它却能够通过自然语言生成流程图、原型图以及交互式页面,甚至可以直接输出HTML代码。本文将详细介绍如何与DeepSe…...
数据结构与算法(两两交换链表中的结点)
原题 24. 两两交换链表中的节点 - 力扣(LeetCode) 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。 示…...
语言模型作为零样本规划者:提取可执行知识以供具身代理使用
【摘要】 本文研究了预训练的语言模型(LLMs)能否被用来执行在交互式环境中的任务。作者发现,尽管LLMs在生成高阶任务的行动计划时可能无法做到完全精确定义,但通过适当提示,大型预训练语言模型可以分解高阶任务到中阶…...
Baklib智能推荐引擎驱动内容中台升级
智能推荐引擎技术架构 现代智能推荐系统的技术架构以语义分析算法为核心,通过自然语言处理技术解构内容特征,结合动态知识图谱实现信息实体关系的智能映射。该系统采用分层设计架构,基础层依托深度学习模型处理海量非结构化数据,…...
显示器长时间黑屏
现象 电脑启动后,进入登录界面前会随机黑屏,有时候十几秒,有时候几分钟 进入桌面后,长时间不操作电脑黑屏,移动鼠标,点击键盘后尝试点亮屏幕,也会消耗较长时间 尝试 重装系统,或者重新安装显卡,都能够恢复,但过段时间以后又出现黑屏情况 集成显卡,独立显卡都出现过 操作系统…...
顺序表与链表·续
引言 本文承接上文(顺序表与链表-CSDN博客),开始对链表的要点提炼。前文提到顺序表适合需要频繁随机访问且数据量固定的场景,而链表适合需要频繁插入和删除且数据量动态变化的场景。链表的引入弥补了顺序表在动态性和操作效率上的…...
nvidia驱动升级-ubuntu 1804
升级 1.从官网下载*.run驱动文件 2.卸载原始驱动 sudo /usr/bin/nvidia-uninstall sudo apt-get --purge remove nvidia-\* # 可能不需要加-\ sudo apt-get purge nvidia-\* # 可能不需要加-\ sudo apt-get purge libnvidia-\* # 可能不需要…...
【Linux】——初识操作系统
文章目录 冯-诺依曼体系结构操作系统shell 冯-诺依曼体系结构 我们现在所使用的计算机就是冯-诺依曼体系结构。 存储器就是内存。 由下图可知,寄存器最快,为啥不用寄存器呢? 因为越快价格就最贵,冯诺依曼体系结构的诞生…...
本地化deepseek
小白都能拥有自己的人工智能 1、我本地环境 系统:win10 cpu:i7(i7-12700),差不多就行 硬盘:500G+2T,可以不用这么大 显卡:七彩虹2060 12G ,够用了 我的配置最高也只能配上8B了, R1模型版本CPUGPU内存存储8B Intel Core i7/AMD Ryzen 7 及以上 无强制要求,有 4…...
利用可变参数模板,可打印任意参数和参数值。(C++很好的调式函数)
很酷的应用: (1) 如何获取可变参数名 代码例子: #define _test(...) (test_t(#__VA_ARGS__, __VA_ARGS__))template<typename... Args> void test_t(const char* names, Args... args) {std::cout << names <<…...
Yashan DB 体系结构
一、体系结构概况 1.1 线程管理 YashanDB采用多线程架构,线程分为两类: • 工作线程(Worker Threads):每个客户端连接到数据库实例时,会创建一个工作线程。工作线程负责处理客户端的SQL请求,执…...
测试工程师Deepseek实战之如何反向PUA它
问: 你是一名资深测试开发工程师 帮我设计一个提效工具,具有以下功能: 1.页面使用PYQT5设计,用两个输入控件,最好是日期类型的控件,第一个日期控件作为开始日期,第二个日期控件作为结束日期;前后…...
Windows系统中在VSCode上配置CUDA环境
前置步骤 安装符合GPU型号的CUDA Toolkit 配置好 nvcc 环境变量 安装 Visual Studio 参考https://blog.csdn.net/Cony_14/article/details/137510909 VSCode 安装插件 Nsight Visual Studio Code Editionvscode-cudacpp 安装 cmake 并配置好环境变量 注:Windows 端…...
React Native 0.76 升级后 APK 体积增大的原因及优化方案
在将 React Native 从 0.71 升级到 0.76 后,打包体积从 40 多 MB 增加到了 80 MB。经过一系列排查和优化,最终找到了解决方案,并将优化过程整理如下。 1. React Native 0.76 体积增大的可能原因 (1) 新架构默认启用 React Native 0.76 默认启用了 New Architecture(新架…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
