volver al índice de vb.net

PortapapelesVB

Publicado el 15/Ene/2002



Varios ejemplos con cosas cómo:
Arrastrar y soltar ficheros en un formulario,
Seleccionar ficheros con el OpenFileDialog,
Leer ficheros de texto y asignarlo a un TextBox Multiline,
Leer ficheros de imágenes y asignarlos a un PictureBox,
Copiar texto y/o imágenes en el portapapeles,
Pegar una imagen desde el portapapeles,
Mostrar los tipos de datos que hay en el portapapeles,
Usar una función del API para convertir nombres cortos en nombres largos,
Usar TabControl con scroll virtual (en el caso del contenedor de la imagen),
Uso de Anchor para autoajustar los controles cuando se cambia el tamaño del formulario.
(no se si se me olvida alguna otra cosa...)
 

Este mismo ejemplo también está disponible en C#


Como has podido comprobar, son un montón de cosas las que te muestro en el código del PortapapelesVB, no sólo lo que el título indica, (por eso he puesto esa lista de cosillas que te voy a mostrar y explicar con esta aplicación creada en Visual Studio.NET beta 2 en español, que es la versión "pública" que hay por ahora... salvo que hayas conseguido la RC1, pero como esa versión está más limitada... he comprobado que esto que te cuento funcione en la Beta 2.

En esta aplicación se usa una función del API para convertir un nombre corto en uno largo, la declaración usada es de la función para UNICODE, por tanto, si estás usando el Windows 98 o el Windows ME, tendrás que usar la versión ANSI, si no lo haces... no te funcionará lo de convertir el nombre corto en uno largo y por tanto parecerá que no funciona lo de arrastrar/soltar ficheros en el formulario.
Estás sobre aviso.

También te muestro este mismo código en C#, (en el link que hay un poco más arriba).
Te diré que inicialmente creé este ejemplo con el VB.NET.
Después añadí un nuevo proyecto C# y copié todos los controles del formulario de VB y los pegué en el de C#.
Lo mismo hice con el código, aunque primero creé los eventos correspondientes a cada botón, etc.
Después simplemente copié el código de cada evento y lo pegué... y por supuesto tuve que "convertirlo" para que funcionara en C#, pero los cambios simplemente fueron necesarios para adaptar la sintaxis del VB a la de C#, ya que no es igual... como te imaginarás, pero gracias al uso del .NET Framework, los cambios son mínimos, ya que el funcionamiento real en VB y C# es idéntico, con la excepción de la mencionada sintaxis.


Veamos el aspecto del formulario en tiempo de diseño, para que te hagas una idea de los controles usados.
 


Figura1: El formulario en tiempo de diseño


Los controles usados son:
No te los voy a enumerar, ya que puedes verlo en el código... sólo decirte que en el control Tab, a diferencia de lo que ocurre con ese mismo control del VB6, no es necesario usar contenedores, ya que en el mismo TAB se puede "pegar" el control que quieras en cada solapa.
En la primera está el TextBox2, que es Multiline y tiene asignado el tipo de letras a Courier New 8 puntos.
En la segunda solapa, está el PictureBox1, el cual tiene asignada la propiedad SizeMode asignada a Autosize, para que la imagen se pueda ver completa.
Para que la imagen se pueda ver al completo, en caso de que sea más grande que el tamaño visible del PictureBox, he asignado la propiedad AutoScroll de la solapa contenedora, con idea de que se muestren barras de desplazamiento si la imagen es más grande de lo que se muestra.

Quiero puntualizar que es muy importante que los valores de "anclaje" del PictureBox no estén modificados para que se adapte al tamaño del formulario.
Te digo esto, porque inicialmente el PictureBox no estaba incluido en la solapa, (ya que el TAB lo he añadido después), y para que se adaptara al tamaño del formulario, le indiqué que el PictureBox se adaptara al tamaño del mismo mediante la propiedad Anchor.
Después inserté el PictureBox dentro de la solapa, pero NO FUNCIONABA lo de AutoScroll... y yo probando y requeteprobando... incluso llegué a pensar que era un BUG... al final se me ocurrió quitarle los valores Anchor que le asigné al PictureBox y ¡todo funcionó bien!
Ya que lo sé... me parece una chorrada, pero no sabes el calentamiento de coco que me dio... en fin... esa es una de las ventajas que otros "probemos" las cosas y las expliquemos... ¿verdad?

Todos los controles están asignados de una forma u otra para que se "anclen" al formulario, es decir, que se auto ajusten al nuevo tamaño que le demos al formulario. Cosa que se consigue mediante la propiedad Anchor de cada control.

Te digo los valores asignados a cada control (al final sí que voy a relacionar los controles... je):
El control que aquí no esté relacionado es que tiene los valores por defecto, que creo que sólo es el Label de arriba y el PictureBox.

TextBox1: Top, Left, Right
TextBox2: Los cuatro (está contenido en el Tab1)
btnExaminar, btnMostrar, btnCopiarText, btnCopiarImg, btnPegarImg: Top, Right
btnSalir: Bottom, Right
lblInfo: Bottom, Left, Right
TabControl1: los cuatro.

Otras propiedades asignadas:
El formulario, para permitir que acepte ficheros soltados (Drag&Drop), debe tener la propiedad AllowDrop = True.

Si se me ha escapado alguna... puedes comprobarlo en el código del formulario, pero... ¡no me regañes!

Te explico ahora el código usado:
 

Aceptar ficheros soltados en el formulario (Drag & Drop):

Como ya te he comentado, hay que asignar la propiedad AllowDrop = True del formulario.
De esta forma cualquier cosa que se suelte en el formulario será detectado por el evento DragDrop del formulario.
Al contrario de lo que ocurría en VB6, en VB.NET (o en C#) no es necesario escribir código en cada uno de los controles del formulario, sólo hay que hacerlo en el formulario y, aunque se suelte en cualquiera de los controles, lo detectará el formulario.
Si has usado Drag & Drop en VB5/6 sabrás que para detectar que se soltaba un fichero, había que poner código de "intercepción" en cada control, ya que si no lo dejábamos "caer" exactamente en el formulario, no funcionaba...

Para que al arrastrar el fichero que queremos dejar en el formulario se muestre el icono correspondiente, en este caso el de pegar, hay que indicar lo siguiente en el evento DragOver:

Private Sub Form1_DragOver(ByVal sender As Object, ByVal e As DragEventArgs) Handles MyBase.DragOver
    ' Cuando se está arrastrando en el formulario
    e.Effect = DragDropEffects.Copy
End Sub

Una vez que se ha soltado, hay que detectarlo en el evento DragDrop del formulario, aquí te muestro el código:

Private Sub Form1_DragDrop(ByVal sender As Object, ByVal e As DragEventArgs) Handles MyBase.DragDrop
    ' Cuando se suelta un fichero o lo que se está arrastrando, en el formulario
    ' Sólo vamos a permitir ficheros
    '
    ' Comprobar si el tipo de datos es el que queremos
    If e.Data.GetDataPresent("FileName", False) Then
        ' El nombre del fichero soltado
        With TextBox1
            .Lines = CType(e.Data.GetData("FileName", False), String())
            ' Se convierte en nombre largo
            .Text = guille.GetLongFileName(.Text)
            ' Se posiciona el cursor al final
            .SelectionStart = .Text.Length
        End With
    End If
End Sub

Lo que hacemos es:
Comprobamos si el tipo de datos soltados es un fichero:
If e.Data.GetDataPresent("FileName", False) Then

En caso de que se cumpla, es que el tipo de datos soltado es un fichero.
Por tanto lo asignamos al TextBox1, pero fíjate que lo asigno a la propiedad Lines en lugar de asignarlo a la propiedad Text.
Eso lo hago, porque el valor devuelto por la propiedad GetData es un array y no simplemente una cadena de texto... no me preguntes porqué es así, ya que no le veo el sentido.
El CType sirve para que se convierta de forma correcta, ya que tengo activado el Option Strict y si no lo hago así, recibiré un error de que: Option Strict no permite la conversión implícita de System.Object a 1-Matriz dimensional de String.
Es decir, GetData devuelve un objeto, el cual se podría convertir a una cadena de texto... pero, confía en mi, no me ha funcionado, así que tuve que usar la propiedad Lines.

Después de asignar a Lines el nombre del fichero, podemos usar la propiedad Text, ya que al haber sólo una "línea", ésta estará dentro de la propiedad Text del TextBox1.
Y esa propiedad es la que pasamos como parámetro para la función que convierte nombres cortos en largos, ya que los nombres soltados siempre se muestran como nombres cortos, (no se si esto cambiará con la versión final, pero me parece que no).
El valor devuelto por la función GetLongFileName, la cual he creado en un Namespace llamado guille, se vuelve a asignar a la propiedad Text del TextBox1 y con idea de que se muestre el final del nombre del fichero, he asignado a la propiedad SelectionStart el valor de la longitud del texto, de esa forma, el cursor se posicionará al final del nombre... ¡truquillos chorras que funcionan!

Después veremos el código de la función GetLongFileName, que simplemente llama a una función del API.

 

Leer un fichero de texto y asignarlo a un TextBox y/o
asignar una imagen a un PictureBox desde un fichero:

Cuando tenemos el nombre del fichero, podemos mostrar ese fichero en el textbox o en el picturebox, según sea un archivo (sí, he dicho archivo en vez de fichero... ¡que raro! ¿verdad?) de texto o uno de imagen.
Lo de mostrar se hace en el evento Click del botón Mostrar:

Private Sub btnMostrar_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnMostrar.Click
    ' Leer el fichero indicado y mostrarlo en el textbox o en el picture
    ' Por defecto cualquier fichero "no soportado" lo tomaremos como texto
    Dim EsTexto As Integer = 1
    '
    If (TextBox1.Text.IndexOf(".txt") > -1) Then
        EsTexto = 1
    ElseIf (TextBox1.Text.IndexOf(".rtf") > -1) Then
        EsTexto = 1
    ElseIf (TextBox1.Text.IndexOf(".htm") > -1) Then
        EsTexto = 1
    ElseIf (TextBox1.Text.IndexOf(".asp") > -1) Then
        EsTexto = 1
    ElseIf (TextBox1.Text.IndexOf(".bmp") > -1) Then
        EsTexto = 2
    ElseIf (TextBox1.Text.IndexOf(".jpg") > -1) Then
        EsTexto = 2
    ElseIf (TextBox1.Text.IndexOf(".gif") > -1) Then
        EsTexto = 2
    End If
    '
    If EsTexto = 1 Then
        ' Si es un fichero de texto
        Dim sr As System.IO.StreamReader
        '
        ' Interceptamos los posibles errores
        Try
            sr = New System.IO.StreamReader(TextBox1.Text, System.Text.Encoding.Default)
            TextBox2.Text = sr.ReadToEnd()
            sr.Close()
            TextBox2.SelectionStart = TextBox2.Text.Length
        Catch ex As Exception
            MessageBox.Show("Error al abrir el fichero:" & ControlChars.CrLf & ex.Message, "Abrir fichero")
        End Try
        TabControl1.SelectedIndex = 0
    ElseIf EsTexto = 2 Then
        ' Si es un fichero de imagen
        Try
            PictureBox1.Image = Image.FromFile(TextBox1.Text)
        Catch ex As Exception
            MessageBox.Show("Error al asignar el fichero a la imagen." & ControlChars.CrLf & ex.Message, "Abrir fichero")
        End Try
        TabControl1.SelectedIndex = 1
    Else
        lblInfo.Text = " No es un formato reconocido: " & TextBox1.Text
    End If

End Sub

En este código tenemos dos partes:

La prinmera que comprueba que tipo de fichero es, realmente no harían falta tantas comprobaciones, ya que, si no es una imagen del tipo BMP, GIF o JPG se va a suponer que es texto, pero es que en un principio si que hice las comprobaciones pertinentes para que sólo se aceptaran los tipos ahí indicados... y la verdad es que se me olvidó quitar esos tipos... y lo he dejado así, aún a pesar de que tu opinión sobre mi forma de programar te decepcione, para que sepas cómo comprobar distintos tipos de ficheros... (je, je, excusas).
En esas comparaciones se puede usar la función InStr del VB, pero he preferido usar la que "incluye" el objeto String, ya que es compatible con cualquier lenguaje de .NET, como podrás comprobar en el código de C#.
Por tanto, puedes usar IndexOf para saber si una cadena forma parte de otra.
A diferencia del InStr que devuelve un cero cuando no se ha encontrado el texto especificado, IndexOf devuelve -1, ya que las posiciones se cuentan a partir de cero en .NET. Cosa que deberás tener en cuenta...

Lo dicho, si es un fichero de tipo imagen se asignará un 2 a la variable EsTexto y si es otro tipo, se asigna un 1, que es el valor que por defecto le he asignado al declarar la variable.

La segunda parte del código tiene en cuenta si el valor de EsTexto es uno, lo cual quiere decir que es un fichero de tipo texto, o al menos que así es como lo vamos a tratar.
En ese caso, se abre el fichero en cuestión y se asigna al TextBox2.
Para abrir un fichero y leerlo al completo, he usado un objeto StreamReader.
Al asignar a la variable creada el objeto StreamReader, aprovecho para abrir el fichero indicado en el TextBox1.
Después, una vez abierto, (si no se pudiera abrir, se produciría un error, el cual es "interceptado" por el Try/Catch), se asigna a la propiedad Text del TextBox2, (recuerda que a diferencia del VB6, no se puede usar la propiedad por defecto del TextBox2, ya que eso no funciona en .NET), mediante la propiedad ReadToEnd que como su nombre, (en inglés), indica: lee todo el fichero hasta el final.
Una vez leido, se cierra el Stream.
Y para que se vea el texto recién asignado al TextBox2, nos aseguramos de que la solapa (Tab) en la que está el TextBox contenido se muestre, le idicamos al TabControl que la solapa que está seleccionada es la CERO, recuerda que en .NET todo empieza a contarse desde cero.

Para leer y asignar la imagen al picturebox, la cosa es más simple, ya que podemos usar la propiedad FromFile de un objeto Image que devuelve un objeto Image el cual se asigna al objeto Image del PictureBox... ¡cuantos objetos Image!

 

Copiar texto en el portapapeles:

Cuando pulsamos en el botón btnCopiarText copiamos el texto contenido en el TextBox en el portapapeles.
Aquí tienes el código con el que se consigue eso:

Private Sub btnCopiarTxt_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnCopiarTxt.Click
    ' Copiar el contenido del TextBox2 en el portapapeles
    Clipboard.SetDataObject(TextBox2.Text, True)
End Sub

Creo que no necesita explicación, ¿verdad?
Sólo decirte que el True que hay al final es para indicarle al .NET que conserve el contenido del portapapeles cuando nuestra aplicación finalice. Si por el contrario, queremos que ese texto no esté en el portapapeles, simplemente le indicaremos que No lo mantenga, poniendo un valor False.

 

Copiar una imagen en el portapapeles:

Copiar una imagen es tan simple como un texto... si no te lo crees, mira:

Private Sub btnCopiarImg_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnCopiarImg.Click
    ' Copiar la imagen del PictureBox1 en el portapapeles
    Clipboard.SetDataObject(PictureBox1.Image, True)
End Sub

Es decir, simplemente se indica que objeto es el que queremos copiar y se copia.
He de hacer una aclaración, con la Beta2 el formato de imagen copiado en el portapapeles no es muy compatible con otros programas, al menos el Paint y el PaintShop Pro no lo reconocen... aunque, como veremos enseguida, el .NET si que lo reconoce.

 

Pegar una imagen del portapapeles en un PictureBox:

Esta es la operación inversa... y lo que hacemos es tomar una imagen del portapapeles y lo asignamos al control PictureBox.
Al contrario de lo que ocurre con la copia hacia el portapapeles, si copiamos una imagen con otro programa, he probado con el PaintShop Pro, si que lo reconoce el .NET y lo "pega" en el PictureBox.
Este es el código para pegar:

Private Sub btnPegarImg_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnPegarImg.Click
    ' Pegar la imagen del portapapeles en el Picture1
    ' Se comprueba si hay alguna imagen
    '
    ' Mostrar los formatos que hay en el clipBoard
    TextBox2.Lines = Clipboard.GetDataObject.GetFormats()
    If Clipboard.GetDataObject.GetDataPresent("Bitmap") Then
        'MessageBox.Show("Si hay datos del tipo Bitmap")
        PictureBox1.Image = CType(Clipboard.GetDataObject.GetData("Bitmap"), Bitmap)
    End If
End Sub

En este código hacemos lo siguiente, aunque los comentarios son bastantes claros, te lo explico:
En primer lugar, mostramos en el TextBox2 los formatos que hay en el portapapeles, ello se consigue asignando a la propiedad Lines el valor devuelto por GetFormats. Lo asigno a la propiedad Lines, ya que GetFormats devuelve un array, el cual también podrías asignar en un array de tipo String.

Después se comprueba si uno de los formatos es Bitmap, de ser así, se asigna lo que haya en el portapapeles con dicho formato en la propiedad Image del PictureBox. Sólo aclararte que Bitmap no significa BMP, ya que puede ser cualquier imagen: un Gif, un JPG, etc.

 

Usar funciones del API en las aplicaciones .NET:

Para terminar, te voy a mostrar el código con la declaración de la función del API que convierte un nombre corto en uno largo.

Option Strict On

Imports System.Runtime.InteropServices

Namespace guille
    Module WinAPI
        Public Const MAX_PATH As Integer = 260
        '
        ' Declaración de GetLongPathName para Unicode
        <DllImport("KERNEL32.DLL", EntryPoint:="GetLongPathNameW", SetLastError:=True, _
        CharSet:=CharSet.Unicode, ExactSpelling:=True, _
        CallingConvention:=CallingConvention.StdCall)> _
        Private Function GetLongPathName(ByVal lpszShortPath As String, ByVal lpszLongPath As String, ByVal cchBuffer As Integer) As Integer
            ' Devuelve el nombre largo de un nombre corto
        End Function
        '
        'Declarar la función de la forma clásica: (con la función de ANSI)
        'Private Declare Function GetLongPathName Lib "kernel32.dll" Alias "GetLongPathNameA" (ByVal lpszShortPath As String, ByVal lpszLongPath As String, ByVal cchBuffer As Integer) As Integer
        '
        Public Function GetLongFileName(ByVal sFileName As String) As String
            ' Convertir el nombre indicado en nombre largo
            Dim s As String = StrDup(MAX_PATH, Chr(0))
            Dim i As Integer = GetLongPathName(sFileName, s, s.Length)
            If i > MAX_PATH Then
                ' Se requiere más espacio
                s = StrDup(i, Chr(0))
                i = GetLongPathName(sFileName, s, s.Length)
            End If
            If i = 0 Then
                Return ""
            Else
                Return s.Substring(0, i)
            End If
        End Function
    End Module
End Namespace

De este código quiero destacar lo siguiente:
Se pueden declarar las funciones del API de dos formas:
La forma clásica, o bien usando el DllImport, con el cual podemos usar funciones tanto UNICODE como ANSI.
Ya sabrás que en VB6 para usar las funciones Unicode, había que asignar dichas funciones en un fichero TLB, ya que no se pueden usar las declaraciones directamente, no me preguntes porqué, ya que ahora mismo no lo recuerdo... je, je.
Pero en VB.NET tampoco se puede usar una declaración clásica, por ejemplo esto no funcionaría:
 

Private Declare Function GetLongPathName Lib "kernel32.dll" Alias "GetLongPathNameW" _
    (ByVal lpszShortPath As String, ByVal lpszLongPath As String, ByVal cchBuffer As Integer) As Integer

Tampoco daría error, pero he probado y no funciona.

En caso de que quieras usar esa API en Windows 98/ME, tendrías que declarar la versión ANSI de esa función, en ese caso, (aunque no lo he comprobado), puedes usar la forma "antigua" o clásica o bien usar DllImport, pero indicándole que la función original se llama GetLongPathNameA y el Charset es Ansi.

Una captura del ejemplo en tiempo de ejecución con una imagen del colega Joe, (el del bigote), y un servidor, (de espaldas, sí esa oreja es TODA mia), cuando estuvimos en Seattle:


Figura 2: en funcionamiento

 

Y eso es todo por hoy... que con tanto explicar me he pasado al día 15... y después te quejas de que hay poca información en mis páginas, con el trabajico que me cuesta explicar las cosas... je, je.

¡Que te aproveche!
Nos vemos.
Guillermo

Aquí tienes un link al código completo para VB.NET: PortapapelesVB.zip 15.9KB

Si quieres el de C#, te vas a la página de C# y de allí te lo bajas.
Te recuerdo que tanto el formulario como el ejecutable, están creados con el Visual Studio.NET Beta 2 español, aunque creo que funcionará en cualquier versión posterior.


Ir al índice de vb.net

la Luna del Guille o... el Guille que está en la Luna... tanto monta...