Изменение CurrentUICulture во время выполнения в локализуемой форме в WinForms

Я искал, как изменить язык формы, у которой для атрибута Localizable установлено значение true.

https://msdn.microsoft.com/en-us/library/system.threading.thread.currentuiculture(v=vs.110).aspx

Это необходимо для установки языка формы, но это необходимо установить до того, как мы создадим экземпляр формы. Это не может быть вызвано после этого события.

В поисках информации я увидел следующий вопрос: https://stackoverflow.com/a/11738932/3286975, но, как сказано в комментарии, у меня есть элементы управления внутри TabControl и MenuStrip, поэтому они не затронуты.

Я попытался изменить это, безуспешно получив все элементы управления формы.

введите описание изображения здесь

введите описание изображения здесь

В этом меню я вызываю следующий обратный вызов:

    private void englishToolStripMenuItem_Click_1(object sender, EventArgs e)
    {
        string lang = (string) ((ToolStripMenuItem) sender).Tag;
        base.Culture = CultureInfo.CreateSpecificCulture(lang);
    }

    private void spanishToolStripMenuItem_Click(object sender, EventArgs e)
    {
        string lang = (string) ((ToolStripMenuItem) sender).Tag;
        base.Culture = CultureInfo.CreateSpecificCulture(lang);
    }

Я меняю культуру с помощью тега.

Когда я нажимаю на нее, ничего не происходит. Кроме того, я немного изменил метод ApplyResources из упомянутого ответа.

private void ApplyResources(Control parent, CultureInfo culture)
{
        this.resManager.ApplyResources(parent, parent.Name, culture);

        foreach (Control ctl in parent.IterateAllChildren())
        {
            //this.ApplyResources(ctl, culture);
            this.resManager.ApplyResources(ctl, ctl.Name, culture);
        }
}

Где IterateAllChildren - это следующее: https://stackoverflow.com/a/16725020/3286975

Кроме того, я безуспешно пробовал с (System.LINQ): Controls.OfType<Label>() (потому что у меня есть один ярлык для проверки) ...

введите описание изображения здесь

введите описание изображения здесь

Но когда я выбираю испанский язык, текст не меняется.

Так что, может быть, я не справляюсь с детьми. А может, вызвав метод CreateCulture, не знаю.

Заранее спасибо!

РЕДАКТИРОВАТЬ:

Я протестировал, чтобы получить диспетчер ресурсов моей формы с помощью информации о культуре, и он каждый раз возвращает значение по умолчанию:

 ResourceSet resourceSet = new ResourceManager(typeof(frmCredentials)).GetResourceSet(new CultureInfo(lang), true, true);
            foreach (DictionaryEntry entry in resourceSet)
            {
                string resourceKey = entry.Key.ToString();
                object resource = entry.Value; //resourceSet.GetString(resourceKey);
                if (resource.GetType().Equals(typeof(string)))
                    Console.WriteLine("Key: {0}\nValue: {1}\n\n", resourceKey, (string) resource);
            }

Где new CultureInfo(lang), я тоже тестировал: new CultureInfo("es") & Thread.CurrentThread.CurrentCulture (CurrentUICulture) безуспешно. Как будто он никогда не существует или не заменяется, но в моем дизайне и проводнике файлов я могу видеть файлы.

РЕДАКТИРОВАТЬ2:

Может быть, потому, что я использую ILMerge для объединения всех dll в одну уникальную. Я просматриваю это: с одной сборкой, несколько -языковое развертывание Windows Forms (ILMerge и вспомогательные сборки / локализация) - возможно?

Ответ на EDIT2:

Да, при удалении ILMerge проблема решена, и первое решение, которое я дал, решает эту проблему. Но по какой-то причине испанский язык выбран в качестве языка по умолчанию, и когда я попытался получить от него набор ресурсов, он мне ничего не вернул.

Кроме того, я установил для атрибута Localizable значение false, и он не создал файл resx по умолчанию со значениями. Не знаю, хорошая ли это практика.

Я попробую что-нибудь новенькое ...


person z3nth10n    schedule 01.09.2017    source источник
comment
Я допускаю, что следующий подход не самый лучший, но, поскольку вы знаете, что у вас есть TabControl и MenuStrip, вы можете изменить свой метод IterateAllChildren, чтобы специально отслеживать эти элементы управления и выполнять особую обработку, чтобы гарантировать, что вы охватите все их дочерние элементы управления. Повторная инициализация формы (как предложила @Isma) была бы лучшим подходом, если бы вы могли справиться с очисткой всех данных в форме (например, сначала сохранив их).   -  person wablab    schedule 01.09.2017
comment
stackoverflow.com/questions/21067507/ Решение, указанное на вопросе StackOverflow, работает как шарм.   -  person rqnn    schedule 01.09.2017
comment
Что ж ... Это хороший подход, если вы сделаете, например, следующее: stackoverflow.com/questions/5411694/ Я ценю вашу помощь, но я предпочитаю что-нибудь попроще, если не будет других вариантов, я попробую.   -  person z3nth10n    schedule 02.09.2017
comment
Вместо использования ILMerge вы можете использовать _ 1_, это решает проблемы пространства имен, которые часто вызывает ILMerge. Существует даже библиотека, которая автоматически встраивает файлы и добавляет за вас код загрузки сборки. (На самом деле создатель слияния IL отвечает в комментариях к первой ссылке Как автор ILMerge, я думаю, что это фантастика! Если бы я знал об этом, я бы никогда не написал ILMerge.)   -  person Scott Chamberlain    schedule 02.09.2017


