Cómo automatizar las aplicaciones de Visual Basic para que el uso de los temas de Windows XP no sea un problema

Publicado: 21/Ago/2003
Actualizado: 21/Ago/2003


Como habrás comprobado si has leído el artículo sobre cómo usar los temas de Windows XP desde Visual Basic, para que algunos controles se vean correctamente al usar los temas de Windows XP en las aplicaciones de Visual Basic, había que "contenerlos" en un control Picture, pues bien, si en tu aplicación tienes un montón de formularios en los que hay que usar esos contenedores extras, la tarea realmente no será trivial (por llamarlo de una forma suave) y puede que desistas porque te de pereza... y como resultado, decidirás finalmente no usar los temas de XP.

Pues bien, si ese es tu caso, el código que te mostraré dentro de poco seguramente te vendrá bien, ya que te permitirá usar los temas de Windows XP en tus aplicaciones de Visual Basic sin que tengas que hacer prácticamente nada... o casi... ya que tendrás que incluir una línea de código en cada formulario y poco más.

Ese "poco más" es que además de la línea de código, también tendrás que incluir un control Picture en cada formulario que quieras "automatizar".

 

Por si andas algo despistado o eres novel en esto de que en tus aplicaciones de Visual Basic se puedan usar los temas de Windows XP, te daré un pequeño sermón introductorio antes de mostrarte el código y explicarte cómo usarlo.

 

Nota para los que no quieren que le cuenten películas y sólo quieran el código:

Como se que hay gente que son programadores de "cortar y pegar" a los que sólo les interesa el código y "punto", (a diferencia de los programadores que "les gusta programar"), y el sitio del Guille no está vetado a nadie...

Si eres de los que no quieren que te cuenten ningún "rollo" y simplemente quieres usar el código... puedes ir al final de esta página y bajarte el código.

Pero si te interesa saber "el porqué" del código que muestro... entonces, sigue leyendo, así tendrás una idea mejor del porqué las rutinas que muestro hacen lo que hacen... aunque puede ser que te aburras y no te enteres de todo, al menos lo intentarás.
Gracias.

 

¿Cual es el problema con los temas de Windows XP y Visual Basic?

Dependiendo de la versión de Visual Basic que estemos usando, este problema será mayor o menor.

En Visual Basic 5.0 sólo habrá problemas con los controles Frames que estén incluidos en otros Frames.
En Visual Basic 6.0 además de los Frames, habrá problemas "visuales" con los botones y sobre todo con los OptionButtons.

En las siguientes imágenes puedes ver cómo quedaría un formulario usando VB5 y el mismo compilado con VB6, sin usar el código que te mostraré ni usando el truco de contener los controles dentro de un Picture, que al fin y al cabo es lo que "automatizará" la rutina que te propongo como solución.

VB5 sin tratamiento especial VB6 sin tratamiento especial

 

Como puedes comprobar el Visual Basic 5 "disimula" mejor y hasta parece que la cosa está bien, salvo por el "caption" o texto de los frames contenidos dentro de otro Frame, en los cuales el tipo de letras es diferente, además de que no se muestra completo.
En Visual Basic 6 la cosa es peor... además del texto de los Frames, los botones tienen un "reborde" negro y de los Options, ni te cuento, ya que es evidente de que no es lo que nos gustaría mostrar.

 

¿Cómo soluciona la rutina estos problemas "visuales"?

Pues lo que hace el código que te mostraré es "poner" los controles dentro de un picture, ya que al hacer esto, esos problemas visuales desaparecen.

Hay varias opciones para automatizar esta "metedura" de controles.
En el caso de que tengamos Visual Basic 5.0, sólo tendremos que preocuparnos de los Frames incluidos dentro de otros Frames.
Pero en Visual Basic 6.0 hay que tener en cuenta también los botones y lo más complicado, los options.

