在 WPF .net core 5 中运行时更改应用程序区域性时如何更新 属性 绑定

How to update property binding when changing the culture of the application at runtime in WPF .net core 5

我试图让我的 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;
                    }
                }    

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="{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>

提前致谢。

您可能需要通知您的 WPF 表单控件。只需要为 CurrentCulture 提高 PropertyChanged 这是一个 example

遗憾的是,没有现成的简单解决方案可以适用于所有情况。
该决定取决于您认为对其实施有效的内容以及您如何实施绑定。

  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]));
    }

但是这个方法有它的缺点:它重绘了所有windows,UI元素的状态被重置(例如,光标在TextBox或SelectedItem中的位置),可能有绑定不是数据上下文等

  1. 如果您需要考虑文化的绑定不多,或者它们不仅是为数据上下文创建的,那么剩下的就是调用所有此类绑定的重绘。
    通过使用 MiltiBinding 而不是 Binding 进行最小的更改,这可以完成,其中一个绑定(通常是最后一个)将使用具有当前文化的 属性。
    对于这样的 MiltiBinding,转换器 returns 接收到的值数组的第一个值(如果有两个)。
    为简单起见,您可以从 MiltiBinding 派生一个 class 以使其应用程序尽可能接近常规绑定。

以另一种方式补充答案:

  1. 可以使用FrameworkElement.Language属性设置文化。 这是一个依赖项 属性 所以你可以绑定它。 属性 值由子项继承(类似于 DataContext)。 如果将它设置为 Window,那么其中的所有元素也将取相同的值。 或者您可以将其设置为某个特定元素。

可以声明其他类型以减少 XAML 代码。
例子.
静态 class 用于全局设置文化:

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语言的示例:

<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>

答案

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;
            }
        }

没有 ConverterCulture class

    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>