Cómo asignar un color o una imagen al fondo de un formulario MDI principal
Por Enrique Martínez Montejo
Última revisión: 02/09/2007
 

Ni que decir tiene que a muchos programadores de Visual Basic no les hace ni pizca de gracia el color de fondo que presentan los formularios que actúan de Interfaz de Múltiples Documentos, los llamados MDI principales, ya que el color gris oscuro que presenta el fondo de la aplicación, no les parece muy atractivo del todo.

El color de fondo de los formularios MDI viene dado por el color de sistema SystemColors.AppWorkspace, que referencia al elemento Fondo de la aplicación de la ficha Apariencia del cuadro de diálogo Propiedades de Pantalla, que como es sabido, se puede acceder al mismo a través del Panel de Control, o pulsando con el botón secundario del ratón sobre el escritorio de Windows. En dicho cuadro de diálogo, el usuario puede elegir la combinación de colores del sistema que crea conveniente, o bien, modificar el color de cualquier elemento de los existentes. Por tanto, estamos ante una preferencia más del usuario, de la misma forma que si se tratara de cualquier otra preferencia regional personal, como por ejemplo, el símbolo decimal de los números o el formato de fecha corta que se utilizará.

Aquellos que me conocen por mis participaciones en los grupos de noticias públicos de Microsoft, saben que soy inflexible en cuanto a las preferencias que el usuario tiene establecidas en su sistema operativo, las cuales hay que respetar, nos gusten o no, de la misma forma que nosotros queremos que se respete nuestra decisión de haber adquirido un coche de color amarillo fosforescente, por poner un ejemplo. Por tanto, los colores que el usuario tenga definidos en su sistema, no van a ser menos que el símbolo de moneda que tenga establecido.

También, y a decir verdad, los colores definidos en una combinación de ellos, viene establecida de manera predeterminada, y en algunos casos, dichas combinaciones de colores no son de lo más acertadas, por lo que la inmensa mayoría de usuarios, bien porque son conformistas o desconocen la manera de modificar los colores, tienen definido el color gris oscuro (o aquel color formado por los componentes de Rojo, Verde y Azul equivalentes a los valores 128, 128, 128, respectivamente) como el color de fondo de la aplicación, o lo que es lo mismo, el color de fondo que tendrán los formularios que actúan como MDI principales.

Con el siguiente artículo, aparte de enseñar como asignar un color o una imagen al fondo de los formularios MDI, pretendo también explicar lo que sucede internamente cuando decidimos que un formulario cualquiera de nuestra aplicación, actúe como formulario MDI principal.

La clase MdiClient

Esta clase representa el contenedor para los formularios secundarios de Interfaz de Múltiples Documentos (MDI),  y no se puede heredar, ya que se encuentra declarada como NotInheritable. Dicha clase hereda directamente de la clase System.Windows.Forms.Control, que es la clase base para todos los controles, incluidos los formularios, que aunque no heredan directamente de Control, tienen a ésta clase dentro de su jerarquía de herencia.

Es fácil indicar que un formulario actúe de MDI principal: simplemente hay que establecer el valor True a la propiedad IsMdiContainer, bien en tiempo de diseño o en tiempo de ejecución.

Al establecer el valor True a la propiedad IsMdiContainer, automáticamente se crea una instancia de la clase MdiClient que, como control que es (porque hereda de la clase Control), se añade a la colección Controls del formulario, de igual manera que se añaden al formulario los controles TextBox o Button.

Una vez creada la instancia del objeto MdiClient, en el 90 y pico por 100 de los casos, el color de fondo del formulario cambia a gris oscuro, aunque se haya definido explícitamente el valor de la propiedad BackColor del formulario MDI. Pero en realidad, lo que sucede es que el control MdiClient se sobrepone sobre el formulario MDI propiamente dicho, de tal forma que nos hace creer que es el fondo del formulario MDI, pero no es así; es el fondo propio del control MdiClient que Visual Basic ha creado automáticamente.

Esto lo puede comprobar fácilmente, cuando abra el formulario MDI en el Diseñador de formularios de Visual Studio, y pasa sucesivamente de la ventana del diseñador a la ventana de código, y viceversa. Observará que el formulario, primero establece su color de fondo, y posteriormente se colorea de gris oscuro, si la propiedad IsMdiContainer tiene el valor True.

