【问题标题】:How to update property binding when changing the culture of the application at runtime in WPF .net core 5在 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>

提前致谢。

【问题讨论】:

    标签: c# wpf .net-core data-binding cultureinfo


    【解决方案1】:

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

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

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

    以另一种方式补充答案:

    1. 您可以使用 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)) 但没有任何改变。如果你遇到过这样的情况,你能告诉我怎么做吗?
      猜你喜欢
      • 2013-01-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-24
      • 2013-08-16
      • 1970-01-01
      • 2017-12-31
      • 1970-01-01
      相关资源
      最近更新 更多