Curso Básico de Programación
en Visual Basic

 

Entrega Treinta: 2/May/99
por Guillermo "guille" Som

Si quieres linkar con las otras entregas, desde el índice lo puedes hacer

 

¿Te extraña ver otra entrega tan pronto?
Aprovecha y no te quejes... que ya llegarán nuevas "lagunas"; aunque puede que no tantas... depende... o dependerá del tiempo, ganas, inspiración y hasta motivación... que hasta para escribir entregas sobre el Visual Basic hay que estar motivado e inspirado... que no es tan sencillo esto de ponerse a escribir cuatro chorradillas sobre un lenguaje... y sino, que se lo digan al Guille...
En serio, esto de explicar, en la medida de lo posible, las cosas de forma que hasta se entienda, es algunas veces complicado... aunque, dicho sea de paso, algunas veces no lo consigo... pero... eso es lo que hay, y como por ahora no recibo muchas quejas, pues... me imagino que la cosa queda más o menos clara... aunque se que no todo lo claro que a algunos le gustaría...

Quiero aclarar un par de cosillas, para que no te entre la desesperación si no das pie con bola...
Desde hace algunas entregas, la versión de VB que hay que usar es como mínimo la 4, (preferiblemente de 32 bits), aunque cada vez voy dirigiéndome más a la versión 5, giro que sobre todo se notará cuando empecemos con el tema de los módulos de clases y esas cosillas orientadas a objetos; así que si tienes un VB anterior a la versión 5, ve planteándote el conseguir una nueva versión... o te perderás algunas cosillas interesantes. El que avisa...

Como viene siendo habitual desde hace un par de entregas, esta también está "tecleada" por la "güena" de Encarni, que ya ha terminado con los manuscritos o garabatos que le di... así que, si tardo en publicarlas, la culpa será sólo mía...

Hablando de publicar... de vez en cuando recibo una petición de "autorización" para usar estas entregas en diferentes sitios, tanto de Internet como para "revistas" de colegios y esas cosillas, gracias a los que lo hacéis y lo único que pido es que se "respete" el contenido, es decir TODO el contenido, incluido los "desvaríos" y chorradillas, como estas, que escribo... si me entero de que no lo hacen... me voy a enfadar.

Ya está bien de tantas chorradas, así que vamos a seguir con los menús que el tiempo apremia.


Antes de ver el código "operativo" de las opciones que añadimos en la entrega anterior, vamos a seguir aprendiendo cosas de los menús, para tener los conceptos más claros, que al fin y al cabo es lo que interesa.

 

Menús que contienen menús

 

Aunque en nuestro pequeño editor no lo vamos a usar, veremos cómo se crean opciones de menús que a su vez muestran otros menús.

Para ello vamos a crear otra opción en el menú ficheros para poder imprimir, pero que nos mostrará un par de opciones: configurar la impresora además de la opción imprimir. El aspecto de este menú sería el siguiente:

img30_01.gif (3377 bytes)

Para poder conseguir un menú dentro de otro menú no hay que hacer nada especial… simplemente usaremos lo que hasta ahora hemos visto: indentar opciones.

Ya viste que cuando queríamos mostrar las opciones de uno de los menús principales, simplemente "desplazábamos" las siguientes opciones hacia la derecha… pues esto mismo es lo que hay que hacer… desplazar las nuevas opciones y el Visual Basic sabrá que tiene que mostrarlas en otro menú.

Por tanto, cada vez que necesitamos mostrar un menú al hacer "click" en una de las opciones de cualquier menú, desplazaremos esas opciones hacia la derecha.

Vamos a verlo de forma práctica:

Muestra el formulario, haz que se muestre el "diseñador de menús", posiciónate en la opción "Salir", pulsa en el botón insertar y escribe en la descripción, (o caption), &Imprimir…, en el nombre del menú escribe mnuFicImp, pulsa en siguiente para que acepte lo que hemos escrito, para insertar una nueva línea, pulsa en el botón insertar y escribe en el caption: &Selecionar impresora… y mnuFicImpSelec en el nombre del menú; antes de pulsar en siguiente, dale al botón con la flecha hacia la derecha, en esta ocasión tendremos seis puntos suspensivos delante de esta opción, (me refiero a la lista de abajo), esto indicará que está dos niveles hacia la derecha, es decir que tenemos un menú dentro de otro menú.

