Una de las cosas que quería hacer en la aplicación de la que ya comente algo en este post, es que en las distintas opciones cuando se haga una operación que pueda llevar tiempo no se bloquee todala aplicación esperando a que esa operación termine para poder seguir trabajando.
Como la idea es usar DataBinding ( usando BindingSource ) contra instancias de clases que representan los datos.
Esas operaciones, por ejemplo obtener datos de una base de datos, actualizarlos, llamar a Web Services, … se tendrían que hacer en hilos ( threads ) a parte … y aquí es donde se plantea el problema.
En .NET no se pueden realizar modificaciones sobre los elementos de la UI desde un hilo que o sea aquel en que se hayan creado.
En su momento no encontré una solución ( probé varias cosas sin éxito alguno … y la verdad es que eran posibilidades bastante complicadas de implementar … juas ).
Al final hace un par de dias ( esto de volver a ver cosas en lo del CAB parece haberme sentado bien …. juas ) se me ocurrio una posibilidad tras estar modificado un formulario, al ver en el código del mismo las llamadas a SuspendLayout y ResumeLayout se me ocurrió irme a la ayuda a ver si el BindingSource tenía algo similar … y … efectivamente SuspendBinding y ResumeBinding … si es que a veces no hay nada como leerse la ayuda …
La solución pasa por llamar al método SuspendBinding de la instancia de BindingSource antes de realizar ninguna modificación sobre el objeto que se utiliza para los bindings, realizar las modificaciones que hagan falta ( ojo que no se estaría accediendo directamente a los controles de Windows Forms, sino a los datos asociados a estos ) y una vez que se haya acabado llamar al método ResumeBiding de la instancia de BindingSource.
Para lanzar la operación en un hilo a parte se utilizaría BackgroundWorker
Aquí va un ejemplo que es el mismo código que he usado para hacer las pruebas en la aplicación:
public void Refresh(BindingSource bindingSource) { if (this.IsActive && this.AllowRefresh) { if (this.ViewState == ViewState.Default) { // Creamos el BackgroundWorker y lo ejecutamos pasando como parámetro // la instancia de BindingSource. BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += new DoWorkEventHandler(OnRefresh); worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(OnRefreshCompleted); worker.RunWorkerAsync(bindingSource); } } } protected void OnRefresh(object sender, DoWorkEventArgs e) { // Cambiamos el estado a Busy ( se desactivan botones y opciones de menu para // la vista, se mantienen así mientras el estado sea Busy) this.ViewState = ViewState.Busy; // Obtenemos el servicio de persistencia ( NHibernate ahora mismo ) IPersistenceService persistence = this.WorkItem.Services.Get<IPersistenceService>(); // Obtenemos la instancia asociada al BindingSource TEntity entity = (TEntity)(((BindingSource)e.Argument).Current); // Suspendemos los bindings ((BindingSource)e.Argument).SuspendBinding(); // Abrimos la sesión de persistencia y obtenemos los datos persistence.Open(); persistence.Load<TEntity>(entity, persistence.GetKeyValue<TEntity>(entity)); // Pasamos como resultado el mismo BindingSource que nos llego aqui como parámetro e.Result = e.Argument; }
protected virtual void OnRefreshCompleted(object sender, RunWorkerCompletedEventArgs e) { IPersistenceService persistence = this.WorkItem.Services.Get<IPersistenceService>(); ((BindingSource)e.Result).ResumeBinding(); if (persistence != null) { persistence.Close(); persistence = null; } if (e.Error != null) { #warning Procesar excepcion aqui } this.ViewState = ViewState.Default; }
Si se quisiese comprobar que la aplicación no se queda bloqueada esperando y se puede seguir trabajando de forma normal, bastaría con poner un Thread.Sleep(xxxxx) en el mismo sitio en el que se modifiquen los datos ( en el caso del ejemplo en OnRefresh ) y comprobar que al finalizar los datos se presentan en pantalla de forma correcta.
En el ejemplo se utiliza un servicio de persistencia que utiliza NHibernate, la verdad es que no se si usar NHibernate o LLBLGen Pro ( del que tengo licencia regalo del autor de la criatura Frans Bouma
) … las dudas me asaltan de mala manera en este tema concreto y amenazan seriamente con no dejarme vivir … juas, para obtener datos de una base de datos y presentarlos en pantalla.
In DoWork event , I create controls child, and in ProgressChanged event I try add controls to SplitterPanel, but the application not responds. I use InvokeRequired for controls child.
Any suggestions, please ?
Private Sub bgCargaFichero_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgCargaFichero.DoWork
Try
‘SplitContainer1.Panel1.SuspendLayout()
‘SplitContainer1.Refresh()
‘ Procesar fichero
Me.ProcesarFicheroCargadoWork() ‘ IN THIS METHOD I CREATE CONTROLS Type = ContenedorVisorBase
‘SplitContainer1.Panel1.ResumeLayout(False)
If bgCargaFichero.CancellationPending = True Then
e.Cancel = True
Else
e.Result = True
End If
End Sub
Private Sub bgCargaFichero_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bgCargaFichero.ProgressChanged
Dim contenedorPagina As GRUPOBACKUP.Administrador.Util.Cliente.ControlesWindows.ContenedorVisorBase = Nothing
contenedorPagina = CType(e.UserState, GRUPOBACKUP.Administrador.Util.Cliente.ControlesWindows.ContenedorVisorBase)
If contenedorPagina IsNot Nothing Then
AddMiniaturaToPanel(contenedorPagina)
End If
=====
Delegate Sub AddMiniaturaToPanelDelegate2(ByVal cCTL As Control)
Private Sub AddMiniaturaToPanel(ByVal cCTL As Control)
If cCTL.InvokeRequired Then
Dim d As New AddMiniaturaToPanelDelegate2(AddressOf AddMiniaturaToPanel)
Me.Invoke(d, New Object() {cCTL})
Else
SplitContainer1.Panel1.Controls.Add(cCTL) ‘ Here, the application not responds !!!!
End If
End Sub
Leí en un foro que:
I read somewhere that SuspendBinding and ResumeBinding don’t work properly for DataGridViews because it involves ‘complex data-binding’?
Saludos.