Navegador Web escrito con Visual Basic 2005 [2ª parte][añadiendo formulario de opciones y localización]
Fecha: 05/Ene/2006 (5 de Enero de 2006)
|
Resumen del ejercicio
- La revista dotNetManía de diciembre (nº 21) publica un artículo de el Guille titulado "Localización práctica de aplicaciones", continuación de otro artículo del mes de octubre, sobre las clases de .NET que podemos usar para mostrar los formularios con diferentes referencias culturales e idiomáticas
- Aprovechando mi ejercicio anterior de fecha 14 de diciembre de 2005 "Navegador Web escrito con Visual Basic 2005 Express Edition", he ampliado la aplicación para poner en práctica algunas de las propuestas de el Guille pero, como siempre, "rebajando" el nivel de complejidad del código para simplificarlo al máximo y hacerlo plenamente comprensible al programador poco experto
- En esta entrega, se añade un formulario de opciones que permite configurar el programa, tanto en lo que respecta a si preferimos que arranque en ventana o a pantalla completa como a la hora de cambiar el idioma utilizado (restringido a 2: español e inglés)
- Aparte de ello, se plantea la lectura y escritura de claves en el registro de Windows para que podamos añadir la aplicación a la lista de navegadores del sistema y a la lista de aplicaciones
Añadiendo configuraciones
A las configuraciones existentes que guardaban las medidas de la ventana cuando ésta se cerraba (width, height, left, top) se añaden otras 2: una para guardar el modo de inicio de la aplicación (en ventana o a pantalla completa) y otra para guardar el nombre de la cultura por defecto (español):
Se accede a ellas mediante la clase MySettings usando la palabra clave My, como vemos en este código referido a Ventana:
'obtener los datos de la configuración guardada al cerrar el formulario por última vez ' Select Case My.Settings.Ventana 'si se configura para arrancar en ventana Case "full_no" Me.Width = My.Settings.Anchura Me.Height = My.Settings.Altura Me.Left = My.Settings.Izquierda Me.Top = My.Settings.Derecha 'si se configura para arrancar a pantalla completa Case "full" Me.WindowState = FormWindowState.Maximized End SelectY también se puede modificar su valor y guardarlo al salir de la aplicación; el método específico que guarda la configuración es ChangeAndPersistSettings() pero también se puede utilizar en su lugar la opción "Save My.Settings on shutdown" de la pestaña Application en las propiedades del proyecto; lo vemos de nuevo referido a Ventana:
'método específico que guarda la configuración, también se puede utilizar en su lugar la opción '<Save My.Settings on shutdown> de la pestaña Application en las propiedades del proyecto Sub ChangeAndPersistSettings() 'guardar las medidas de la ventana sólo si no está maximizada o minimizada 'y si My.Settings.Ventana está configurado como "full_no" If (Me.WindowState = FormWindowState.Normal) AndAlso (My.Settings.Ventana = "full_no") Then My.Settings.Altura = Me.Height My.Settings.Anchura = Me.Width My.Settings.Izquierda = Me.Left My.Settings.Derecha = Me.Top End If ' 'My.Settings.Cultura = lengua 'guardar la configuración My.Settings.Save() End SubAñadiendo el formulario de opciones
Siempre resulta más cómodo que los programas tengan un formulario centralizado en el que poder modificar sus parámetros. En este ejercicio se consigue mediante un formulario de tipo modal (diálogo) que permite al usuario seleccionar entre varias opciones o realizar alguna acción:
- elegir el modo de inicio de la aplicación: ventana o pantalla completa
- elegir el idioma de la interfaz (castellano o inglés)
- registrar la aplicación en la lista de aplicaciones y navegadores
Para comprender los fundamentos de la localización de aplicaciones .NET recomiendo leer los referidos artículos del Guille pues mejor que en ellos no lo podría explicar, aquí sólo voy a mostrar el resultado conseguido y a remarcar a los no iniciados que el proceso es bastante sencillo y al alcance de cualquiera.
Al cambiar la elección del idioma, ha de modificarse en todos los formularios de la aplicación, incluido por supuesto el de opciones. Como ejemplo, éste es el formulario de opciones cuando la interfaz está en español:
Y éste es el mismo formulario con la interfaz en inglés:
Cambio de idioma: cerrar y reiniciar el programa
El cambio instantáneo del idioma de la interfaz se puede conseguir fácilmente mediante la propiedad CurrentCulture del hilo actual, como en este código:
Select Case cbIdiomas.SelectedIndex 'el ítem 1 (índice 0) es "Español (España)" Case 0 lengua = "es-ES" Case 1 'el ítem 2 (índice 1) es "Inglés (Estados Unidos)" lengua = "en-US" End Select My.Settings.Cultura = lengua ' Thread.CurrentThread.CurrentCulture = New CultureInfo(lengua) Thread.CurrentThread.CurrentUICulture = New CultureInfo(lengua)Pero he optado por cerrar el hilo actual del programa y arrancar un hilo nuevo con la cultura (idioma) recién seleccionada. Para ello, desde el formulario de opciones llamo a un método del formulario principal:
'si el ítem seleccionado del comboBox no se corresponde con el valor 'de la cultura del hilo actual, llamar al método nuevoHilo() If Not (Thread.CurrentThread.CurrentCulture.Name = lengua) Then Call nuevoHilo() End IfEl método nuevoHilo crea un nuevo hilo (thread) para enlazar con el método nuevoForm que arranca una instancia diferente del formulario, después de que se cierre la instancia actual. Antes de poder arrancar el nuevo proceso con un control ActiveX de la clase WebBrowser es necesario configurar su "ApartmentState", por ello antes de arrancar el segundo proceso se configura su "ApartmentState" al modo STA (un apartamento -Apartment- es un contenedor lógico dentro de un proceso para los objetos que tienen las mismas características de comunicación con los subprocesos):
Public Shared Sub nuevoHilo() 'cerrar el formulario y que se vuelva a abrir automáticamente para que 'en el nuevo inicio lea correctamente la configuración de idioma y cultura ' 'crear un nuevo hilo (thread) para enlazar con el método nuevoForm() que arranca 'otra instancia diferente de Form1 después de que se cierre la instancia actual Dim hilo As New Thread(AddressOf nuevoForm) hilo.SetApartmentState(ApartmentState.STA) hilo.Start() 'cerrar la aplicación y su hilo actual Application.Exit() End Sub 'método que inicia de nuevo la aplicación Shared Sub nuevoForm() 'detenerse 0,4 segundos Thread.Sleep(400) 'iniciar de nuevo la aplicación en el nuevo hilo Application.Run(New Form1) End SubNOTA (ayuda MSDN): "Un apartamento es un contenedor lógico dentro de un proceso para los objetos que comparten requisitos iguales de acceso a los subprocesos. Todos los objetos que se encuentran en el mismo apartamento pueden recibir llamadas de cualquier subproceso del apartamento. NET Framework no utiliza apartamentos, y los objetos administrados son responsables de utilizar los recursos compartidos de forma segura para la ejecución de subprocesos.
Dado que las clases COM utilizan apartamentos, el CRL necesita crear e inicializar un apartamento cuando llama a un objeto COM en una situación de interoperabilidad COM. Un subproceso administrado puede crear y entrar en un apartamento de un único subproceso (STA: que sólo acepta un subproceso), o un apartamento multiproceso (MTA: que contiene uno o varios subprocesos); hay un tercer ApartmentState indeterminado, cuando el estado no se ha establecido todavía.
Puede controlar el tipo de apartamento creado estableciendo la propiedad ApartmentState del subproceso en uno de los valores de la enumeración ApartmentState. Puesto que un subproceso determinado sólo puede inicializar un apartamento COM una única vez, no se puede cambiar el tipo de apartamento después de la primera llamada al código no administrado."No hay que olvidar traducir los cuadros de diálogo o cualquier otro elemento cuyo aspecto dependa de la cultura activa:
'mensaje después de la operación, diferente según la cultura Select Case lengua.Substring(0, 2).ToLower() Case "es" MsgBox("Operación completada correctamente.", MsgBoxStyle.Information, "Info") Case Else MsgBox("Operation succesfully completed.", MsgBoxStyle.Information, "Info") End SelectEscribiendo en el registro
La lista de aplicaciones del sistema se guarda en la clave
HKEY_CLASSES_ROOT\Applications
y la lista de navegadores web está en
HKEY_LOCAL_MACHINE\Software\Clients\StartMenuInternet
Podemos recuperar o almacenar datos del registro mediante la clase Microsoft.Win32.Registry pero también podemos hacerlo a partir del objeto My; en el código se observan ámbos tipos de código. La clase Registry define objetos RegistryKey que permiten manipular valores del registro con sus métodos GetValue y SetValue:
'leer y escribir en el registro de Windows Shared Sub manejarReg() ' 'objeto RegistryKey que permite manipular valores del registro con sus métodos GetValue y SetValue Dim clave1, clave2 As RegistryKey ' 'ruta contiene la ruta completa de la aplicación, incluido el ejecutable 'se usan comillas al principio y al final pues la ruta contiene espacios Dim ruta As String ruta = """" & Application.ExecutablePath & """" 'nombre contiene el nombre del ensamblado con la extensión Dim nombre As String nombre = System.IO.Path.GetFileName(My.Application.Info.AssemblyName) ' 'HKEY_CLASSES_ROOT\Applications '(añadir el programa a la lista de aplicaciones) '#1 clave1 = My.Computer.Registry.ClassesRoot.CreateSubKey("Applications\emiWeb.exe\Shell\Open\command") clave1.SetValue("", ruta & " ""%1""", RegistryValueKind.String) clave1 = My.Computer.Registry.ClassesRoot.CreateSubKey("Applications\emiWeb.exe\Shell\Open\ddeexec\Application") clave1.SetValue("", "emiWeb.exe ""%1""", RegistryValueKind.String) clave1 = My.Computer.Registry.ClassesRoot.CreateSubKey("Applications\emiWeb.exe\Shell\Open\ddeexec\Topic") clave1.SetValue("", "WWW_OpenURL", RegistryValueKind.String) '#2 ' 'HKEY_LOCAL_MACHINE\Software\Clients\StartMenuInternet 'añadir el programa a la lista de navegadores del sistema clave2 = My.Computer.Registry.LocalMachine.CreateSubKey("Software\Clients\StartMenuInternet\emiWeb.exe") clave2.SetValue("", nombre, RegistryValueKind.String) clave2 = clave2.CreateSubKey("Shell\Open\Command") clave2.SetValue("", ruta, RegistryValueKind.String) ' End SubEl código relativo a clave1 (desde '#1 hasta '#2) equivale a ejecutar un archivo REG con el siguiente contenido:
Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\Applications\emiWeb.exe] [HKEY_CLASSES_ROOT\Applications\emiWeb.exe\Shell] [HKEY_CLASSES_ROOT\Applications\emiWeb.exe\Shell\Open] [HKEY_CLASSES_ROOT\Applications\emiWeb.exe\Shell\Open\command] @="\"D:\\PAPELES\\Visual Studio Projects\\Visual Studio 2005\\Visual Basic\\Navegador Web\\Navegador Web\\bin\\Debug\\emiWeb.exe\" \"%1\"" [HKEY_CLASSES_ROOT\Applications\emiWeb.exe\Shell\Open\ddeexec] [HKEY_CLASSES_ROOT\Applications\emiWeb.exe\Shell\Open\ddeexec\Application]@="emiWeb.exe \"%1\"" [HKEY_CLASSES_ROOT\Applications\navegadorWeb.exe\Shell\Open\ddeexec\Topic] @="WWW_OpenURL"El resultado queda escrito en el registro como muestra la imagen:
Espacios de nombres usados en el código de este artículo:
System
System.Windows.Forms
System.IO
System.Globalization
System.Threading
Microsoft.Win32
Fichero con el código de ejemplo: miliuco_web2.zip - 1.120 KB
(MD5 checksum: [CF554DC3CD9377D9E733EC44CCB65EF3])