El siguiente método sirve para aplicar, de forma automatizada, y recursivamente, los recursos aplicables de localización para un
Form específico, o para todos los
Forms visibles de la aplicación actual.
En otras palabras, el siguiente método sirve para automatizar un cambio de idioma en nuestra aplicación, y tan solo necesitando una línea de código para llamar a dicho método...
He tenido que desarrollar este método, por que todas las alternativas que hay disponibles por Internet son muy básicas e ineficientes, ya que se limitan a iterar los controles y controles hijo, mientras que mi implementación además también itera los menús y sus items de forma recursiva, y los componentes de un form (como un NotifyIcon).
Ejemplos de uso:' Aplica recursos de localización a un form específico
Dim form As Form = Me
Dim cultureName As String = "es-ES"
ApplyCultureResources(form, cultureName)
' Aplica recursos de localización a todos los forms de la aplicación
Dim cultureName As String = "es-ES"
ApplyCultureResources(cultureName)
Salida de depuración (ejemplo limitado):Cambio de idioma a Inglés:
Culture: English (en), Component: Form1 , Text: My Form
Culture: English (en), Component: Button1 , Text: My Button
Culture: English (en), Component: ToolStrip1 , Text: (null)
Culture: English (en), Component: ToolStripStatusLabel1 , Text: Testing
Culture: English (en), Component: MenuStrip1 , Text: (null)
Culture: English (en), Component: ToolStripMenuItem1 , Text: One
Culture: English (en), Component: ToolStripMenuItem2 , Text: Two
Culture: English (en), Component: TabControl1 , Text: (null)
Culture: English (en), Component: TabPage1 , Text: Page 1
Culture: English (en), Component: TabPage2 , Text: Page 2
Culture: English (en), Component: NotifyIcon1 , Text: Icon
Cambio de idioma a Español:
Culture: Spanish (es), Component: Form1 , Text: Mi Form
Culture: Spanish (es), Component: Button1 , Text: Mi Botón
Culture: Spanish (es), Component: ToolStrip1 , Text: (null)
Culture: Spanish (es), Component: ToolStripStatusLabel1 , Text: Probando
Culture: Spanish (es), Component: MenuStrip1 , Text: (null)
Culture: Spanish (es), Component: ToolStripMenuItem1 , Text: Uno
Culture: Spanish (es), Component: ToolStripMenuItem2 , Text: Dos
Culture: Spanish (es), Component: TabControl1 , Text: (null)
Culture: Spanish (es), Component: TabPage1 , Text: Página 1
Culture: Spanish (es), Component: TabPage2 , Text: Página 2
Culture: Spanish (es), Component: NotifyIcon1 , Text: Icono
IMPORTANTE: el siguiente método depende de los métodos de extensión
ForEachControl y
ForEachItem que compartí en el post anterior de este hilo:
Y también depende de este otro método de extensión:
''' <summary>
''' Provides extension methods for the <see cref="WinForms.IContainerControl"/> interface.
''' </summary>
<HideModuleName>
Public Module IContainerControlExtensions
''' <summary>
''' Gets the underlying <see cref="System.ComponentModel.ComponentCollection"/> collection
''' of the source <see cref="IContainerControl"/>.
''' </summary>
'''
''' <param name="container">
''' The source <see cref="IContainerControl"/>.
''' </param>
'''
''' <returns>
''' The underlying <see cref="System.ComponentModel.ComponentCollection"/> collection
''' of the source <see cref="IContainerControl"/>.
''' </returns>
<DebuggerStepThrough>
<Extension>
<EditorBrowsable(EditorBrowsableState.Always)>
Public Function GetComponentCollection(container As IContainerControl) As ComponentCollection
Dim type As Type = container.GetType()
Dim componentsField As FieldInfo = type.GetField("components", BindingFlags.NonPublic Or BindingFlags.Instance)
If componentsField Is Nothing Then
Throw New InvalidOperationException("""components"" field was not found through Reflection.")
End If
Dim containerComponents As IContainer = TryCast(componentsField.GetValue(container), IContainer)
Return containerComponents?.Components
End Function
End Module
El código:''' <summary>
''' This method sets the current UI culture to the specified culture name,
''' then applies culture-specific resources to the specified <see cref="Form"/>,
''' to its controls and child controls, including menus and their items, and
''' the components in the form's <see cref="ComponentCollection"/>, recursively.
''' </summary>
'''
''' <example> This is a code example.
''' <code language="VB">
''' Dim form As Form = Me
''' Dim cultureName As String = "es-ES"
''' ApplyCultureResources(form, cultureName)
''' </code>
''' </example>
'''
''' <param name="form">
''' The form to apply resources to.
''' </param>
'''
''' <param name="cultureName">
''' The culture name of the resources to apply.
''' </param>
Public Shared Sub ApplyCultureResources(form As Form, cultureName As String)
Dim culture As CultureInfo = CultureInfo.GetCultureInfo(cultureName)
#If Not NETCOREAPP Then
My.Application.ChangeUICulture(cultureName)
#Else
Thread.CurrentThread.CurrentUICulture = culture
#End If
Dim resources As New ComponentResourceManager(form.GetType())
' Action delegate that applies resources to an IComponent.
Dim applyResources As Action(Of IComponent, String) =
Sub(component As IComponent, name As String)
If String.IsNullOrEmpty(name) Then
' Not valid to apply localization resources.
Exit Sub
Else
resources.ApplyResources(component, name, culture)
End If
' Applies resources to the items and subitems of a ToolStrip component, recursively.
If TypeOf component Is ToolStrip Then
Dim ts As ToolStrip = DirectCast(component, ToolStrip)
ToolStripExtensions.ForEachItem(ts, recursive:=True, Sub(item) applyResources(item, item.Name))
End If
#If
DEBUG Then ' Prints debug information. ' Flags to retrieve the "Text" property of a component.
Const textPropBindingFlags As BindingFlags =
BindingFlags.Instance Or BindingFlags.Static Or
BindingFlags.Public Or BindingFlags.NonPublic
Dim textProp As PropertyInfo =
(From prop As PropertyInfo In component.GetType().GetProperties(textPropBindingFlags)
Where prop.PropertyType Is GetType(String) AndAlso
prop.Name.Equals("Text", StringComparison.OrdinalIgnoreCase)
).SingleOrDefault()
Dim text As String = DirectCast(textProp?.GetValue(component), String)
If String.IsNullOrEmpty(text) Then
text = "(null)"
End If
Debug.
WriteLine($
"Culture: {culture.EnglishName} ({culture.Name}), Component: {name,-40}, Text: {text}") #End If
End Sub
' Apply resources to the form.
applyResources(form, form.Name)
' Apply resources to the controls hosted in the form, recursively.
FormExtensions.ForEachControl(form, recursive:=True, Sub(ctrl) applyResources(ctrl, ctrl.Name))
' Apply resources to the components hosted in the ComponentCollection of the form.
Dim components As ComponentCollection = IContainerControlExtensions.GetComponentCollection(form)
If components IsNot Nothing Then
' Flags to retrieve the "Name" property of a component.
Const namePropBindingFlags As BindingFlags =
BindingFlags.Instance Or BindingFlags.Static Or
BindingFlags.Public Or BindingFlags.NonPublic
For Each component As IComponent In components
Dim nameProp As PropertyInfo =
(From prop As PropertyInfo In component.GetType().GetProperties(namePropBindingFlags)
Where prop.PropertyType Is GetType(String) AndAlso
prop.Name.Equals("Name", StringComparison.OrdinalIgnoreCase)
).SingleOrDefault()
Dim name As String = DirectCast(nameProp?.GetValue(component), String)
applyResources(component, name)
Next component
End If
' This code finds and applies resources to component fields declared at the form level
' (including those in the auto-generated code of the form designer) that doesn't have
' defined a "Name" property (such as NotifyIcon, ColorDialog, OpenFileDialog, etc).
Const fieldsBindingFlags As BindingFlags =
BindingFlags.Instance Or BindingFlags.DeclaredOnly Or BindingFlags.Static Or
BindingFlags.Public Or BindingFlags.NonPublic
Dim fields As IEnumerable(Of FieldInfo) =
From field As FieldInfo In form.GetType().GetFields(fieldsBindingFlags)
Where GetType(IComponent).IsAssignableFrom(field.FieldType) AndAlso
Not GetType(Control).IsAssignableFrom(field.FieldType) AndAlso
Not GetType(ToolStripItem).IsAssignableFrom(field.FieldType)
For Each field As FieldInfo In fields
Dim component As IComponent = DirectCast(field.GetValue(form), IComponent)
Dim name As String = field.Name.TrimStart("_"c) ' E.g.: "_NotifyIcon1" -> "NotifyIcon1"
applyResources(component, name)
Next field
End Sub
''' <summary>
''' This method sets the current UI culture to the specified culture name,
''' then applies culture-specific resources to the open forms of the current application,
''' to its controls and child controls, including menus and their items, and
''' the components in the form's <see cref="ComponentCollection"/>, recursively.
''' </summary>
'''
''' <example> This is a code example.
''' <code language="VB">
''' Dim cultureName As String = "es-ES"
''' ApplyCultureResources(cultureName)
''' </code>
''' </example>
'''
''' <param name="cultureName">
''' The culture name of the resources to apply.
''' </param>
Public Shared Sub ApplyCultureResources(cultureName As String)
For Each form As Form In System.Windows.Forms.Application.OpenForms
ApplyCultureResources(form, cultureName)
Next form
End Sub