Ответы (3)


Подход MVVM (Model - View - ViewModel) имеет некоторые преимущества, которые могут быть полезны в вашем случае.

Создайте новые файлы ресурсов для языков, которые вы будете использовать для локализации. Работа с собственными файлами ресурсов формы может быть немного сложной, потому что она восстанавливается каждый раз, когда вы вносите изменения в конструктор - поэтому я думаю, что собственный файл ресурсов будет легче поддерживать и даже делиться с другими формами и даже проектами.

LocalizationValues.resx // (default english), set Access Modifier to "Internal" or "Public"
    "Title": "Title"
    "Description": "Description"

LocalizationValues.es.resx
    "Title": "Título"
    "Description": "Descripción"

Visual Studio сгенерирует статический класс LocalizationValues со свойствами в качестве ключей RESX-файла. Таким образом, "Заголовок" можно получить как LocalizationValues.Title

Создайте класс "viewmodel", который представляет все тексты, которые вы используете при локализации. Класс должен реализовывать INotifyPropertyChanged интерфейс.

public class LocalizationViewModel : INotifyPropertyChanged
{
    public string Title
    {
        get
        {
            return LocalizationValues.Title;
        }
    }

    public string Description
    {
        get
        {
            return LocalizationValues.Description;
        }
    }

    public void SetLanguage(string language)
    {
        var culture = new CultureInfo(language);
        Thread.CurrentThread.CurrentUICulture = culture;

        // This is important, 
        // By raising PropertyChanged you notify Form to update bounded controls
        NotifyAllPropertyChanged();
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyAllPropertyChanged()
    {
        // Passing empty string as propertyName
        // will indicate that all properties of viewmodel have changed
        NotifyPropertyChanged(string.Empty);
    }

    protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Затем в форме привязать модель просмотра к элементам управления

public partial class YourForm : Form
{
    private LocalizationViewModel _viewmodel;

    public YourForm()
    {
        InitializeComponent();

        _viewmodel = new LocalizationViewModel();

        // Bound controls to correspondent viewmodel's properties
        LblTitle.DataBindings.Add("Text", _viewmodel, "Title", true);
        LblDescription.DataBindings.Add("Text", _viewmodel, "Description", true);
    }

    // Menu buttons to change language
    private void SpanishToolStripMenuItem_Click(object sender, EventArgs e)
    {
        _viewmodel.SetLanguage("es");
    }

    private void EnglishToolStripMenuItem_Click(object sender, EventArgs e)
    {
        _viewmodel.SetLanguage("en");
    }
}

Приведенный выше подход даст больше преимуществ, чем только обновление элементов управления. Вы получаете четко разделенные части вашего приложения, которые можно тестировать независимо друг от друга.

person Fabio    schedule 02.09.2017
comment
Ваши два звонка в NotifyPropertyChanged(nameof(...)) можно упростить. Если вы только что NotifyPropertyChanged("") подняли пустую строку или ноль для PropertyChanged события, это означает, что все свойства изменились и все должны быть повторно оценены. Примечание для этого находится в раздел примечаний в документации к самому мероприятию. - person Scott Chamberlain; 02.09.2017
comment
@ScottChamberlain - хорошо, не знал об этом. Документацию следует читать внимательно - это реально сэкономит время. Спасибо - person Fabio; 02.09.2017

Существуют разные решения для решения проблемы, в том числе MVVM, упомянутый в других ответах. Но вы можете рассмотреть и другие варианты.

Вызов ApplyResource для всех элементов управления

Вы можете установить текущую культуру пользовательского интерфейса для текущего потока и вызов ApplyResource для всех элементов управления. Для этого вам нужно создать метод для возврата всех элементов управления, а затем просто вызвать ApplyResource для всех элементов управления, например:

private void englishToolStripMenuItem_Click(object sender, EventArgs e)
{
    SetCulture("en-US");
}
private void persianToolStripMenuItem_Click(object sender, EventArgs e)
{
    SetCulture("fa-IR");
}
public void SetCulture(string cultureName)
{
    System.Threading.Thread.CurrentThread.CurrentUICulture =
       System.Globalization.CultureInfo.GetCultureInfo(cultureName);
    var resources = new System.ComponentModel.ComponentResourceManager(this.GetType());
    GetChildren(this).ToList().ForEach(c => {
        resources.ApplyResources(c, c.Name);
    });
}
public IEnumerable<Control> GetChildren(Control control)
{
    var controls = control.Controls.Cast<Control>();
    return controls.SelectMany(ctrl => GetChildren(ctrl)).Concat(controls);
}

Создание компонента расширения локализации текста

Вы также можете создать компонент-расширитель, который можно использовать как во время разработки, так и во время выполнения, и назначить некоторый файл ресурсов и ключи ресурсов элементам управления. Таким образом, вы можете просто переключаться между языками, изменяя текущую культуру пользовательского интерфейса текущего потока. Чтобы увидеть пример идеи, взгляните на этот пост:

person Reza Aghaei    schedule 02.09.2017
comment
Моя основная проблема сейчас в том, что сборки, использующие ILMerge, не работают. Итак, я должен разобраться, спасибо за ваш ответ! - person z3nth10n; 02.09.2017
comment
Это может быть ваш следующий вопрос. Не смешивайте несколько вопросов в одном посте :) - person Reza Aghaei; 02.09.2017
comment
Мне нужно будет это сделать, и я опубликую свое решение, но спасибо! - person z3nth10n; 02.09.2017
comment
Могу сказать, что предоставленные решения решат вашу проблему в обычном приложении. Я не имею представления о проблеме в другом случае. Я рассмотрю ваш другой вопрос, если вы опубликуете минимальный воспроизводимый пример :) - person Reza Aghaei; 02.09.2017
comment
Кстати, не упустите вторую идею. Он очень расширяемый. В большинстве случаев я предпочитаю первое решение, но в некоторых случаях я расширил вторую идею и использовал ее для локализации текста элементов управления с помощью поддержки времени разработки. - person Reza Aghaei; 02.09.2017