Llegado a este punto, ya sabemos que lo que estamos observando es el color de fondo del control MdiClient creado, o el color del valor de su propiedad BackColor, la cual hereda de la clase base Control, que por defecto toma el valor del color de sistema SystemColors.AppWorkspace, por tanto, será el valor de la propiedad BackColor de objeto MdiClient el que deberemos modificar para que ese fondo superpuesto existente en nuestro formulario MDI, se muestre con el color que nosotros deseemos asignarle.

El ejemplo que muestro a continuación enseña la manera de crear un formulario MDI principal en tiempo de ejecución, sin necesidad de asignar el valor True a la propiedad IsMdiContainer del formulario, porque en su lugar vamos a crear una instancia de la clase MdiClient que posteriormente añadiremos a la colección Controls del formulario.

Public Class CreateMDIForm

    Inherits System.Windows.Forms.Form

    Public Shared Sub Main()
        Application.EnableVisualStyles()
        Application.Run(New CreateMDIForm)
    End Sub

    Public Sub New()
        InitializeComponent()
    End Sub

    Private Sub InitializeComponent()
        Me.SuspendLayout()
        '
        'CreateMDIForm
        '

        Me.ClientSize = New System.Drawing.Size(292, 266)
        Me.Name = "CreateMDIForm"
        Me.Text = "Cómo crear un formulario MDI en tiempo de ejecución."
        Me.WindowState = System.Windows.Forms.FormWindowState.Maximized
        Me.ResumeLayout(False)
    End Sub

    Protected Overrides Sub OnLoad(ByVal e As EventArgs)

        ' Indicamos que se trata de un formulario MDI principal,
        ' y le asignamos un color de fondo cualquiera.
        '

        SetMdiContainer(Me, Color.AliceBlue)

        ' Comprobamos que el formulario es MDI principal.
        '

        MessageBox.Show(Me.IsMdiContainer.ToString)

        ' Creamos un formulario MDI secundario de demostración.
        '

        Dim frm As Form = New Form

        With frm
            .MdiParent = Me
            .Text = "Formulario MDI secundario."
           
.StartPosition = FormStartPosition.CenterScreen
            .Show()
        End With

    End Sub

    Private Sub SetMdiContainer(ByVal frm As Form, ByVal formBackColor As Color)

        ' Creamos una nueva instancia de la clase MdiClient
        '

        Dim mdi As MdiClient = New MdiClient

        ' Le asignamos el color de fondo.
        '

        mdi.BackColor = formBackColor

        ' Añadimos el control a la colección Controls
        ' del formulario.
        '

        frm.Controls.Add(mdi)

    End Sub

End Class

 

Cómo crear un formulario MDI en tiempo de ejecución

Si ha ejecutado el código fuente, creo que sobran los Comentarios. Digamos que el código fuente contenido en el procedimiento SetMdiContainer, es la manera correcta de crear un formulario MDI principal en tiempo de ejecución, si nuestra intención es tener el control sobre la apariencia del formulario.

Salvo que en el formulario haya añadido más controles, si comprueba el número de controles existentes en el formulario. observará que la propiedad Count de la colección Controls devuelve un único control, que se corresponderá con el control MdiClient que hemos creado y añadido a la colección de controles del formulario MDI principal, lo que viene a confirmar que el objeto MdiClient es otro control más que se añade al formulario para que actúe de contenedor para otros formularios secundarios MDI.

Si en lugar de crear el formulario MDI principal en tiempo de ejecución, lo ha creado en tiempo de diseño (asignando el valor True a la propiedad IsMdiContainer), también podemos referenciar el control MdiClient subyacente que Visual Basic ha creado por nosotros. Simplemente hay que recorrer la colección de controles para detectar cual de ellos es de la clase MdiClient. Pero en este supuesto, solamente podemos modificar el color de fondo del formulario, o mejor dicho, del control MdiClient, en tiempo de ejecución, tal y como muestra el siguiente ejemplo.

Protected Overrides Sub OnLoad(ByVal e As EventArgs)

    ' Obtenemos si procede el contenedor MdiClient del formulario
    '

    Dim mdi As MdiClient = GetMdiContainer(Me)

    ' Si no es un contenedor MDI abandonamos el procedimiento.
    '

    If (mdi Is Nothing) Then Return

    ' Modificamos el color de fondo
    '

    mdi.BackColor = Color.AliceBlue

    ' Creamos un formulario MDI secundario de demostración.
    '

    Dim frm As Form = New Form()

    With frm
        .MdiParent = Me
        .Text = "Formulario MDI secundario."
        .StartPosition = FormStartPosition.CenterScreen
        .Show()
    End With

