在 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 中运行时更改应用程序文化时如何更新属性绑定的主要内容,如果未能解决你的问题,请参考以下文章

在 MFC 中运行时更改编辑框属性

在visual studio 2008中运行时,表单不显示更改

在 IIS 中运行时,AspNet Core 使用内存存储库中的数据保护

在 postgres 中运行时查询计划更改

WPF中运行时使内容可以上下左右被鼠标拖动应该怎么做?

在 C# 中运行时更改 Crystal Report 数据源(访问)