Gabriel Magaña | Si te desagrada el cambio, la irrelevancia te va a desagradar aún más

May/09

21

C#: Trabajando con Image y PictureBox

Una de las cosas más frustrantes de trabajar con imágenes y controles PictureBox es que cuando alguien instala tu aplicación, al tratar de modificar archivos de imágenes después de haberlos mostrado vía un PictureBox, algunos usuarios van a obtener errores de “fichero bajo uso por otro usuario” y “error GDI+ desconocido.” Estos errores solo pasan en algunas maquinas, y es muy inconsistente su aparición en general. Es difícil saber qué está pasando realmente.

Microsoft hace el problema peor, da mala información en sus páginas como esta: http://support.microsoft.com/kb/311754. Este artículo da una solución que no funciona, y aparte dice que lo que ocurre es “de acuerdo al diseño.” Ignorando la cuestión de cómo alguien podría poner este comportamiento intencionalmente en un sistema, el hecho de que el comportamiento no sea consistente constituye un error en la implementación.

Si hacemos una búsqueda para este problema, pueden ver varios arreglos propuestos:

  • Llamar .Dispose() a la imagen antes de grabar un archivo
  • Llamar .Dispose() a la imagen antes de grabar un archivo, y luego llamar al recolector de basura
  • Hacer una copia del objeto Image antes de asignarlo al control PictureBox (.NET es demasiado inteligente para esto, al pareces)
  • Usar Image.Clone() para hacer una copia del objeto Image antes de asignarlo al PictureBox
  • Usar FileStream para abrir el archivo

Hay muchísimas páginas y foros en el internet acerca de este problema, y los leí todos, créanme. Ninguno me dio una respuesta 100% efectiva, aunque me apuntaron en la dirección correcta: El problema es en asociar una imagen obtenida de un archivo con un PictureBox. La meta es de usar toda medida en hacer pensar al objeto Image que la imagen no vino de un archivo.

Ya basta de quejarnos… ¿Así que como lo arreglamos? Aparentemente este es un error en Windows desde los días de VB6, asi que no tiene caso esperar a que Microsoft lo arregle.

Después de muchos jalones de pelo e intentos fallidos, esto es lo que finalmente funcionó para mi: Abrir el archivo que contiene la imagen, y de la manera más indirecta posible, asignar los contenidos al objeto Image que vas a usar para mostrar la imagen en el PictureBox. Haz lo mismo cuando vas a salvar una imagen. Si no mezclas objetos Image con acceso a archivos todos van a estar felices y sin errores.

Estos son los pasos a seguir en general:

  • Para abrir un archivo:
    • Abre el archive, pero no como un objeto Image, lee el archive a un arreglo de byte
    • Convierte ese arreglo de byte a un objeto Image, la conversión detectara cual formato (JPG, PNG, etc…) usa el archivo
    • Usa ese objeto Image para asignarlo al PictureBox y desplegarlo
    • NO llames al método .Save() de Image para grabar un archivo, ve lo siguiente…
  • Para grabar un archivo:
    • Clona el objeto Image via el metodo .Clone()
    • Graba la imagen via a un MemoryStream, usando tu codificación preferida (JPG, PNG, etc…)
    • Convierte el MemopryStream a un arreglo de byte
    • Graba ese arreglo de byte al archivo destino

Aquí hay un poco de código que lo demuestra:

Para leer el archivo:

public static Image NonLockingOpen(string filename) {
	Image result;

	#region Save file to byte array

	long size = (new FileInfo(filename)).Length;
	FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read);
	byte[] data = new byte[size];
	try {
		fs.Read(data, 0, (int)size);
	} finally {
		fs.Close();
		fs.Dispose();
	}

	#endregion

	#region Convert bytes to image

	MemoryStream ms = new MemoryStream();
	ms.Write(data, 0, (int)size);
	result = new Bitmap(ms);
	ms.Close();

	#endregion

	return result;
}

Para escribir el archivo:

public static void NonLockingSave(Image img, string fn, ImageFormat format) {
	// Delete destination file if it already exists
	if (File.Exists(fn))
		File.Delete(fn);

	try {

		#region Convert image to destination format

		MemoryStream ms = new MemoryStream();
		Image img2 = (Image)img.Clone();
		img2.Save(ms, format);
		img2.Dispose();
		byte[] byteArray = ms.ToArray();
		ms.Close();
		ms.Dispose();

		#endregion

		#region Save byte array to file

		FileStream fs = new FileStream(fn, FileMode.CreateNew, FileAccess.Write);
		try {
			fs.Write(byteArray, 0, byteArray.Length);
		} finally {
			fs.Close();
			fs.Dispose();
		}

		#endregion

	} catch {
		// Delete file if it was created
		if (File.Exists(fn))
			File.Delete(fn);

		// Re-throw exception
		throw;
	}
}

Finalemente, aqui hay un método de extension que puede ayudarte:

public static void SaveViaStreams(this Image img, string fn, ImageFormat format) {
	NonLockingSave(img, fn, format);
}

Una vez que usé estos métodos para abriy y grabar mis archivos, todos mis errores de usar imagenes con PictureBox se fueron, en todas las computadoras quemis clientes usan. !Espero te ayuden a tí igual!

Comparte: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Facebook
  • Twitter
  • LinkedIn
  • email

· ·

  • Masturbo

    podrias poner todo el ejemplo del codigo? la idea se entiende pero seria genial que pusieras el codigo completo.
     

  • MIKE

    Hola te envío UN SALUDO CORDIAL MI NOMBRE ES MIGUEL PONCE…. me encuentro muy contento porque realmente me AYUDO EL CÓDIGO…
    Gracias por la GENTILEZA
    Cuídate mucho 
    Chua..

  • Anotnio

    Con un GIF da de nuevo el Error ¬¬ es decir cargar un gif animado provoca que salga de nuevo la exepcion

  • Neil

    Voy a probarlo pero parece muy prometedor.
    Muchas gracias de antemano.

    • neil

      No funciono seguiré probando.

  • Liz Montesinos

    Eres mi héroe!!! Gracias!!

  • Angel Gutpa

    Excelente aportación, gracias por compartirla.

<<

>>

Theme Design by devolux.nh2.me