Ahora pulsa en los botones siguiente e insertar y escribe &Imprimir y mnuFicImpImp, ya sabes dónde, para que esté en el mismo nivel que Seleccionar Impresora, tendrás que pulsar en la flecha que señala a la derecha... para que esté en el mismo nivel de menús.
Si por casualidad ves que salen más puntos suspensivos de la cuenta... pulsa en el botón con la flecha a la izquierda para quitar la indentación que le hayas dado de más.

Cierra el diseñador de menús.
Muestra el formulario, si pulsas en el menú Ficheros y a continuación en Imprimir..., verás que tenemos un nuevo menú, en este caso con las dos opciones que hemos añadido, (ver la figura anterior).
Así de fácil se crean los sub-menús o menús que se muestran al seleccionar una opción de un menú.

 

Nota:
Cuando tienes opciones ya creadas y quieres insertar nuevas opciones, hay que usar el botón Insertar, pero la "indentación" que muestra es la misma que tiene el menú en el que nos posicionamos antes de insertar, por tanto tendrás que usar las flechas de indentación para ajustar los niveles de menús.

 

Mostrar menús emergentes (popupmenús)

Antes de ver el código para esta utilidad, que realmente es lo de menús, ya que la intención de la misma es ver cómo crear y manejar menús, vamos a ver cómo hace que se muestre un menú emergente: de esos que se suelen mostrar al pulsar el botón derecho del ratón.

Si estás usando Windows 95 o superior, habrás notado que las cajas de texto ya incluyen las opciones habituales de edición, nosotros no tendremos necesidad de codificar nada para tener disponibles esa característica. Pruébalo. Ejecuta con F5 el programa y pulsa en el textbox, verás que se muestra un menú emergente. Pero ese menú es el del sistema, si queremos que se muestre el nuestro habrá que escribir un par de líneas de código. ¿Dónde? En el evento MouseDown del textbox, por tanto escribe esto:


Private Sub txtEditor_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
    ' Si pulsamos el botón derecho... (vbRightButton = 2)
    If Button = vbRightButton Then
        ' Mostrar el menú de Edición
        PopupMenu mnuEdit
        ' Si queremos mostrar en negrita uno de los menús,
        ' lo indicaremos en el quinto parámetro
        'PopupMenu mnuEdit, , , , mnuEditor(cEdCopiar)
    End If
End Sub

Ya vimos que uno de los parámetros del evento MouseDown estaba el de saber que botón se está pulsando, por tanto usaremos ese parámetro, que es Button, para saber si es el botón derecho, (vbRightButton que tiene un valor igual a 2), y si es así, mostramos nuestro propio menú. Para ello usamos la instrucción PopupMenu, en el primer parámetro indicamos el nombre del menú que contiene las opciones que queremos mostrar, del resto de los parámetros, en principio sólo nos podría interesar el último, que indica que menú debe mostrarse seleccionado.

En nuestro caso el menú a mostrar es mnuEdit que es del que cuelgan las opciones de este menú.

Con PopupMenu se puede usar cualquier menú, incluso si no está visible. Esa característica se suele usar cuando queremos mostrar menús en nuestra aplicación pero queremos que se muestren solamente como menús emergentes, en otra ocasión veremos esto.

Una vez que un menú emergente se ha mostrado funciona de igual manera que si no fuese emergente… se que es "lógico", pero lo aclaro por si las moscas… es decir cuando se selecciona una opción, se ejecuta el código que hayamos escrito en el evento click de esa opción.

Un detalle, puede que al pulsar el botón derecho en el textbox, no se muestre el menú emergente, casi con toda seguridad tendrás que pulsarlo por segunda vez, si no recuerdo mal, esto no ocurría con el VB4...

 

 

Teclas de acceso rápido en los menús.