End Sub

Private Function GetMdiContainer(ByVal frm As Form) As MdiClient

    ' La función comprobará si existe un control MdiClient
    ' en el formulario indicado, devolviendo la referencia
    ' al citado control.


    For Each ctrl As Control In frm.Controls
        If (TypeOf ctrl Is MdiClient) Then
            Return DirectCast(ctrl, MdiClient)
        End If
    Next

    ' No existe ningún control MdiClient.
    '

    Return Nothing

End Function

 

Cómo obtener el control MdiClient existente en un formulario MDI

Ahora que ya conoce la forma de cambiar el color de fondo de los formularios MDI principales, voy a pasar a detallar la manera de asignar una imagen de fondo a dichos formularios.

La propiedad BackgroundImage

Antes de explicar la manera de asignar una imagen para el fondo de un formulario MDI, voy a explicar brevemente la propiedad BackgroundImage.

Esta propiedad obtiene y establece la imagen de fondo que se mostrará en un control, y se encuentra implementada en la clase base Control, de la cual la hereda la clase Form. En cambio, la clase MdiClient sobrescribe la propiedad implementada en Control, aunque en definitiva, lo que hace es asignar y recuperar la imagen desde su propia clase base, tal y como se recomienda que se haga con aquellas clases derivadas que reemplazan la propiedad BackgroundImage, a fin de extender la implementación de la clase base.

En principio, simplemente podemos asignar cualquier imagen de un formato permitido a la propiedad BackgroundImage del formulario MDI. Por ejemplo, en tiempo de ejecución le asignaría un objeto tipo Image en el evento Load del formulario:

Protected Overrides Sub OnLoad(ByVal e As EventArgs)

   
' Asignamos una imagen al fondo del formulario.
    '

    Me
.BackgroundImage = Image.FromFile("C:\Mis imagenes\Imagen1.jpg")

End Sub

Pero llegado este momento, el problema surge cuando desee mover los formularios secundarios dentro del área cliente del formulario MDI principal, sobre todo si la imagen de fondo es de un tamaño considerable. Observará que le cuesta trabajo mover los formularios, dejando éstos su rastro a través del fondo del propio formulario MDI. Esto se debe al valor de la propiedad BackgroundImageLayout, que por defecto tiene el valor ImageLayout.Tile, con lo cual, la imagen se dispone en mosáico por toda el área cliente del formulario.

Para eliminar esa cierta pesadez, puede aumentar el rendimiento estableciendo a la propiedad BackgroundImageLayout cualquier valor distinto a Tile:

Protected Overrides Sub OnLoad(ByVal e As EventArgs)

   
' Asignamos una imagen al fondo del formulario.
    '

    Me
.BackgroundImage = Image.FromFile("C:\Mis imagenes\Imagen1.jpg")

    ' Centramos la imagen dentro del rectángulo cliente del control.
    '

    Me.BackgroundImageLayout = ImageLayout.Center

End Sub

¡Ahora, sí! Los formularios secundarios se mueven con más agilidad.

Dibujar el fondo de un formulario MDI con un archivo gráfico

Para no tener que acordarnos de asignar un valor adecuado a la propiedad BackgroundImageLayout del formulario, otra alternativa a establecer una imagen al fondo del formulario, pasaría por dibujar el propio fondo del formulario con la imagen de cualquier archivo gráfico, en el evento Paint del formulario.

En principio, ésta alternativa sólo sería factible para los formularios normales y corrientes, es decir, aquellos que no sean MDI principales. En éstos últimos, aunque también se puede dibujar directamente sobre el fondo del formulario, no se vería absolutamente nada, porque como he indicado anteriormente en el apartado la clase MdiClient, el control MdiClient subyacente se superpone sobre el fondo del propio formulario MDI, y observaríamos el típico color gris oscuro característico, o cualquier otro color que se le haya asignado a la propiedad BackColor del control MdiClient. Nuevamente, la solución pasaría por detectar el control MdiClient subyacente, y dibujar la imagen sobre su propio fondo, utilizando para ello el evento Paint del objeto MdiClient:

