C# 在WinForms中以可本地化的形式在运行时更改CurrentUICulture

C# 在WinForms中以可本地化的形式在运行时更改CurrentUICulture,c#,winforms,currentculture,C#,Winforms,Currentculture,我一直在研究如何更改Localizable属性设置为true的表单的语言 这是为了设置表单的语言,但需要在实例化表单之前设置。此事件后无法调用此函数 搜索信息时,我看到了以下问题:但是,正如一条评论所说,我在TabControl和MenuStrip中有控件,所以它们不受影响 我已经尝试过修改它,在没有运气的情况下获得表单的所有控件 在此菜单中,我调用以下回调: private void englishToolStripMenuItem_Click_1(object sender,

我一直在研究如何更改
Localizable
属性设置为true的表单的语言

这是为了设置表单的语言,但需要在实例化表单之前设置。此事件后无法调用此函数

搜索信息时,我看到了以下问题:但是,正如一条评论所说,我在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如下所示:

此外,我还尝试了(System.LINQ):
Controls.OfType()
(因为我有一个标签来测试这个),但运气不好

但是,当我选择西班牙语时,不会更改任何文本

所以也许,我和孩子们在一起失败了。或者通过调用方法
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);
            }
newcultureinfo(lang)
中,我还测试了:
newcultureinfo(“es”)
Thread.CurrentThread.CurrentCulture
(CurrentUICulture),但没有运气。就像它从未存在或被替换,但在我的设计和文件资源管理器中,我可以看到这些文件

EDIT2:

可能是因为我使用ILMerge将所有DLL合并到一个唯一的DLL中。我正在回顾:

回复EDIT2:

是的,问题解决了,我给出的第一个解决方案解决了这个问题。但出于某种原因,西班牙语被视为默认语言,当我试图从中获取资源集时,它没有返回任何信息

Aso,我已经将Localizable属性设置为false,并且它没有创建具有值的默认resx文件。我不知道这是否是一个好的做法

我将尝试一些新的…

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生成静态类
本地化值
,属性作为.resx文件的键。因此,“Title”可以作为本地化值访问。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));
    }
}
然后在表单中将viewmodel绑定到控件

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");
    }
}

上述方法将提供比仅更新控制更大的好处。您可以获得应用程序的清晰分离部分,这些部分可以彼此独立测试。

有不同的解决方案,包括其他答案中提到的MVVM。但是你也可以考虑一些其他的选择。

在所有控件上调用ApplyResource

您可以在所有控件上设置当前线程的当前UI区域性和调用
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);
}
private void englishToolStripMenuItem\u单击(对象发送方,事件参数e)
{
SetCulture(“en-US”);
}
私有void persianToolStripMenuItem\u单击(对象发送方,事件参数e)
{
集落培养(“fa-IR”);
}
public void SetCulture(字符串cultureName)
{
System.Threading.Thread.CurrentThread.CurrentUICulture=
系统.全球化.文化信息.获取文化信息(cultureName);
var resources=new System.ComponentModel.ComponentResourceManager(this.GetType());
GetChildren(this.ToList().ForEach(c=>{
资源。应用资源(c,c.Name);
});
}
公共IEnumerable GetChildren(控件)
{
var controls=control.controls.Cast();
返回控件。选择多个(ctrl=>GetChildren(ctrl)).Concat(控件);
}
创建文本本地化扩展程序组件

您还可以创建可在设计时和运行时使用的扩展程序组件,并为控件分配一些资源文件和资源密钥。通过这种方式,您可以通过更改当前线程的当前UI区域性在语言之间切换。要想了解这一想法的一个例子,请看以下帖子:


经过一些尝试,我意识到有几件事情失败了

我还要说的是,在这个问题上给予的所有帮助都是非常感谢的,而且这是非常有用的

第一个问题是,我已经意识到,使用此解决方案的子层次结构中,级别低于1的控件不起作用

迭代所有控件是一项昂贵的任务。也许我的最差,但迭代次数较少(因为我只搜索带有文本的控件)

//
///这种形式的当前文化
/// 
[可浏览(错误)]
[说明(“本表格的当前文化”)]
[EditorBrowsable(EditorBrowsableState.Never)]
公共文化信息文化
{
获取{返回this.culture;}
设置
{
if(this.culture!=值)
{
ResourceSet ResourceSet=new ComponentResourceManager(GetType()).GetResourceSet(value,true,true);
IEnumerable entries=resourceSet
    /// <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();
            }
        }
    }