Ya te comenté que se pueden asignar teclas de acceso rápido a las opciones del menú, no todas, pero casi… por ejemplo, no se pueden asignar F10 ni Alt+F4, pero eso no es inconveniente, ya que se pueden detectar de otra forma, aunque ahora mismo no es lo que nos interesa…

¿Cómo se añaden estos accesos rápidos? Pues… con el diseñador de menús… aunque también se puede hacer mediante código. Pero vamos a usar el "diseñador".
A estas alturas ya debes saber cómo mostrar ese dialogo… ¿verdad? Pues muéstralo y vamos a añadir inicialmente ese tipo de accesos a las opciones del menú de edición.

Selecciona la opción "Cortar", en el cuadro de diálogo hay una lista con las teclas que podemos usar, (en inglés es Shorcut), selecciona Ctrl+X de la lista desplegable. Haz lo propio con Copiar (Ctrl+C), Pegar (Ctrl+V) y Seleccionar todo (Ctrl+A), cierra el cuadro de diálogo y pulsa en el menú edición, verás que se han añadido al Caption esas teclas, pero no sólo están "mostradas", sino que si ejecutas el programa verás que están operativas y que no es necesario hacer nada extra... simplemente pulsar esas combinaciones de teclas y se ejecutará el código asociado a esa opción del menú.

img30_02.gif (7520 bytes)

Para comprobarlo, vamos a codificar la opción de seleccionar todo:
Muestra el form y selecciona el menú edición, pulsa en cualquiera de las opciones y se mostrará la ventana de código, (también podrías haberla seleccionado directamente, pero así parece que ibas a hacer otra cosa, je, je ), el código en este evento será el siguiente:

Private Sub mnuEditor_Click(Index As Integer)
    ' Cuando se selecciona un elemento del menú Edición
    ' se entra en este evento, el índice nos indicará
    ' el elemento seleccionado.
    ' Existen unas constantes para usarlas en lugar del número,
    ' por si añadimos o quitamos algunos
    Select Case Index
    Case cEdDeshacer
        '
    Case cEdCortar
        '
    Case cEdCopiar
        '
    Case cEdPegar
        '
    Case cEdSeleccionarTodo
        With txtEditor
            .SelStart = 0
            .SelLength = Len(.Text)
        End With
    Case cEdBuscar
        '
    Case cEdBuscarSig
        '
    Case cEdReemplazar
        '
    End Select
End Sub

Ejecuta la aplicación y escribe varias líneas en el texbox, pulsa la tecla control y sin soltarla pulsa la tecla a, (esto como habrás observado en otras ocasiones, se simplifica diciendo: pulsa Ctrl+A), ¡y ya está! ¡todo el texto seleccionado!.

Por supuesto que también puedes ir al menú edición y clickear en la opción seleccionar todo.

Te explico un poco el código que hace que seleccione todo el texto:
Le indicamos que la posición de inicio del texto seleccionado sea la primera posición: .SelStart = 0
Le indicamos que el texto seleccionado sea de igual longitud que todo el texto que hay escrito: .SelLength = Len(.Text)
Si no hubiésemos usado el With txtEditor... End With, tendríamos que haber especificado el nombre del objeto, el código sería este otro:

'
    Case cEdSeleccionarTodo
        txtEditor.SelStart = 0
        txtEditor.SelLength = Len(txtEditor)

Ahora vamos a codificar un par de opciones del menú edición, lo vamos a hacer con código en Visual Basic; de eso se trata ¿no?, aunque podríamos apoyarnos en llamadas al API de Windows, es decir: usar funciones propias del Windows para hacerlo, pero por ahora lo vamos a dejar, entre otras cosas para que sepas como funciona y sobre todo como se accede al portapapeles, (ClipBoard), desde el Visual Basic.

 

Como acceder al portapapeles (ClipBoard)

 

Las opciones que estarán relacionadas con el portapapeles son: Cortar, Copiar y Pegar; antes de codificar estas opciones en el evento mnuEditor_Click, veamos cómo manipular el clipboard.

Los métodos que vamos a usar de este objeto son:

Clear para borrar el contenido del portapapeles

