在 WPF .net core 5 中运行时更改应用程序文化时如何更新属性绑定
Posted
技术标签:
【中文标题】在 WPF .net core 5 中运行时更改应用程序文化时如何更新属性绑定【英文标题】:How to update property binding when changing the culture of the application at runtime in WPF .net core 5 【发布时间】:2021-11-18 14:24:50 【问题描述】:我正在尝试让我的 WPF 应用程序支持两种语言。但是当我尝试将 DataTime
DP 绑定到 UserControl
内的 TextBlock
并在运行时更改当前文化时遇到了问题。
DateTime
格式不会更改为更新后的文化,而只会在重新启动应用时更改,然后保持静止。
我的代码:
App.xaml.cs
public App()
CultureInfo CultureInformation = new CultureInfo("en-UK");
CultureInformation.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";
CultureInformation.DateTimeFormat.LongDatePattern = "ddd, dd/MM/yyyy";
CultureInfo.DefaultThreadCurrentCulture = CultureInformation;
CultureInfo.DefaultThreadCurrentUICulture = CultureInformation;
MainWindow.xaml.cs
private void UpdateLanguage(string Language)
LanguageComboBox.SelectedValue = Properties.Settings.Default.Language = Language;
Properties.Settings.Default.Save();
//
ResourceDictionary Dictionary = new();
Dictionary.Source = new Uri(@$"..\Languages\Language.xaml", UriKind.Relative);
Resources.MergedDictionaries.Clear();
Resources.MergedDictionaries.Add(Dictionary);
//
if (Language == "العربية")
CultureInfo CultureInformation = CultureInfo.CreateSpecificCulture("ar-EG");
CultureInformation.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";
CultureInformation.DateTimeFormat.LongDatePattern = "ddd, dd/MM/yyyy";
Thread.CurrentThread.CurrentCulture = CultureInformation;
Thread.CurrentThread.CurrentUICulture = CultureInformation;
else if (Language == "English")
CultureInfo CultureInformation = CultureInfo.CreateSpecificCulture("en-UK");
CultureInformation.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";
CultureInformation.DateTimeFormat.LongDatePattern = "ddd, dd/MM/yyyy";
Thread.CurrentThread.CurrentCulture = CultureInformation;
Thread.CurrentThread.CurrentUICulture = CultureInformation;
转化文化
public class CultureAwareBinding : Binding
public CultureAwareBinding()
ConverterCulture = CultureInfo.CurrentCulture;
UserControl.xaml
<TextBlock Grid.Row="5" FontSize="14" FontFamily="StaticResource Segoe Semibold" Foreground="DynamicResource BackgroundBrush">
<TextBlock Text="local:CultureAwareBinding Path=StartTime, StringFormat=0:hh:mm tt"/>
<TextBlock Text="" FontSize="12" FontFamily="StaticResource Segoe Icons"/>
<TextBlock Text="local:CultureAwareBinding Path=EndTime, StringFormat=0:hh:mm tt"/>
</TextBlock>
提前致谢。
【问题讨论】:
【参考方案1】:很遗憾,没有现成的简单解决方案可以适用于所有情况。 该决定取决于您认为对其实施有效的内容以及您如何实施绑定。
-
假设您拥有到 Window 数据上下文的所有绑定。
而实际上你需要在所有 Windows 上调用 DataContext 视图的渲染。
然后你就可以在 Code Behind App 中使用这个方法了:
public static async void RerenderAllDataContext()
var windows = Current.Windows.OfType<Window>().ToList();
var dataContextes = windows.ToDictionary(w => w, w => w.DataContext);
var dispatcher = Current.Dispatcher;
await dispatcher.InvokeAsync(() => windows.ForEach(w => w.DataContext = null));
await dispatcher.InvokeAsync(() => windows.ForEach(w => w.DataContext = dataContextes[w]));
但是这种方法有它的缺点:它重绘所有窗口,UI元素的状态被重置(例如,光标在TextBox或SelectedItem中的位置),可能有没有绑定到Data Context等。
-
如果您需要考虑文化的绑定并不多,或者它们不仅是针对数据上下文创建的,那么剩下的就是调用所有此类绑定的重绘。
通过使用 MiltiBinding 而不是 Binding,只需进行少量更改即可完成此操作,其中一个绑定(通常是最后一个)将使用当前区域性的属性。
对于这样的MiltiBinding,转换器返回接收到的值数组的第一个值(如果有两个)。
为简单起见,您可以从 MiltiBinding 派生一个类,以使其应用程序尽可能接近常规 Binding。
以另一种方式补充答案:
-
您可以使用 FrameworkElement.Language 属性来设置区域性。
这是一个依赖属性,因此您可以绑定它。
属性值由子级继承(类似于 DataContext)。
如果将其设置为 Window,则其中的所有元素也将采用相同的值。
或者,您可以将其设置为某个特定元素。
可以声明其他类型以减少 XAML 代码。 示例。 全局设置文化的静态类:
using System;
using System.Windows.Markup;
namespace Wpf.Data
public static class LanguageAware
public static XmlLanguage CurrentLanguage get; private set; = XmlLanguage.Empty;
public static event EventHandler CurrentLanguageChanged;
public static void SetCurrentLanguage(XmlLanguage currentLanguage)
if (currentLanguage == null)
currentLanguage = XmlLanguage.Empty;
if (!Equals(CurrentLanguage, currentLanguage))
CurrentLanguage = currentLanguage;
CurrentLanguageChanged?.Invoke(null, EventArgs.Empty);
其使用示例:
<Window.Resources>
<sys:DateTime x:Key="date">12.31.2021 15:47</sys:DateTime>
</Window.Resources>
<UniformGrid Columns="1">
<TextBlock Text="Binding Path=(wpfdata:LanguageAware.CurrentLanguage)" />
<TextBox Text="Binding Mode=OneWay, Source=StaticResource date, StringFormat=\0:F\"
Language="Binding Path=(wpfdata:LanguageAware.CurrentLanguage)"/>
<TextBox Text="Binding Mode=OneWay, Source=StaticResource date, StringFormat=\0:F\"/>
<Button Content="Russian" Click="OnCultureClick" CommandParameter="RU" Margin="10"/>
<Button Content="English-USA" Click="OnCultureClick" CommandParameter="En-Us" Margin="10"/>
</UniformGrid>
<x:Code><![CDATA[
private void OnCultureClick(object sender, RoutedEventArgs e)
string lang = ((Button)sender).CommandParameter as string;
if (lang == null)
LanguageAware.SetCurrentLanguage(null);
else
LanguageAware.SetCurrentLanguage(XmlLanguage.GetLanguage(lang));
]]>
</x:Code>
用命令替换 XAML 中的点击器的标记扩展:
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Threading;
namespace Wpf.Data
[MarkupExtensionReturnType(typeof(ICommand))]
public class LanguageCommandExtension : MarkupExtension
private static readonly LanguageCommand command = new LanguageCommand();
public void RaiseCanExecuteChanged() => command.RaiseCanExecuteChanged();
public override object ProvideValue(IServiceProvider serviceProvider)
return command;
private class LanguageCommand : ICommand
private readonly EventHandler requerySuggested;
/// <inheritdoc cref="ICommand.CanExecuteChanged"/>
public event EventHandler CanExecuteChanged;
private static readonly Dispatcher dispatcher = Application.Current.Dispatcher;
/// <summary> The method that raises the event <see cref="CanExecuteChanged"/>.</summary>
public void RaiseCanExecuteChanged()
if (dispatcher.CheckAccess())
invalidate();
else
_ = dispatcher.BeginInvoke(invalidate);
private readonly Action invalidate;
public LanguageCommand()
invalidate = () => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
requerySuggested = (o, e) => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
CommandManager.RequerySuggested += requerySuggested;
/// <inheritdoc cref="ICommand.CanExecute(object)"/>
public bool CanExecute(object parameter)
if (parameter == null || parameter is XmlLanguage)
return true;
try
if (parameter is string str)
return XmlLanguage.GetLanguage(str) != null;
if (parameter is CultureInfo culture)
str = culture.Name;
return XmlLanguage.GetLanguage(str) != null;
catch (Exception)
return false;
/// <inheritdoc cref="ICommand.Execute(object)"/>
public void Execute(object parameter)
if (parameter is XmlLanguage language)
else if (parameter == null)
language = null;
else
try
if (parameter is string str)
language = XmlLanguage.GetLanguage(str);
else if (parameter is CultureInfo culture)
str = culture.Name;
language = XmlLanguage.GetLanguage(str);
else
throw new InvalidCastException(nameof(parameter));
catch (Exception)
throw new ArgumentException(nameof(parameter));
LanguageAware.SetCurrentLanguage(language);
设置面板语言的使用示例:
<Window.Resources>
<sys:DateTime x:Key="date">12.31.2021 15:47</sys:DateTime>
</Window.Resources>
<UniformGrid Columns="1"
Language="Binding Path=(wpfdata:LanguageAware.CurrentLanguage)">
<TextBlock Text="Binding Path=(wpfdata:LanguageAware.CurrentLanguage)" />
<TextBox Text="Binding Mode=OneWay, Source=StaticResource date, StringFormat=\0:F\"/>
<TextBox Text="Binding Mode=OneWay, Source=StaticResource date, StringFormat=\0:F\"/>
<Button Content="Russian" Command="wpfdata:LanguageCommand" CommandParameter="RU" Margin="10"/>
<Button Content="English-USA" Command="wpfdata:LanguageCommand" CommandParameter="En-Us" Margin="10"/>
</UniformGrid>
语言属性的标记扩展:
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace Wpf.Data
[MarkupExtensionReturnType(typeof(Binding))]
public class LanguageAwareExtension : Binding
public LanguageAwareExtension()
Path = new PropertyPath(
"(0)",
typeof(LanguageAware).GetProperty(nameof(LanguageAware.CurrentLanguage)));
ConverterCulture = CultureInfo.CurrentCulture;
设置窗口语言的使用示例:
<Window ----------------------------
----------------------------
Language="wpfdata:LanguageAware">
<Window.Resources>
<sys:DateTime x:Key="date">12.31.2021 15:47</sys:DateTime>
</Window.Resources>
<UniformGrid Columns="1">
<TextBlock Text="Binding Path=(wpfdata:LanguageAware.CurrentLanguage)" />
<TextBox Text="Binding Mode=OneWay, Source=StaticResource date, StringFormat=\0:F\"/>
<TextBox Text="Binding Mode=OneWay, Source=StaticResource date, StringFormat=\0:F\"/>
<Button Content="Russian" Command="wpfdata:LanguageCommand" CommandParameter="RU" Margin="10"/>
<Button Content="English-USA" Command="wpfdata:LanguageCommand" CommandParameter="En-Us" Margin="10"/>
</UniformGrid>
</Window>
【讨论】:
你能给我一个如何实现第二个选项的例子,因为我不熟悉 wpf 中的多重绑定,但除此之外,第一个解决方案对我不起作用。跨度> 查看我的答案的补充。我在那里添加了另一个实现选项和带有代码的扩展示例。 我没有仔细查看您的代码。事实是,当源的值发生变化时,不会重新创建 Binding。因此,Binding.ConverterCulture 属性(您在 CultureAwareBinding 类中设置)将与初始化 Window 时相同。因此,我提出的第一个选项不适用于您的实施。 感谢您的回复 终于解决了这个问题:github.com/dotnet/wpf/issues/1946#issuecomment-534564980 ..我会发布答案,非常感谢您的时间。【参考方案2】:答案
App.xaml.cs
public App()
CultureInfo CultureInformation = new CultureInfo("en-US");
CultureInformation.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";
CultureInformation.DateTimeFormat.LongDatePattern = "ddd, dd/MM/yyyy";
CultureInfo.DefaultThreadCurrentCulture = CultureInformation;
CultureInfo.DefaultThreadCurrentUICulture = CultureInformation;
//
XmlLanguage language = XmlLanguage.GetLanguage(CultureInformation.IetfLanguageTag);
const BindingFlags kField = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;
typeof(XmlLanguage).GetField("_equivalentCulture", kField).SetValue(language, CultureInformation);
typeof(XmlLanguage).GetField("_compatibleCulture", kField).SetValue(language, CultureInformation);
typeof(XmlLanguage).GetField("_specificCulture", kField).SetValue(language, CultureInformation);
FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(language));
MainWindow.xaml.cs
private void UpdateLanguage(string Language)
LanguageComboBox.SelectedValue = Properties.Settings.Default.Language = Language;
Properties.Settings.Default.Save();
//
ResourceDictionary Dictionary = new();
Dictionary.Source = new Uri(@$"..\Languages\Language.xaml", UriKind.Relative);
Resources.MergedDictionaries.Clear();
Resources.MergedDictionaries.Add(Dictionary);
//
if (Language == "العربية")
CultureInfo CultureInformation = CultureInfo.CreateSpecificCulture("ar-EG");
CultureInformation.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";
CultureInformation.DateTimeFormat.LongDatePattern = "ddd, dd/MM/yyyy";
Thread.CurrentThread.CurrentCulture = CultureInformation;
Thread.CurrentThread.CurrentUICulture = CultureInformation;
//
XmlLanguage language = XmlLanguage.GetLanguage(CultureInformation.IetfLanguageTag);
const BindingFlags kField = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;
typeof(XmlLanguage).GetField("_equivalentCulture", kField).SetValue(language, CultureInformation);
typeof(XmlLanguage).GetField("_compatibleCulture", kField).SetValue(language, CultureInformation);
typeof(XmlLanguage).GetField("_specificCulture", kField).SetValue(language, CultureInformation);
this.Language = language;
else if (Language == "English")
CultureInfo CultureInformation = CultureInfo.CreateSpecificCulture("en-US");
CultureInformation.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";
CultureInformation.DateTimeFormat.LongDatePattern = "ddd, dd/MM/yyyy";
Thread.CurrentThread.CurrentCulture = CultureInformation;
Thread.CurrentThread.CurrentUICulture = CultureInformation;
//
XmlLanguage language = XmlLanguage.GetLanguage(CultureInformation.IetfLanguageTag);
const BindingFlags kField = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;
typeof(XmlLanguage).GetField("_equivalentCulture", kField).SetValue(language, CultureInformation);
typeof(XmlLanguage).GetField("_compatibleCulture", kField).SetValue(language, CultureInformation);
typeof(XmlLanguage).GetField("_specificCulture", kField).SetValue(language, CultureInformation);
this.Language = language;
有NO ConverterCulture类
public class CultureAwareBinding : Binding
public CultureAwareBinding()
ConverterCulture = CultureInfo.CurrentCulture;
UserControl.xaml (普通绑定)
<TextBlock Grid.Row="5" FontSize="14" FontFamily="StaticResource Segoe Semibold" Foreground="DynamicResource BackgroundBrush">
<TextBlock Text="Binding Path=StartTime, StringFormat=0:hh:mm tt"/>
<TextBlock Text="" FontSize="12" FontFamily="StaticResource Segoe Icons"/>
<TextBlock Text="Binding Path=EndTime, StringFormat=0:hh:mm tt"/>
</TextBlock>
【讨论】:
【参考方案3】:您可能需要通知您的 WPF 表单控件。只需要为 CurrentCulture 提高 PropertyChanged 这是example
【讨论】:
我在我的窗口中实现了 iNotifyPropertyChanged 并调用了 notifypropertychanged(nameof(currentculture)) 但没有任何改变。如果你遇到过这样的情况,你能告诉我怎么做吗?以上是关于在 WPF .net core 5 中运行时更改应用程序文化时如何更新属性绑定的主要内容,如果未能解决你的问题,请参考以下文章
在visual studio 2008中运行时,表单不显示更改