После некоторых попыток я понял, что некоторые вещи терпят неудачу.

Я также должен сказать, что вся предоставленная помощь в этом вопросе очень ценится, а также что очень полезен фрагмент LocalizedForm .

Первая проблема заключается в том, что я понял, что элементы управления, которые находятся ниже уровня 1 в дочерней иерархии с этим решением, не работают.

А перебор всех элементов управления - дорогостоящая задача. Может быть, мой худший, но у меня меньше итераций (потому что я ищу только элементы управления с текстом)

    /// <summary>
    /// Current culture of this form
    /// </summary>
    [Browsable(false)]
    [Description("Current culture of this form")]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public CultureInfo Culture
    {
        get { return this.culture; }
        set
        {
            if (this.culture != value)
            {
                ResourceSet resourceSet = new ComponentResourceManager(GetType()).GetResourceSet(value, true, true);
                IEnumerable<DictionaryEntry> entries = resourceSet
                    .Cast<DictionaryEntry>()
                    .Where(x => x.Key.ToString().Contains(".Text"))
                    .Select(x => { x.Key = x.Key.ToString().Replace(">", "").Split('.')[0]; return x; });

                foreach (DictionaryEntry entry in entries)
                {
                    if (!entry.Value.GetType().Equals(typeof(string))) return;

                    string Key = entry.Key.ToString(),
                           Value = (string) entry.Value;

                    try
                    {
                        Control c = Controls.Find(Key, true).SingleOrDefault();
                        c.Text = Value;
                    }
                    catch
                    {
                        Console.WriteLine("Control {0} is null in form {1}!", Key, GetType().Name);
                    }
                }

                this.culture = value;
                this.OnCultureChanged();
            }
        }
    }

Что я делаю, так это следующее: сначала я ищу ResourceManager, будьте осторожны! Потому что это вторая проблема, заключающаяся в том, что если вы используете CultureInfo.CreateSpecificCulture вместо CultureInfo.GetCultureInfo, в некоторых случаях будет создана новая культура и будут возвращены значения по умолчанию (Form1.resx значения вместо Form1.es.resx значений (например)).

После того, как мы загрузили все значения из файла resx, мы перебираем их все и удаляем двойное >> (оно появляется в некоторых случаях) и получаем имена этих Элементы управления, в которых объявлен только текстовый атрибут.

Следующий шаг - найти Control и заменить его Text ...

Что ж, у меня небольшой беспорядок с производными классами, поэтому я создал систему try-catch, потому что Элементы управления. Найти поиск во всех производных классах Я бы предпочел быть более конкретным, но я не знаю, как ... (Вот почему я создал этот вопрос)

При этом нам не нужно сохранять какие-либо объекты, потому что мы не будем их очищать и воссоздавать.

Основная проблема здесь заключалась не в том, как я это делал, потому что это было правильно. Проблема в том, что объединенные сборки делают странные вещи, когда вы вызываете, например, следующее:

ResourceSet resourceSet = new ComponentResourceManager(GetType()).GetResourceSet(value, true, true);

value будет значением по умолчанию ... Примерно так, эта Культура не существует, потому что объединенная сборка не может найти файл resx.

Итак, я попробую AppDomain.CurrentDomain.AssemblyResolve, который мне предложил @ScottChamberlain. Или, может быть, ILRepack.

Любая помощь по оптимизации или идеи (в комментариях) о том, почему это не работает, будут оценены!

person z3nth10n    schedule 02.09.2017