La solución más rápida (en cuanto a tiempo de ejecución y lógica de programación) sería la de incluir cada control "problemático" dentro de un Picture, esto es rápido, aunque además de consumir muchos más pictures, (uno por cada control de tipo Frame, CommandButton u Option), que esté dentro de un Frame, tendrá un problema añadido: si hay varios Options, estos deberían estar dentro de un mismo contenedor para que al seleccionar uno de ellos, los demás que estén en el mismo "nivel" se comporten correctamente y si se inserta cada uno en un Picture, perderán esa conexión.

Otra solución, también rápida, (incluso más que la anterior), es la de usar un Picture en cada Frame, de forma que los controles contenidos en ese Frame, se "metan" en el Picture y a su vez, el Picture incluirlo dentro del Frame.
El problema de esta solución es que los controles Frames son muy suyos y perderíamos algunas de las "cosillas" que podemos hacer con los controles incluidos dentro de un Frame.
Imagina que tienes controles que están "justo" en la línea del Frame o muy cerca de esa línea. En las imágenes mostradas tienes varios de esos controles que están en la línea del Frame.
Como te decía, si incluimos un Picture dentro de un Frame para que contenga los controles incluidos en el Frame, la posición y tamaño de este Picture debe ser menor que el Frame, por ejemplo la posición superior del Picture debe ser 190, con idea de que el texto mostrado en el Frame se vea completo. En este caso, si tenemos un control en la posición 0, no se vería bien, ya que en lugar de estar "al filo" del Frame, estaría "al filo" del Picture, por tanto, a pesar de que la posición real del control sigue siendo cero, visualmente estaría en la posición 190.

La tercera opción, (siempre hablando de que estamos trabajando con VB6), sería la usar varios Pictures (recuerda que los Pictures a los que me estoy refiriendo se usan para contener los controles problemáticos), de forma que cuando la rutina se encuentre con un Frame o un CommandButton los incluya dentro de un Picture y cuando se encuentre con controles Options, los agrupe de forma que todos los que estén dentro de un Frame estén dentro de un mismo Picture-contenedor, para que no pierdan "la gracia" de los Options, los cuales interactúan entre ellos si están en un mismo contenedor.

En el caso de Visual Basic 5.0, como el problema sólo lo tendremos con Frames, dentro de otros Frames, el código se encarga de usar un Picture sólo cuando un Frame esté dentro de otro Frame.

Otra pregunta podría ser:
¿Cómo sabe el código que estamos usando la versión 5.0 ó la 6.0 de Visual Basic?
La respuesta es simple: Porque nosotros se lo decimos.
Este código "supone" que si hemos declarado una constante de compilación condicional llamada ESVB5, es que estamos usando el Visual Basic 5.0, en otro caso, es porque la versión usada es la 6.0 (u otra distinta de VB5, aunque con VB4 no lo he probado).

El código propuesto para automatizar todo este "rollo" está compuesto por varios procedimientos, en el caso de Visual Basic 5.0, sólo hay una rutina, pero para Visual Basic tenemos dos rutinas:

crearPicFrm1, esta rutina es la que usa un Picture "interno" para cada Frame.
La ventaja de este código es que es rápido y consume pocos Picture-contenedores: uno por cada Frame que tengamos en el formulario. El problema es que si hay controles que están "muy arriba" del Frame, no se verán bien.
Esta rutina sería la preferida para usar con VB6 si sabemos que no hay botones ni Options situados cerca de la parte superior del Frame.

crearPicFrm2, esta rutina buscará los Frames y CommandButtons y creará un Picture-contenedor en el que incluirá esos controles. En el caso de que haya controles del tipo OptionButtons, los agrupará en un mismo Picture.
Si no quieres "complicarte la vida", puedes usar directamente esta rutina, ya que aunque consume más controles-contenedores, es segura y lo deja todo como debe ser.

Además de estas rutinas que son las que hacen el trabajo "sucio", si quieres que sea el propio código el que decida cual de las dos rutinas usar, puedes usar la rutina genérica:
CrearPicFrm, esta función, en el caso de usar VB6, comprueba si hay algún control "muy arriba", en ese caso utiliza crearPicFrm2. Si no hay ningún control problemático que esté posicionado muy arriba, usará crearPicFrm1.