GetText para recuperar el texto que haya

SetText para asignar un texto

GetFormat, para saber si el tipo de formato está disponible en el portapapeles los tipos de formatos se averiguan con las constantes predefinidas en el VB5:

vbCFLink vínculo DDE

vbCFText formato texto, el que a nosotros nos interesa

vbCFBitmat formato bmp

vbCFMetafile formato wmf (metafile)

vbCFDib mapa de bits independientes del dispositivo, habitualmente todos los gráficos soportan este formato

vbCFPalette paleta de colores

vbCFRtf formato RTF (rich text)

GetData obtener los datos del portapapeles

SetData Asignar datos al portapapeles

Por ahora vamos a trabajar con los cuatro primeros métodos:
Con GetFormat, sabremos si hay algún texto en el portapapeles, en caso de que así sea, al mostrar el menú, habilitaremos la opción Pegar y si no hay texto la deshabilitaremos. De igual manera habilitaremos o no las opciones de Cortar y Copiar, pero en este caso comprobaremos si hay texto seleccionado

¿Dónde se hace esto?

Todas estas comprobaciones se hacen en el evento click del menú edición, (del que "cuelgan" las otras opciones), ya que este evento se dispara cuando pulsamos en él y es entonces cuando de muestran las opciones.

Private Sub mnuEdit_Click()
    ' Por defecto, las opciones están deshabilitadas
    mnuEditor(cEdCortar).Enabled = False
    mnuEditor(cEdCopiar).Enabled = False
    mnuEditor(cEdPegar).Enabled = False

    ' Comprobamos si hay texto en el portapapeles
    If Clipboard.GetFormat(vbCFText) Then
        ' Hay texto, habilitamos la opción de pegar
        mnuEditor(cEdPegar).Enabled = True
    End If

    ' Si hay texto seleccionado, habilitamos Cortar y Copiar
    If txtEditor.SelLength Then
        mnuEditor(cEdCortar).Enabled = True
        mnuEditor(cEdCopiar).Enabled = True
    End If
End Sub

Cuando muestres el menú de edición, seguramente "notarás" cómo los item cambian de estado… pero eso es lo menos importante, ya que lo que interesa es que estén habilitados los que tengan que estarlo.

Ahora que hemos habilitado y/o deshabilitado las distintas opciones de edición, vamos a usarlas.

Según vimos, el menú de edición está en un array y por tanto comparten el mismo nombre de menú y según el índice, hará referencia a uno u otro, para acceder a las distintas opciones, usamos constantes...

'
    '...
    Case cEdCortar
        ' Copiamos el texto seleccionado en el portapapeles
        Clipboard.SetText txtEditor.SelText
        ' y lo quitamos del textbox
        txtEditor.SelText = ""
    Case cEdCopiar
        ' Simplemenente copiamos el texto seleccionado en el portapapeles
        Clipboard.SetText txtEditor.SelText
    Case cEdPegar
        ' Ponemos en el textbox el texto que haya en el portapapeles
        txtEditor.SelText = Clipboard.GetText()
    '...

Para el caso de deshacer, podemos usar el API de Windows, realmente para todas estas opciones también, e incluso no necesitamos ni siquiera codificar nada, ya que el propio Windows se encarga de hacerlo… pero lo pongo aquí para que sepas hacerlo ya que puedes añadir otras opciones, como buscar, reemplazar, etc.

 

Usar el API, para deshacer

La función del API para este menester es: SendMessage, la declaración es esta: (aunque pueden existir otras con diferentes parámetros)

Nota:
Esta declaración y el valor de las constantes que voy a dar son para Windows de 32 bits, si quieres saber los valores y la declaración para 16 bits, en la sección API de mis páginas lo tienes... (esto es por si estás leyendo esta entrega desde otro sitio distinto al de mis páginas)

Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
    (ByVal hWnd As Long, ByVal wMsg As Long, _
    ByVal wParam As Long, lParam As Any) As Long

Ahora necesitamos unas constantes para las distintas "tareas":
Para saber si se puede deshacer:

'
Private Const EM_CANUNDO = &HC6
Private Const EM_UNDO = &HC7

