elhacker.net cabecera Bienvenido(a), Visitante. Por favor Ingresar o Registrarse
¿Perdiste tu email de activación?.

 

 


Tema destacado: Usando Git para manipular el directorio de trabajo, el índice y commits (segunda parte)


+  Foro de elhacker.net
|-+  Programación
| |-+  Programación General
| | |-+  .NET (C#, VB.NET, ASP) (Moderador: kub0x)
| | | |-+  Problema con modificación de un PictureBox desde el hilo generado por un Timer.
0 Usuarios y 1 Visitante están viendo este tema.
Páginas: [1] Ir Abajo Respuesta Imprimir
Autor Tema: Problema con modificación de un PictureBox desde el hilo generado por un Timer.  (Leído 4,521 veces)
SARGE553413

Desconectado Desconectado

Mensajes: 176


Ver Perfil
Problema con modificación de un PictureBox desde el hilo generado por un Timer.
« en: 6 Octubre 2014, 00:00 am »

Hola a todos.

Tengo un método en C# que actualiza el contenido de un PictureBox que recibe como parámetro, algo así:
Código:
void fun(PictureBox pb){
   pb.Image = (Image)this.bitMap; //bitMap es un objeto Bitmap.
}
El caso es que esa función la uso de callback en un Timer, para ir cambiando la imagen cada cierto tiempo.
Si todo el código anterior lo meto en el mismo Windows.Form donde está el PictureBox todo va bien.
El problema es que quiero sacar todo ese código a otra clase, y usar esta clase desde mi Windows.Form. Es cuando hago esto cuando todo falla.

El error que me da es: Unexpected “Bitmap Region is already Locked”.

He buscado bastante por internet y he intentado varias cosas:
1 - He probado con Timers.Timer y con Windows.Forms.Timer .
2 - He usado el keyword 'lock' en el método de callback.
3 - He leído algo de Invoke e InvokeRequired. PictureBox tiene el InvokeRequired a 'false' así que imagino que no tiene nada que ver con usar invoke.

¿Alguien sabe qué puedo hacer?

Gracias, saludos.


En línea

Eleкtro
Ex-Staff
*
Desconectado Desconectado

Mensajes: 9.809



Ver Perfil
Re: Problema con modificación de un PictureBox desde el hilo generado por un Timer.
« Respuesta #1 en: 6 Octubre 2014, 00:48 am »

El mensaje de error se explica por si mismo, te está indicando que se está intentando bloquear la misma imagen simultaneamente (Bitmap.LockBits) sin haberla desbloqueado previamente (Bitmap.UnlockBits), y claro está, no puedes bloquear la imagen dos veces consecutivas si no la desbloqueas, comprueba bien lo que estés haciendo con ese objeto 'bitMap'.

EDITO: Tras leer el título de tu pregunta (que antes no me fijé muy bien) afirmas que la aplicación es multi-hilo, entonces probablemente la causa se deba a que varios hilos están intentando acceder/usar la misma imagen simultaneamente, el problema sigue siendo el mismo que he mencionado arriba.
Prueba a utilizar el método Bitmap.Clone para evitar esa excepción.





3 - He leído algo de Invoke e InvokeRequired. PictureBox tiene el InvokeRequired a 'false' así que imagino que no tiene nada que ver con usar invoke.

Si tu aplicación es single-threaded no tienes porque preocuparte desde que Class (Form) asignes la propiedad de cualquier control,
de lo contrario, si tu aplicación es multi-htreaded y estás intentando modificar la propiedad de ese control desde un thread distinto al que creó el control, entonces por supuestisimo debes usar Control.InvokeRequired + Control.Invoke.

Aparte, en lo referente al Timer, no veo razón para que te estés complicando la vida con un Threading.Timer (+ Callbacks), el cual si tu aplicación es multi-hilo entonces es probable que también cause un error derivado con la imagen, ¿es realmente necesario para tu propósito?, prueba a utilizar un Windows.Forms.timer en su lugar.

saludos


« Última modificación: 6 Octubre 2014, 01:19 am por Eleкtro » En línea

SARGE553413

Desconectado Desconectado

Mensajes: 176


Ver Perfil
Re: Problema con modificación de un PictureBox desde el hilo generado por un Timer.
« Respuesta #2 en: 6 Octubre 2014, 08:57 am »

Hola de nuevo, gracias por la respuesta. Primero debo aclarar que estoy seguro de que desbloqueo el bitmap.

He probado una cosa que funciona: desde el fichero fuente del formulario que uso (Form1) hago un método que usa un Timers.Timer y muestra bien las imágenes (boca abajo, no se por qué).
También he probado un método que en lugar de un Timer es un bucle con un Thread::Sleep().
Repito, de estas dos formas funciona bien.

Si traslado cualquiera de estas 2 formas a otra clase todo falla, no entiendo por qué.
Voy a copiar el código en C++/CLI (es exactamente igual que C#, sintaxis aparte), de los métodos que uso, sin Timer:
Form1 (sin usar clase aparte)
Código:
//Esta cabecera es para poder usar el método como callback del timer.
void Form1::showMyImage2(Object ^source, ElapsedEventArgs ^e){
try{
int len = 0;
unsigned char *c = this->getFrame2((String^)Form1::path + Convert::ToString(this->counter) + ".bmp", len);
BitmapData ^bmpd = this->bmp->LockBits(Drawing::Rectangle(0, 0,
this->width, this->height), ImageLockMode::ReadWrite,this->pixelFormat);
bmpd->Scan0 = (IntPtr)c;
this->bmp->UnlockBits(bmpd);
this->picBox->Image = dynamic_cast<Image^>(this->bmp);
                //Esto es para ir cambiando del frame 0 al 1, al 2 ... etc.
this->counter = (this->counter == 5 ? 0 : this->counter + 1);

}
catch (Exception ^e){
Console::WriteLine(e->InnerException);
Console::WriteLine(e->Message);
Console::WriteLine(e->StackTrace);
}
}

array<unsigned char>^ Form1::getFrame2(String ^path, int %len){
FileStream ^fs = gcnew FileStream(path, FileMode::Open, FileAccess::Read);
BinaryReader ^br = gcnew BinaryReader(fs);
FileInfo ^aux = gcnew FileInfo(path);
int nBytes = aux->Length;
Console::WriteLine("long: {0}, int: {1}", nBytes, (int)nBytes);
array<unsigned char> ^buffer = br->ReadBytes(nBytes);
len = nBytes;
pin_ptr<unsigned char> p = &buffer[0];
unsigned char *p2 = p;
return p2;
}

La otras otras clases, AbstractLiveView y LiveView. AbstracLiveView tiene un método abstracto "getNextFrame()" que implementa LiveView. Estas clases  preteden sustituir el código de Form1.

AbstractLiveView
Código:
//Esta cabcera es por usar este método como callback de un timer.
void AbstractLiveView::updateFrame(Object ^source, EventArgs ^e){
try{
this->bmpData = this->bmp->LockBits(Drawing::Rectangle(0, 0, this->width, this->heigth),
ImageLockMode::ReadWrite, this->pixelFormat);

                //Método abstracto implementado en LiveView (abajo)
this->bmpData->Scan0 = this->getNextFrame();

this->bmp->UnlockBits(this->bmpData);
this->picBox->Image = dynamic_cast<Image^>(this->bmp);
this->picBox->Update();
}
catch (Exception ^e){
Console::WriteLine(e->Message);
Console::WriteLine(e->StackTrace);
}
}

void AbstractLiveView::startLiveView(int frequency){
for (int i = 0; i <= 5; i++){
this->updateFrame(nullptr,nullptr);
}
}

LiveView (Implementación de getNextFrame())
Código:
//Obtiene el siguiente frame a dibujar (lo coge del disco duro)
IntPtr LiveView::getNextFrame(){
pin_ptr<unsigned char> p = nullptr;
try{
String ^path = pathImage + Convert::ToString(this->counter) + ".bmp";
FileStream ^fs = gcnew FileStream(path, FileMode::Open, FileAccess::Read);
BinaryReader ^br = gcnew BinaryReader(fs);
FileInfo ^aux = gcnew FileInfo(path);
uint nBytes = aux->Length;
array<unsigned char> ^buffer = br->ReadBytes(nBytes);
p = &buffer[0];
unsigned char *p2 = p;
this->counter = (this->counter == 5 ? 0 : this->counter + 1);
return (IntPtr)p;
}
catch (Exception ^e){
Console::WriteLine(e->Message);
Console::WriteLine(e->StackTrace);
return (IntPtr)p;
}

}

Saludos y gracias.
« Última modificación: 6 Octubre 2014, 09:03 am por SARGE553413 » En línea

Eleкtro
Ex-Staff
*
Desconectado Desconectado

Mensajes: 9.809



Ver Perfil
Re: Problema con modificación de un PictureBox desde el hilo generado por un Timer.
« Respuesta #3 en: 6 Octubre 2014, 16:21 pm »

Como he comentado en otras ocasiones C++/Cli no es mi fuerte, quizás me equivoque en algún aspecto de lo que voy a comentar a continuación, pero he notado cierta irregularidad aquí:

Código
  1. FileStream ^fs = gcnew FileStream(path, FileMode::Open, FileAccess::Read);
  2. BinaryReader ^br = gcnew BinaryReader(fs);

Me he fijado en el operador gcnew, y me he informado un poco sobre su funcionalidad, pero al parecer dicho operador lo único que hace es indicar que los objetos se deben auto liberar, eso no significa que se vayan a liberar inmediatamente despues de enviar el return value, el garbage collector libera recursos cuando así lo cree necesario (a menos que lo invoques directamente), puede ser inmediatamente, o pueden pasar 2 o 20 segundos, aunque, con el uso de ese operador no estoy nada seguro la verdad, pero prueba a añadirle un bloque Finally a tu Try/catch para liberar los objetos:

Código
  1. Dim fs As FileStream = Nothing
  2. Dim br As BinaryReader = Nothing
  3.  
  4. Try
  5.    fs = New FileStream("", FileMode.Open, FileAccess.Read)
  6.    br = New BinaryReader(fs)
  7.  
  8. Catch ex As Exception
  9.  
  10. Finally
  11.    If fs IsNot Nothing Then
  12.        fs.Close()
  13.    End If
  14.  
  15.    If br IsNot Nothing Then
  16.        br.Close()
  17.    End If
  18.  
  19. End Try

C# (conversión al vuelo):
Código
  1. FileStream fs = null;
  2. BinaryReader br = null;
  3.  
  4. try {
  5. fs = new FileStream("", FileMode.Open, FileAccess.Read);
  6. br = new BinaryReader(fs);
  7.  
  8. } catch (Exception ex) {
  9.  
  10. } finally {
  11. if (fs != null) {
  12. fs.Close();
  13. }
  14.  
  15. if (br != null) {
  16. br.Close();
  17. }
  18.  
  19. }

Lo mismo digo en el bloque Try/Catch donde usas el método LockBits, ¿que ocurre si hubiera un error de por medio y no se procesa la instrucción UnLockBits?,
yo directamente eliminaría el Try/catch, y en caso de encontrar una excepción la intentaría resolver, pero bueno, si quieres usar Try/catch entonces es tu deber asegurarte de que la imagen se desbloquea siempre en cualquier circunstancia:

Código
  1. Dim bmpData As BitmapData = Nothing
  2. Dim bmp As Bitmap = Nothing
  3. Dim success As Boolean = False
  4.  
  5. Try
  6.    bmp = Bitmap.FromFile("")
  7.    bmpData = bmp.LockBits(New Rectangle With {.Width = bmp.Width, .Height = bmp.Height},
  8.                           ImageLockMode.ReadWrite, bmp.PixelFormat)
  9.  
  10.    bmp.UnlockBits(bmpData)
  11.    success = True
  12.  
  13. Catch ex As Exception
  14.  
  15. Finally
  16.    If Not success Then
  17.        bmp.UnlockBits(bmpData)
  18.    End If
  19.  
  20. End Try

C# (conversión al vuelo):
Código
  1. BitmapData bmpData = null;
  2. Bitmap bmp = null;
  3. bool success = false;
  4.  
  5. try {
  6. bmp = Bitmap.FromFile("");
  7. bmpData = bmp.LockBits(new Rectangle {
  8. Width = bmp.Width,
  9. Height = bmp.Height
  10. }, ImageLockMode.ReadWrite, bmp.PixelFormat);
  11.  
  12. bmp.UnlockBits(bmpData);
  13. success = true;
  14.  
  15.  
  16. } catch (Exception ex) {
  17. } finally {
  18. if (!success) {
  19. bmp.UnlockBits(bmpData);
  20. }
  21.  
  22. }

No se si esos pequeños ajustes resolverán el problema, pero tampoco puedo ayudar mucho más.

Saludos!
« Última modificación: 6 Octubre 2014, 16:29 pm por Eleкtro » En línea

Eleкtro
Ex-Staff
*
Desconectado Desconectado

Mensajes: 9.809



Ver Perfil
Re: Problema con modificación de un PictureBox desde el hilo generado por un Timer.
« Respuesta #4 en: 7 Octubre 2014, 20:37 pm »

Se me ha ocurrido una posible solución, de forma más simple.

SyncLock Statement - MSDN
Citar
The SyncLock statement ensures that multiple threads do not execute the statement block at the same time. SyncLock prevents each thread from entering the block until no other thread is executing it.

Utilízalo en el bloque donde haces las operaciones con el bitMap, ejemplo:

Código
  1. Sub GetFrame
  2.    SyncLock bitMap
  3.        ' hacer aquí operaciones con el bitMap... lockbits, unlockbits, etc
  4.    End SyncLock
  5. End sub

o sino, prueba con el bitmapData.

Saludos!
« Última modificación: 7 Octubre 2014, 21:35 pm por Eleкtro » En línea

Páginas: [1] Ir Arriba Respuesta Imprimir 

Ir a:  

Mensajes similares
Asunto Iniciado por Respuestas Vistas Último mensaje
DirecT x | Cargar Texturas desde un PictureBox
Programación Visual Basic
<[(x)]> 5 3,214 Último mensaje 6 Febrero 2009, 04:17 am
por BlackZeroX
Deactivar timer de form1 desde form2?
Programación Visual Basic
usuario oculto 2 1,940 Último mensaje 2 Julio 2011, 14:01 pm
por usuario oculto
Crear un timer desde 0 « 1 2 »
Programación C/C++
Riki_89D 10 6,002 Último mensaje 25 Octubre 2011, 19:43 pm
por <<<-Basura->>>
Core generado!!!!!!!! Problema con programa
Programación C/C++
EdgarKrieger 3 12,273 Último mensaje 4 Diciembre 2013, 11:30 am
por Eternal Idol
Manejo de Picturebox con un Timer en C#
.NET (C#, VB.NET, ASP)
romybe 2 4,101 Último mensaje 5 Diciembre 2014, 01:34 am
por romybe
WAP2 - Aviso Legal - Powered by SMF 1.1.21 | SMF © 2006-2008, Simple Machines