Nota:
Debido a que CrearPicFrm es la única función pública, si te decides por usar directamente cualquiera de los otros procedimientos "directamente", tendrás que cambiar la "cobertura" de Private a Public.

En las siguientes imágenes puedes ver el resultado del formulario anterior una vez usado el código "automatizador", además del aspecto que tendría sin aplicarle los temas de Windows XP.

El formulario sin usar temas de XP VB5 usando crearPicFrmVB5

Estas otras son las del VB6, en la primera captura, (la de la izquierda), no hay ningún control problemático, y se usan menos Pictures-contenedores, en la segunda, (la de la derecha), al haber un Option justo en la línea del Frame, se usa la otra rutina y se crean más Pictures-Contenedores.

VB6 usando crearPicFrm1 VB6 usando crearPicFrm2

Por supuesto que se podrá mejorar el código que "por fin" te voy a mostrar, pero... esto es lo que yo he hecho.

 

¿Cómo usar el código en mis formularios?

Para poder usar este código hay dos cosas que tendrás que hacer en cada formulario:
1- Incluir un Picture al que tendrás que llamarlo picFrm y deberá tener asignado el valor 0 (cero) en la propiedad Index.
2- Incluir esta línea en el evento Form_Load del formulario:
CrearPicFrm Me
Y si quieres usar la información devuelta por la función:
s = CrearPicFrm(Me)
Aunque la información devuelta no es necesaria para nada. Es la que se muestra en la etiqueta inferior de las imágenes mostradas.

Para terminar... sí, ya mismo te muestro el código... recordarte que debes incluir la llamada a la función del API InitCommonControls en el evento Form_Initialize del primer formulario que se vaya a mostrar en la aplicación (realmente puedes incluir la llamada en todos los formularios, pero si sólo lo incluyes en el que se use como inicio es suficiente) y crear un fichero llamado igual que la aplicación y que después del .exe debe tener la extensión .manifest.
También debes saber que no todos los controles "comunes" de Windows adoptarán el nuevo look de XP, ya que los incluidos en el control mscomctl.ocx no "entienden" de los temas de Windows XP.
Estos detalles los tienes explicados en el artículo publicado el 30 de Octubre 2002.

 

Ahora sí, aquí tienes el código para automatizar la creación de controles contenedores.

Recuerda que si el código lo vas a usar en VB5, tendrás que crear una constante de compilación llamada ESVB5 y darle un valor distinto de cero. Esta variable la puedes incluir en las propiedades del proyecto, tal como te muestro en la siguiente imagen:

 

 

El código del módulo a incluir en los proyectos:

'------------------------------------------------------------------------------
' Módulo para contener las rutinas de creación de picFrm            (19/Ago/03)
' Automatizar los formularios de VB para usar temas de XP
'
' ©Guillermo 'guille' Som, 2003
'------------------------------------------------------------------------------
Option Explicit

' constantes para la posición del picture dentro del Frame
Const picL As Long = 30&, picW As Long = 90&
' estos dos valores deben ir "emparejados" con una diferencia de 30
Const picT As Long = 190&, picH As Long = picT + 30& '225&

Public Function CrearPicFrm(ByVal frm As Form, Optional ByVal noBuscarOption As Boolean = False) As String
    ' Esta rutina comprobará si hay algún option con un valor Top inferior a picT,
    ' en ese caso llamará a crearPicFrm2.
    ' Si noBuscarOption = True, se usará crearPicFrm1 sin usar el bucle de buscar
    ' Indicar ese valor si sabemos que no hay ningún OptionButton en una parte alta de un frame
    Dim s As String
    '
#If ESVB5 Then
    's = "VB5"
    s = "Usando crearPicFrmVB5"
    crearPicFrmVB5 frm
