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