Y para el resto de las opciones que hemos visto sería de esta forma:

Private Const WM_CUT = &H300
Private Const WM_COPY = &H301
Private Const WM_PASTE = &H302
Private Const WM_CLEAR = &H303
Private Const WM_UNDO = &H304

Para saber si se puede deshacer:

If SendMessage(txtEditor.hWnd, EM_CANUNDO, 0&, ByVal 0&) Then
    mnuEditor(cEdDeshacer).Enabled = True
End If

Fíjate que el último parámetro tiene "antepuesto" al valor CERO la palabra ByVal, esto es porque en la declaración de la función se ha especificado como As Any.

Para deshacer, también usamos la misma función del API, pero en este caso le indicamos que "deshaga":

'
Case cEdDeshacer
    Call SendMessage(txtEditor.hWnd, WM_UNDO, 0, ByVal 0&)

Usar el API para Cortar, Copiar y Pegar:

Para los casos de Cortar, Copiar y Pegar, se usarían los "mensajes" apropiados, es decir: WM_CUT, WM_COPY y WM_PASTE.
En todos los casos, se usa el hWnd del txtEditor para indicarle a la función de Windows, que debe operar sobre esa "ventana", por tanto todas las operaciones se realizarán en la ventana de la cual indicamos el "manejador" de ventanas (handle), que el VB nos proporciona mediante la propiedad de sólo lectura hWnd. Todos los controles que "actúan" como ventanas, tienen la propiedad hWnd.

Como puedes comprobar no tiene nada extraño ni raro, es decir que esto del API de Windows es como todo: para saber lo que hay que hacer hay que saber cómo hacerlo… y esa es la intención de este cursillo, intentar enseñarte a hacer cosas con el Visual Basic... e incluso con cosas que no es Visual Basic propiamente dicho...

Vamos a ponerlo todo junto y veamos el código que tenemos hasta ahora en el formulario:

'
'------------------------------------------------------------------------------
' Editor para el curso básico                                       (19/Oct/98)
' Entrega 29, publicada el 25/Abr/99
' Entrega 30, publicada el 02/May/99
'
' ©Guillermo 'guille' Som, 1998-99
'------------------------------------------------------------------------------
Option Explicit

' Constantes para el menú de Edición.
' Los valores se corresponden con el índice de mnuEditor
Const cEdDeshacer = 0
Const cEdCortar = 2
Const cEdCopiar = 3
Const cEdPegar = 4
Const cEdSeleccionarTodo = 6
Const cEdBuscar = 8
Const cEdBuscarSig = 9
Const cEdReemplazar = 10

' Función del API de Windows de 32 bits de múltiple uso
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
    (ByVal hWnd As Long, ByVal wMsg As Long, _
    ByVal wParam As Long, lParam As Any) As Long

' Constantes para saber si se puede deshacer y deshacer
Private Const EM_CANUNDO = &HC6
Private Const EM_UNDO = &HC7

' Las constantes para cortar, copiar, pegar, etc
Private Const WM_CUT = &H300
Private Const WM_COPY = &H301
Private Const WM_PASTE = &H302
Private Const WM_CLEAR = &H303
Private Const WM_UNDO = &H304

Private Sub Form_Resize()
    '------------------------------------------------------------------
    'NOTA:
    '   ScaleWidth y ScaleHeight devuelven el tamaño "interno"
    '   del form o control.
    '   Width y Height devuelven el tamaño externo del form o control.
    '------------------------------------------------------------------

    ' Sólo cuando no esté minimizado el formulario
    If WindowState <> vbMinimized Then
        ' Si esto no funciona, que de seguro no funcionará,
        ' tendrás que ponerlo en el picStatus_Resize
        'lblStatus.Width = picStatus.ScaleWidth - 60

        ' Ajustar el tamaño del TextBox
        'txtEditor.Width = ScaleWidth
        ' Ajustar el alto, hay que tener en cuenta el alto
        ' del picture
        'txtEditor.Height = ScaleHeight - picStatus.Height
        ' Posicionar el textBox en la parte superior izquierda
        'txtEditor.Move 0, 0

        ' Esto es lo mismo que lo anterior
        txtEditor.Move 0, 0, ScaleWidth, ScaleHeight - picStatus.Height
    End If