Public Class CreateMDIForm

    Inherits System.Windows.Forms.Form

    Private WithEvents mdi As MdiClient
    Private img As Image

    Public Shared Sub Main()
        Application.EnableVisualStyles()
        Application.Run(New CreateMDIForm)
    End Sub

    Public Sub New()
        InitializeComponent()
    End Sub

    Private Sub InitializeComponent()
        Me.SuspendLayout()
        '
        'CreateMDIForm
        '

        Me.ClientSize = New System.Drawing.Size(626, 266)
        Me.Name = "CreateMDIForm"
        Me.Text = "Cómo crear un formulario MDI en tiempo de ejecución."
        Me.WindowState = System.Windows.Forms.FormWindowState.Maximized
        Me.ResumeLayout(False)

    End Sub

    Protected Overrides Sub OnLoad(ByVal e As EventArgs)

        ' Indicamos que el formulario actuará de MDI principal.
        '

        Me.IsMdiContainer = True

        ' Obtenemos si procede el contenedor MdiClient del formulario
        '

        mdi = GetMdiContainer(Me)

        ' Si no es un contenedor MDI abandonamos el procedimiento.
        '

        If (mdi Is Nothing) Then Return

        ' Creamos el objeto Image.
        '

        img = Image.FromFile("C:\Mis imagenes\Imagen1.jpg")

        ' Creamos un formulario MDI secundario de demostración.
        '

        Dim frm As Form = New Form

        With frm
            .MdiParent = Me
            .Text = "Formulario MDI secundario."
            .StartPosition = FormStartPosition.CenterScreen
            .Show()
        End With

    End Sub

    Protected Overrides Sub OnFormClosed( _
            ByVal e As System.Windows.Forms.FormClosedEventArgs)

        ' Destruimos el objeto Image.
        '

        img.Dispose()

    End Sub

    Private Sub mdi_Paint(ByVal sender As Object, _
                          ByVal e As System.Windows.Forms.PaintEventArgs) _
                          Handles mdi.Paint

        ' Referenciamos el objeto Graphics.
        '

        Dim grf As Graphics = e.Graphics

        ' Dibujamos dentro del área cliente del objeto MdiClient
        ' con el objeto Image correspondiente.
        '

        grf.DrawImage(img, 0, 0, mdi.Width, mdi.Height)

        ' También se puede dibujar sobre el área cliente del formulario.
        '
        ' grf.DrawImage(img, 0, 0, Me.Width, Me.Height)


        ' Destruimos el objeto Graphics.
        '

        grf.Dispose()

    End Sub

    Private Function GetMdiContainer(ByVal frm As Form) As MdiClient

        ' La función comprobará si existe un control MdiClient
        ' en el formulario indicado, devolviendo la referencia
        ' al citado control.

        For Each ctrl As Control In frm.Controls
            If (TypeOf ctrl Is MdiClient) Then
                Return DirectCast(ctrl, MdiClient)
            End If
        Next

        ' No existe ningún control MdiClient.
        '

        Return Nothing

    End Function

End Class

 

Cómo dibujar el fondo de un formulario MDI con un archivo gráfico

Aplicando la técnica descrita en el ejemplo anterior, no es necesario asignar valores algunos a las propiedades BackgroundImage y BackgroundImageLayout, aunque sí es necesario declarar una variable objeto con eventos que referencie al objeto MdiClient subyacente existente en todos los formularios MDI principales. Será en el evento Paint del objeto MdiClent declarado, donde dibujaremos el fondo de la aplicación utilizando para ello la imagen seleccionada, en lugar de hacerlo en el evento Paint del formulario MDI, porque aquí se dibujaría el fondo del formulario, pero nunca se visualizaría.

En fin. Espero que el artículo le haya servido para conocer un poco más los entresijos existentes en los formularios MDI principales, de las aplicaciones desarrolladas con Visual Basic .net.

 

Otros tips de interés:

 

Otros enlaces de interés:

Índice de la colección de ejemplos de las clases del marco de trabajo de .NET


Enrique Martínez Montejo - 2007

NOTA: La información contenida en este artículo, así como el código fuente incluido en el mismo, se proporciona COMO ESTÁ, sin garantías de ninguna clase, y no otorga derecho alguno. Usted asume cualquier riesgo al poner en práctica, utilizar o ejecutar lo explicado, recomendado o sugerido en el presente artículo.

NOTE: The information contained in this article and source code included therein, is provided AS IS without warranty of any kind, and confers no rights. You assume any risk to implement, use or run it explained, recommended or suggested in this article.