How do I set the UI language for a multi-threaded .NET process, independent of the OS language?

守給你的承諾、 提交于 2020-01-04 04:57:06

问题


Problem:

I have a C# .NET 2.0 application developed on Windows 7 that has translated resources for multiple languages (ex. zh-CHS for Chinese, es for Spanish, etc.).

I have a customer who wants to run their Windows 7 OS in English, but run my .NET application in Spanish (es).

My application is multi-threaded, so just changing the culture of the main GUI thread is not sufficient for my needs (trust me, I tried). This is because other strings displayed to the user through the GUI are generated on other threads. In order to get 100% complete coverage, I would need to set the culture of each individual thread manually to ensure all text from resource files is in the correct language. Because my product is basically a framework for other plugins that other development groups write, I don't have control over actions performed in threads created in other plugins. Because of this, manually changing the culture for each thread is not a valid option.

What I am looking for is a way to set the overall language for the application, without having to change any of the OS user settings.

In doing some research, I came across the following method for setting the preferred UI language for a process: SetProcessPreferredUILanguages

After reading up on this, it appears that this call is what I am looking for. However, when I implemented this call in the Main method of my C# application, it doesn't appear to do anything.

The return value from the following code is true, but I never see my GUI application displaying the text in Spanish.

    [DllImport("Kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
    public static extern Boolean SetProcessPreferredUILanguages(UInt32 dwFlags, String pwszLanguagesBuffer, ref UInt32 pulNumLanguages);

    public void SetLanguages()
    {
        uint numLangs = 0;
        string[] langs = new string[3];
        uint MUI_LANGUAGE_NAME = 0x8; // Use ISO language (culture) name convention

        langs[0] = "es\u0000";
                langs[1] = "zh-CHS\u0000";
        langs[2] = "en-US\u0000";

        numLangs = (uint)langs.Length;

        if (SetProcessPreferredUILanguages(MUI_LANGUAGE_NAME, String.Concat(langs), ref numLangs))
        {
            Console.WriteLine("Successfully changed UI language");
        }
    }

Is there something else I am missing in order for this to successfully run my GUI application with the Spanish resources loaded?

I am trying to implement the 2nd option of the table at the bottom of the MSDN page for Building MUI Applications, where I have Application-specific UI language settings and want to achieve the desired result for resource loading:

Application calls MUI API to set application-specific UI languages or process-preferred UI languages and then calls standard resource loading functions. Resources are returned in the languages set by the application or system languages.

I have made the call to successfully set the process preferred UI languages, but my resources are not being loaded in the language I would expect. A commenter mentioned this call will only work for un-managed resources, which I could not verify 100%, but the behavior seems in indicate this is the case.

I can't be the only person who has ever tried to implement a .NET application in this manner. It's frustrating that there isn't more information about how to do this.

Thanks in advance,

Kyle


回答1:


Starting with .NET 4.5 the CultureInfo.DefaultThreadCurrentCulture property allows you to set the default culture for threads in the current application domain. Other than that I have not found a way to do this elegantly.

Here is the code that will show you how to reproduce this behavior:

using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;

namespace SimpleMultithreadedTestForCulture {

  class Program {

    static void Main() {
      const double value = 12345.78;
      Console.WriteLine("Value from local thread - default culture: {0}", value.ToString("C"));
      CultureInfo myCulture = new CultureInfo(Thread.CurrentThread.CurrentUICulture.Name);
      myCulture.NumberFormat.CurrencySymbol = "₤";
      Thread.CurrentThread.CurrentUICulture = myCulture;
      Thread.CurrentThread.CurrentCulture = myCulture;
      Console.WriteLine("Value from local thread - 'my' culture: {0}", value.ToString("C"));
      Task.Factory.StartNew(() => Console.WriteLine("Value from a different thread: {0}", value.ToString("C")));
      Console.ReadKey();
    }

  }

}

Output:

Value from local thread - default culture: $12,345.78
Value from local thread - 'my' culture: £12,345.78
Value from a different thread: $12,345.78

Here is a related question with a possible solution for .NET 4.0 or before, using a helper class to create your threads. Even though they talk about WPF in there, it applies to other .NET multi-threaded applications too.

I know you did not want to change the culture for each of the new threads, but I cannot think of any other efficient alternatives for it.




回答2:


Based on feedback here, and other research, I have come to the conclusion that setting the culture for a multi-threaded .NET application independent of the OS language is not possible using .NET versions older than 4.5.

This really is a shame.

We recently upgraded our software to use .NET 4.0.




回答3:


Your code has a few problems. Unfortunately the fix of problems do not bring the requested behaviour.

The code should be:

[DllImport("Kernel32.dll", ExactSpelling = true, CharSet = CharSet.Unicode)]
public static extern Boolean SetProcessPreferredUILanguages(UInt32 dwFlags,
  String pwszLanguagesBuffer, ref UInt32 pulNumLanguages);

public void SetLanguages()
{
  uint numLangs = 0;
  string[] langs = new string[4];
  uint MUI_LANGUAGE_NAME = 0x8; // Use ISO language (culture) name convention

  langs[0] = "es\0";
  langs[1] = "zh-CHS\0";
  langs[2] = "en-US\0";
  langs[3] = "\0"; //double null ending

  numLangs = (uint)langs.Length;

  if (SetProcessPreferredUILanguages(MUI_LANGUAGE_NAME, String.Concat(langs), ref numLangs))
  {
    if (numLangs == langs.Length - 1)
      Console.WriteLine("Successfully changed UI language");
    else if (numLangs < 1)
      Console.WriteLine("No language could be set");
    else
      Console.WriteLine("Not all languages were set");
  }
  else
    Console.WriteLine("No language could be set");
}

The pwszLanguagesBuffer is defined as PCZZWSTR and that means list of wide string (therefore Charset.Unicode), string end by zero character (no need to use unicode escape sequence), list ends with double zero character (therefore new "empty language"; other way is to put double \0 to last language). I also added new debug description, to test currently set languages, but unfortunately I can not test it here (at work) because the function needs Win7.


But there is a fully different way how to do this. See AppLocale

I can not give a guarantee, that it will work in your case. The application needs Windows XP and newer (there is a small problem in installing on Windows 7; remember to start the installation in administrator mode, otherwise it would not work). The application has also another, bigger problem. It is not possible to switch from Central Europe to Western. But it works from Central Europe to Greece, and other countries with different characters.

I use this application to develop an older (non unicode) application for some countries. Unfortunately it gives a troublesome warning on start...



来源:https://stackoverflow.com/questions/8312345/how-do-i-set-the-ui-language-for-a-multi-threaded-net-process-independent-of-t

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!