28 - Drag&Drop sobre un TextBox (detectando la codificación de caracteres)Primer que todo vamos a repasar los conceptos básicos que se van a mostrar.
¿Que es un Drag&Drop?
Drag significa
ArrastrarDrop significa
SoltarPor lo tanto => Drag&Drop => Arrastrar y Soltar. Vamos que es la simple función de arrastrar un documento y soltarlo en algún lado. En este caso vamos a ser capaces de seleccionar un archivo de texto y soltarlo sobre un
TextBox dentro del formulario.
Luego tenemos como detectar la codificación de caracteres. La codificación de caracteres es el método que permite convertir un carácter de un lenguaje natural en un símbolo de otro sistema de representación aplicando normas o reglas de codificación
Existen varios tipos de codificación:
- UTF-8
- UTF-16 Big Endian
- UTF-16 Little Endian
- UTF-7
- UTF-32
Entre otros.
Ahora, ¿Cómo vamos a detectar que codificación tiene un fichero?
Para esto vamos a analizar la "
Marca de orden de bytes (BOM)". BOM es un carácter Unicode que se utiliza para indicar el orden de los bytes de un fichero de texto. Su código es
U+FEFF.
Representaciones de las marcas de orden de bytes para cada codificaciónEncoding | Representación (Hex) |
UTF-7 | 2B 2F 76 |
UTF-8 | EF BB BF |
UTF-16 Little Endian | FF FE |
UTF-16 Big Endian | FE FF |
UTF-32 Big Endian | 00 00 FE FF |
Veamos el método que vamos a utilizar para analizar la BOM:
/// <summary>
/// Determina la codificación de un fichero de texto analizando la marca de orden de bytes (BOM)
/// Devuelve la codificación por defecto de la página ANSI del sistema en caso de que el análisis falle
/// </summary>
/// <param name="filename">El fichero de texto a analizar</param>
/// <returns>La codificación detectada.</returns>
public static Encoding GetEncoding(string filename)
{
// Read the BOM
byte[] bom
= new byte[4]; //Buffer para almacenar los datos leidos del fichero
//Utilizamos un FileStream para abrir el fichero
using (FileStream file
= new FileStream
(filename, FileMode
.Open, FileAccess
.Read)) {
file.Read(bom, 0, 4); //Leemos los 4 primeros bytes del fichero
}
// Analizamos la BOM y devolvemos la codificación correcta
if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76) return Encoding.UTF7;
if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) return Encoding.UTF8;
if (bom[0] == 0xff && bom[1] == 0xfe) return Encoding.Unicode; //UTF-16LE
if (bom[0] == 0xfe && bom[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE
if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) return Encoding.UTF32;
//Devolvemos la codificacion por defecto de la página ANSI del sistema.
return Encoding.Default;
}
Nota: Para usar
FileStream, debemos añadir a los
using:
System.IO;
Nota: Ese ^^ nombre de espacio contiene tipos que permiten leer y escribir de archivos y flujos de datos, y tipos que brindan soporte básico para el trabajo con directorios.
Un
FileStream, nos permite crear un flujo alrededor de un fichero, soportando operaciones de lectura y escritura tanto sincrónicas como asincrónicas.
Veamos que lo utilizamos con la declaración
using que nos brinda una sintaxis conveniente para asegurar el uso correcto de objetos que implementen la interfaz
IDisposable. Esta interfaz define un método que nos permite cerrar (de-allocate) los recursos de un objeto. Por lo tanto luego que se salga del ámbito de la declaración
using los recursos del objeto sobre el cual se esta trabajando serán liberados. En este caso (
FileStream) no será necesario hacerle
Close()El
FileStream tiene un constructor con varias sobrecargas, en este caso vamos a utilizar la sobrecarga que tiene esta signatura:
FileStream(String, FileMode, FileAccess)
Donde el primer parámetro viene dado por la ruta del archivo, o sea,
fileList[0]. El segundo es un
enum que especifica como el sistema operativo debe abrir el archivo, en este caso
FileMode.Open, lo cual indica que el sistema operativo debe abrir un fichero existente. La habilidad de abrir un fichero es dependiente del valor especificado por el
enum FileAccess en el próximo parámetro el cual define que permiso se tiene al acceder al archivo (Read, Write o Read/Write). En este caso vamos a utilizar
FileAccess.Read, pues solo vamos a leer el archivo.
El método
Read() que pertenece a la clase
FileStream, nos permite leer un bloque de bytes desde el fichero apuntado por el flujo y guardarlo en un buffer dado. En este caso el método devuelve un
int que representan la cantidad de bytes leídos. Pero modifica el primer parámetro, pues al ser un array, este es pasado al método por referencia, y por lo tanto es modificado. En él se guardan los bytes leídos. Este parámetro es un
byte[]. El segundo parámetro es un
int que representa el
offset (digámosle la posición) en el array (primer parámetro) donde los bytes se comenzarán a guardar. Y el tercer parámetro es el máximo número de bytes a leer.
Ahora, como lo que queremos es mostrar el contenido de un fichero de texto en un
TextBox vamos a insertar un
TextBox en el formulario.
Nota: En este ejemplo>
Propiedad Name del Form -> mainForm
Propiedad Name del TextBox -> textBoxAhora, para permitirle a
textBox recibir información que haya sido arrastrada y soltada sobre él, le vamos a poner la propiedad
AllowDrop = trueAhora nos vamos subscribir a los eventos
textBox_DragEnter y
textBox_DragDrop. Luego vamos a necesitar un
string[] para almacenar las rutas de el/los fichero/s que se soltaron sobre
textBox. Para eso declaramos un campo privado dentro de la clase de
mainForm:
string[] fileList;
Veamos que hacemos una vez que se capture el evento
textBox_DragEnterprivate void textBox_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
fileList = (string[])e.Data.GetData(DataFormats.FileDrop);
if (fileList.Length == 1 && Path.GetExtension(fileList[0]) == ".txt")
{
e.Effect = DragDropEffects.Copy;
}
else
e.Effect = DragDropEffects.None;
}
}
Lo primero que vemos es la propiedad
Data del tipo
DataEventArgs e que devuelve un objeto que implementa
IDataObject, que contiene datos asociado con este evento. Entonces lo que hacemos es llamar al método
GetDataPresent() de la interfaz
IDataObject el cual nos permite determinar si la información almacenada en esta instancia está asociada a un formato especifico. En este caso le pasamos como parámetro el campo estático (solo-lectura)
FileDrop de la clase
DataFormats -
DataFormats.FileDrop. El cual especifica el formato de Windows para arrastrar y soltar ficheros.
Una vez explicado esto, lo que comprobamos es que si lo que se está soltando encima de
textBox es un archivo vamos a hacer una cosa:
Primero que todo:
fileList = (string[])e.Data.GetData(DataFormats.FileDrop);
Con el método
GetData() sobre la propiedad
Data del tipo
DragEventArgs e obtenemos la información (con el formato establecido [DataFormats.FileDrop]) que estamos arrastrando sobre el
textBox. Luego, vemos que le estamos haciendo un
cast a
string[] y se lo asignamos al campo que habíamos declarado. Aquí tendremos toda la información que viene siendo arrastrada y capturada por el evento. Como el formato es
FileDrop lo que nos va a añadir en el
array son las rutas a los documentos que están siendo arrastrados.
Ahora vamos a hacer 2 comprobaciones:
1 - Que solo estemos arrastrando 1 solo fichero, para esto solo tenemos que comprobar que la longitud del
array sea 1
2 - Que la extensión del archivo sea ".txt".
Para comprobar esta segunda condición vamos a utilizar el método
GetExtension() de la clase
Path que pertenece al
namespace System.IO. La clase estática
Path realiza operaciones sobre
strings que contienen información de rutas de ficheros o directorio. En este caso utilizamos el método estático
GetExternsion(), el cual recibe un
string que representa "la ruta" de la cual queremos sacar la extensión. En este caso:
fileList[0], el cual es el único archivo que estamos arrastrando y comprobamos si es igual a ".txt". En ese caso vamos a definir la propiedad
Effect del tipo
DragEventArgs e a
DragDropEffects.Copy, en caso contrario (que no se cumplan las dos condiciones) a
DragDropEffects.None.
La propiedad
Effect aplica el efecto determinado para el cursor cuando se hace una operación de Drag & Drop sobre el control.
Luego, vemos que le asignamos un valor utilizando el
enum DragDropEffects, y su valor
Copy, que crea el efecto de que lo que se está arrastrando es copiado a donde se va a soltar.
En el otro caso está
DragDropEffects.None que implica que donde se va a soltar no acepta datos. Por lo tanto no aceptará los datos cuando se suelten los datos.
Ahora veamos el
EventHandler para
textbox_DragDrop:
private void textBox_DragDrop(object sender, DragEventArgs e)
{
Encoding fileEncoding = GetEncoding(fileList[0]);
using (StreamReader file
= new StreamReader
(fileList
[0], fileEncoding
)) {
textBox.Text = file.ReadToEnd();
}
}
Lo primero que hacemos es capturar lo que devuelve el método
GetEncoding() (explicado anteriormente), el cual devuelve la codificación de caracteres correspondiente al fichero (fileList[0]) tras analizar la BOM.
Luego, utilizando un
StreamReader, que también pertenece al
namespace System.IO vamos a leer el fichero.
Un
StreamReader nos permite leer caracteres de un flujo de bytes en una codificación específica. En este caso utilizamos la declaración
using con el mismo objetivo que explique arriba con el
FileStream. Utilizaremos una sobrecarga del constructor del
StreamReader que recibe la ruta del fichero al cual le queremos crear el flujo (fileList[0]) y la codificación de caracteres con la cual queremos abrirla. En este caso le pasamos lo que nos devolvió el método
GetEncoding().
Entonces dentro del ámbito de la declaración
using vamos a asignarle a la propiedad
Text de
textBox, lo que nos devuelve el método
ReadToEnd() perteneciente a la clase
StreamReader. Este método lee todos los caracteres desde la posición actual (en el stream) hasta el final del stream. Se dice desde la posición actual, pues se pueden haber hecho lecturas desde el stream anteriormente y se queda guardada hasta que posición se leyó, en este caso no se había leído nada, por lo tanto se lee desde el principio hasta el final.
Bueno, esto es todo para este código, solo les queda probarlo. Espero que entiendan todo, quizás no sea la mejor manera de explicarlo, pero lo hice lo mejor y más sencillo que pude.
Cualquier duda/comentario/crítica será bienvenida.
Espero seguir subiendo códigos pronto.
Salu2s