#Else
    '
    Dim usarPicFrm2 As Boolean
    Dim c As Control
    '
    s = "VB6"
    '
    If noBuscarOption = False Then
        On Error Resume Next
        '
        For Each c In frm.Controls
            If TypeOf c.Container Is Frame Then
                If c.Top < picT Then
                    Select Case TypeName(c)
                    Case "Frame", "CommandButton", "OptionButton"
                        usarPicFrm2 = True
                        Exit For
                    End Select
                End If
            End If
        Next
        '
        Err = 0
    End If
    '
    If usarPicFrm2 Then
        s = s & " usando crearPicFrm2"
        crearPicFrm2 frm
    Else
        s = s & " usando crearPicFrm1"
        crearPicFrm1 frm
    End If
    '
#End If
    '
    ' devolver la cadena informativa con los Pictures creados y la rutina usada
    CrearPicFrm = s & ", picFrm usados: " & frm.picFrm.Count & " (0 a " & frm.picFrm.Count - 1 & ")"
End Function
'
#If ESVB5 Then
'
Private Sub crearPicFrmVB5(ByVal frm As Form)
    ' Crea los pictures que contendrá los controles                 (19/Ago/03)
    ' que estén dentro de otros frames.
    ' Versión para VB5.
    ' En VB5 sólo dan problemas los frames.
    '
    '--------------------------------------------------------------------------
    ' La única pega de este código, con diferencia al de VB6, es que los
    ' Options en VB5 se quedan transparentes.
    ' Pero si se usa el código de VB6, no se ven transparentes,
    ' aunque se usarían más controles picFrm (igual que en VB6)
    '--------------------------------------------------------------------------
    '
    Dim nPicFrm As Long
    Dim c As Control
    Dim pic As PictureBox
    '
    On Error Resume Next
    '
    nPicFrm = -1
    For Each c In frm.Controls
        If TypeOf c.Container Is Frame Then
            Select Case TypeName(c)
            ' En VB5 sólo se pondrán en pictures los Frames
            Case "Frame"
                nPicFrm = nPicFrm + 1
                If nPicFrm > 0 Then
                    Load frm.picFrm(nPicFrm)
                End If
                Set pic = frm.picFrm(nPicFrm)
                Set pic.Container = c.Container
                With pic
                    .BorderStyle = 0
                    .Visible = True
                    .Move c.Left, c.Top, c.Width, c.Height
                    '.ZOrder 0
                    .BackColor = .Container.BackColor
                End With
                Set c.Container = pic
                c.Move 0, 0
                c.ZOrder
            End Select
        End If
    Next
    '
End Sub
'
#Else
'
Private Sub crearPicFrm1(ByVal frm As Form)
    ' Usar este código si no hay ningún control "problemático" justo arriba de un Frame
    ' ya que no se vería bien.
    Dim nPicFrm As Long
    Dim c As Control
    Dim c1 As Control
    Dim pic As PictureBox
    '
    On Error Resume Next
    '
    nPicFrm = -1
    For Each c In frm.Controls
        If TypeOf c Is Frame Then
            nPicFrm = nPicFrm + 1
            If nPicFrm > 0 Then
                Load frm.picFrm(nPicFrm)
            End If
            Set pic = frm.picFrm(nPicFrm)
            Set pic.Container = c
            With pic
                .BorderStyle = 0
                .Visible = True
                .Move picL, picT, c.Width - picW, c.Height - picH
                .ZOrder 0
                .BackColor = .Container.BackColor
            End With
            For Each c1 In frm.Controls
                If c1.Container Is c Then
                    ' no tener en cuenta los picFrm
                    If Not (c1 Is pic) Then
                        ' si el control está posicionado
                        ' a menos de 180 (picT), no incluirlo en este picture
                        If c1.Top > picT Then
                            Set c1.Container = pic
                            c1.Move c1.Left - picL, c1.Top - picT
                        End If
                        c1.ZOrder
                    End If
                End If
            Next
        End If
    Next
    '
    'Debug.Print "Total picFrm en crearPicFrm= "; nPicFrm
    Err.Clear
    '
End Sub