End Sub

Private Sub mnuEdit_Click()
    ' Por defecto, las opciones están deshabilitadas
    mnuEditor(cEdDeshacer).Enabled = False
    mnuEditor(cEdCortar).Enabled = False
    mnuEditor(cEdCopiar).Enabled = False
    mnuEditor(cEdPegar).Enabled = False

    ' Para saber si se puede deshacer:
    If SendMessage(txtEditor.hWnd, EM_CANUNDO, 0&, ByVal 0&) Then
        mnuEditor(cEdDeshacer).Enabled = True
    End If

    ' Comprobamos si hay texto en el portapapeles
    If Clipboard.GetFormat(vbCFText) Then
        ' Hay texto, habilitamos la opción de pegar
        mnuEditor(cEdPegar).Enabled = True
    End If

    ' Si hay texto seleccionado, habilitamos Cortar y Copiar
    If txtEditor.SelLength Then
        mnuEditor(cEdCortar).Enabled = True
        mnuEditor(cEdCopiar).Enabled = True
    End If
End Sub

Private Sub mnuEditor_Click(Index As Integer)
    ' Cuando se selecciona un elemento del menú Edición
    ' se entra en este evento, el índice nos indicará
    ' el elemento seleccionado.
    ' Existen unas constantes para usarlas en lugar del número,
    ' por si añadimos o quitamos algunos
    Select Case Index
    Case cEdDeshacer
        Call SendMessage(txtEditor.hWnd, WM_UNDO, 0, ByVal 0&)
    Case cEdCortar
        ' Copiamos el texto seleccionado en el portapapeles
        Clipboard.SetText txtEditor.SelText
        ' y lo quitamos del textbox
        txtEditor.SelText = ""

        ' Si usamos el API:
        'Call SendMessage(txtEditor.hWnd, WM_CUT, 0, ByVal 0&)

    Case cEdCopiar
        ' Simplemenente copiamos el texto seleccionado en el portapapeles
        Clipboard.SetText txtEditor.SelText

        ' Si usamos el API:
        'Call SendMessage(txtEditor.hWnd, WM_COPY, 0, ByVal 0&)

    Case cEdPegar
        ' Ponemos en el textbox el texto que haya en el portapapeles
        txtEditor.SelText = Clipboard.GetText()

        ' Si usamos el API:
        'Call SendMessage(txtEditor.hWnd, WM_PASTE, 0, ByVal 0&)

    Case cEdSeleccionarTodo
        txtEditor.SelLength = txtEditor.SelLength = Len(txtEditor)
        With txtEditor
            .SelStart = 0
            .SelLength = Len(.Text)
        End With
    End Select
End Sub

Private Sub mnuFicSalir_Click()
    ' Terminar el programa
    Unload Me
End Sub

Private Sub picStatus_Resize()
    ' Sólo cuando no esté minimizado el formulario
    If WindowState <> vbMinimized Then
        ' Aquí se ajustará el tamaño del label
        ' cuando cambie el del picStatus
        lblStatus.Width = picStatus.ScaleWidth - 60
    End If
End Sub

Private Sub txtEditor_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
    ' Si pulsamos el botón derecho... (vbRightButton = 2)
    If Button = vbRightButton Then
        ' Mostrar el menú de Edición
        PopupMenu mnuEdit
        ' Si queremos mostrar en negrita uno de los menús,
        ' lo indicaremos en el quinto parámetro
        'PopupMenu mnuEdit, , , , mnuEditor(cEdCopiar)
    End If
End Sub

Ya sólo nos queda codificar las opciones de Abrir y Guardar, que veremos en la siguiente entrega y algunas opciones como buscar, reemplazar, que no se si veremos en la siguiente o en otra...

Nos vemos
Guillermo

Nerja, 14/Ene/99 21:10 (el original)


 
entrega anterior ir al índice siguiente entrega

Ir al índice principal del Guille