在 .net 5 WPF 应用程序中设置文化

Posted

技术标签:

【中文标题】在 .net 5 WPF 应用程序中设置文化【英文标题】:Setting Culture in .net5 WPF application 【发布时间】:2021-12-02 11:36:24 【问题描述】:

在 WPF 应用程序中,我尝试在 OnStartup 中设置文化。

    protected override async void OnStartup(StartupEventArgs startupEventArgs)
    
        base.OnStartup(startupEventArgs);
        var a = new CultureInfo(ConfigurationManager.AppSettings["Language"]);
        Thread.CurrentThread.CurrentCulture = a;
        Thread.CurrentThread.CurrentUICulture = a;            
        CultureInfo.DefaultThreadCurrentCulture = a
        CultureInfo.DefaultThreadCurrentUICulture = a;
        CultureInfo.CurrentCulture = a;
    

如果我使用Click 事件或ICommandMainWindow 开始一个方法,那么在该方法中Thread.CurrentThread.CurrentUICulture 将始终为en-US,这很奇怪(有人可以解释一下吗?)。我可以再次设置为所需的Culture,但我必须在每个调用的方法中一一进行。有其他选择吗?

在 .net4.7 中有一个 workaround,但它在 .net5 中不起作用。

【问题讨论】:

OnStartup 方法为什么是async 有什么原因吗?我可以使用async 关键字重现该问题,但如果我删除它,则会设置正确的文化并使用该文化格式化字符串。因此,如果不需要,也许只需删除 async 部分? @LarsKristensen 没有使用 await 运算符,所以我很困惑如何/为什么会有所作为...... @Dai 我对异步编程不太熟悉,所以我也不知道如何或为什么——但如果我在OnStartup 方法的async 版本中设置断点,它被击中并设置了文化 - 之后加载主窗口时它就不起作用了。但是,我仍然想知道 OnStartup 首先是 async 的原因 - 这在什么情况下有用? @LarsKristensen OnStartup 实际上要长得多,为了简洁起见,我将其缩短了。省略的部分有await 【参考方案1】:

这种行为的原因是async 方法的实现方式。 async 方法有自己的特殊执行上下文。这个上下文有它自己的CultureInfo,它继承自调用async 方法的非异步上下文。 在您的情况下,async 上下文的文化是从主线程继承的,文化被改变之前。

您可以做的是使用Dispatcher.InvokeAsync 来实施已经建议的解决方案,以推迟 CultureInfo 配置。这样配置是在async 上下文之外执行的:

Dispatcher.InvokeAsync(() => CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-EN"));

由于这可能会弄乱您的初始化例程,因为在配置应用程序并显示主窗口后才能使用真实上下文,因此您希望使用不同的解决方案。

例如,您可以使用基于事件的初始化例程,在该例程中首先运行低级应用程序配置(如文化配置),然后继续进行剩余的初始化,其中涉及 async 上下文中的异步操作:

App.xaml.cs

// Event may be defined on a different class
private event EventHandler ConfigurationCompleted;
private void OnConfigurationCompleted() => this.ConfigurationCompleted?.Invoke(this, EventArgs.Empty);

protected override void OnStartup(StartupEventArgs startupEventArgs)

  this.ConfigurationCompleted += ConfigureInAsyncContext;

  // Do "low-level" application configuration. Code may be executed in a different class context

  CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-EN");

  ...

  // Continue in async context
  OnConfigurationCompleted();

 
private async void ConfigureInAsyncContext(object sender, EventArgs e)

  // TODO::Execute async operations

  new MainWindow().Show();
 

关键是将非异步配置与异步初始化分开。

【讨论】:

【参考方案2】:

正如它在评论中正确指出的那样,它可以在没有async 的情况下工作,使用async 你需要在应用程序的线程上进行。关键是,CultureInfo.CurrentCulture 设置了 current 线程的值,因为它是一个线程池,你在async 事件处理程序中看不到它,或者我想你可以偶尔拥有它,如果你会得到相同的线程。

protected async override void OnStartup(StartupEventArgs e)

    base.OnStartup(e);

    await Application.Current.Dispatcher.BeginInvoke((Action)(() =>
    
        var a = new CultureInfo(ConfigurationManager.AppSettings["Language"]);
        Thread.CurrentThread.CurrentCulture = a;
        Thread.CurrentThread.CurrentUICulture = a;

        CultureInfo.DefaultThreadCurrentCulture = a;
        CultureInfo.DefaultThreadCurrentUICulture = a;
        CultureInfo.CurrentCulture = a;
    
    ));

【讨论】:

这个解决方案不能解决我的问题。代码在启动时运行,并处于等待状态,但应用程序仍使用默认文化,例如。如果我有一个变量 decimal d = 12345.67m; 并希望使用 ToString("C") 将其显示为货币。 我检查了Thread.CurrentThread.CurrentUICulture,它设置为正确的文化。您是否检查/评估了BeginInvoke 中的文化,可能您还有其他问题。 有趣的是,当我的窗口打开时,我在十进制值上调用ToString 方法,这显然发生在OnStartup 方法完成之前。该值以我的默认文化 (da-DK) 显示,但如果我稍后(例如,在单击按钮后)再次调用 ToString 方法,它将以 BeginInvoke 中设置的文化显示(英文)。因此,您的解决方案有效,但在初始启动时无效 - 我不知道为什么不。 跟线程无关。仅仅因为方法被定义为异步并不意味着该方法在与主线程不同的线程上执行。线程上下文可以更改为 tas 延续,即在等待之后。如示例中所示,OnStartup 完全在 UI 线程上执行。原因是异步方法是如何实现的。异步方法有自己的特殊上下文。这个上下文有它自己的 CultureInfo,它继承自调用异步方法的线程。在这种情况下,异步上下文的文化是在文化改变之前从主线程继承的。 正如名字 Begin... 就像 BeginInvoke 所暗示的那样,该方法是旧 API 的一部分。您应该改用较新的 InvokeAsync。

以上是关于在 .net 5 WPF 应用程序中设置文化的主要内容,如果未能解决你的问题,请参考以下文章

如何在asp.net core razor pages 2.2中设置日期格式和文化

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

如何在 umbraco 中设置全球文化?

应用程序启动或导航到主页时在 url 中设置当前文化

尝试在 WPF 中设置组合框的背景

如何在 WPF 应用程序中 SOAP 客户端的“Set-Cookie”标头响应的新请求中设置“Cookie”标头