Private Sub crearPicFrm2(ByVal frm As Form)

    '
    ' Crea los pictures que contendrá los controles                 (19/Ago/03)
    ' que estén dentro de otros frames.
    ' Versión para VB6.
    ' En VB6 dan problemas los frames, commandbutons y options.
    '
    ' Esta rutina "maneja" bien los Options, pero "consume" más pictures
    '
    Dim nPicFrm As Long
    Dim c As Control
    Dim pic As PictureBox
    '
    Dim i As Long, j As Long
    Dim minL As Long, minT As Long, minW As Long
    Dim b As Long
    Dim nOpB As Long
    Dim opB() As OptionButton
    '
    On Error Resume Next
    '
    nOpB = -1
    nPicFrm = -1
    For Each c In frm.Controls
        If TypeOf c.Container Is Frame Then
            Select Case TypeName(c)
            ' Debido a que los OptionButton no funcionarían bien    (19/Ago/03)
            ' al no estar agrupados, mejor agruparlos en un contenedor
            Case "CommandButton", "Frame"
                nPicFrm = nPicFrm + 1
                If nPicFrm > 0 Then
                    Load frm.picFrm(nPicFrm)
                End If
                Set pic = frm.picFrm(nPicFrm)
                Set pic.Container = c.Container
                With pic
                    .BorderStyle = 0
                    .Visible = True
                    .Move c.Left, c.Top, c.Width, c.Height
                    '.ZOrder 0
                    .BackColor = .Container.BackColor
                End With
                Set c.Container = pic
                c.Move 0, 0
                c.ZOrder
            Case "OptionButton"
                ' añadirlo al array
                nOpB = nOpB + 1
                ReDim Preserve opB(nOpB)
                Set opB(nOpB) = c
            End Select
        End If
    Next
    ' comprobar los OptionButtons que están en un mismo frame       (19/Ago/03)
    For i = 0 To nOpB
        If TypeOf opB(i).Container Is Frame Then
            Set c = opB(i).Container
            minT = opB(i).Top
            minW = opB(i).Left + opB(i).Width
            b = i
            nPicFrm = nPicFrm + 1
            If nPicFrm > 0 Then
                Load frm.picFrm(nPicFrm)
            End If
            Set pic = frm.picFrm(nPicFrm)
            Set pic.Container = opB(i).Container
            minL = opB(i).Left
            pic.Tag = "nPicFrm" & nPicFrm
            Set opB(i).Container = pic
            For j = i + 1 To nOpB
                If opB(j).Container Is c Then
                    If opB(j).Top < minT Then
                        minT = opB(j).Top
                    Else
                        ' b contendrá el que esté más "abajo"
                        b = j
                    End If
                    If opB(j).Left < minL Then
                        minL = opB(j).Left
                    End If
                    If opB(j).Left + opB(j).Width > minW Then
                        minW = opB(j).Left + opB(j).Width
                    End If
                    Set opB(j).Container = pic
                    opB(j).ZOrder
                End If
            Next
            With pic
                .BorderStyle = 0
                .Visible = True
                .Move minL, minT, minW - minL, (opB(b).Top + opB(b).Height - minT)
                ' ponerlo debajo, por si hubiese algún otro control "desplazado"
                ' por ejemplo en el caso de que los Options estén uno al lado del otro
                .ZOrder 1
                .BackColor = .Container.BackColor
            End With
            ' posicionar correctamente los Options
            For j = i To nOpB
                If opB(j).Container.Tag = opB(i).Container.Tag Then
                    opB(j).Top = opB(j).Top - minT
                    opB(j).Left = opB(j).Left - minL 'pic.Left
                End If
            Next
        End If
    Next
    '
    '
    'Debug.Print "Total picFrm en crearPicFrm2= "; nPicFrm
    Err.Number = 0
End Sub
'
#End If

 

Espero que con este código te sea más fácil automatizar tus formularios para que puedan usar los temas de Windows XP.

Nos vemos.
Guillermo


Aquí tienes el zip con el código del módulo BAS además de un par de formularios para que veas cómo trabaja.
También incluyo el fichero de proyecto para VB5, así como los ejecutables y los ficheros .manifest correspondientes.
Fichero: temasXPvbauto.zip 59.3 KB


ir al índice