Colaboraciones en el Guille

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)
Autor: Emilio Pérez Egido (miliuco)

 



Resumen del ejercicio

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):

Settings

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 Select

Y 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 Sub

Añ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:

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 If

El 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 Sub

NOTA (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 Select

Escribiendo 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 Sub

El 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])


ir al índice principal del Guille