Tengo un método de extensión SafeInvoke Control similar al que Greg D analiza aquí (menos la verificación IsHandleCreated).
Lo estoy llamando de la System.Windows.Forms.Form
siguiente manera:
public void Show(string text) {
label.SafeInvoke(()=>label.Text = text);
this.Show();
this.Refresh();
}
A veces (esta llamada puede provenir de una variedad de hilos) esto da como resultado el siguiente error:
System.InvalidOperationException
ocurrió
Message
= "No se puede llamar a Invoke o BeginInvoke en un control hasta que se haya creado el identificador de ventana."
Source
= "System.Windows.Forms"
StackTrace: at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous) at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args) at System.Windows.Forms.Control.Invoke(Delegate method) at DriverInterface2.UI.WinForms.Dialogs.FormExtensions.SafeInvoke[T](T control, Action`1 action) in C:\code\DriverInterface2\DriverInterface2.UI.WinForms\Dialogs\FormExtensions.cs:line 16
¿Qué está pasando y cómo lo soluciono? Sé que no es un problema de creación de formularios, ya que a veces funcionará una vez y fallará la próxima vez, entonces, ¿cuál podría ser el problema?
PD. Realmente soy horrible en WinForms, ¿alguien conoce una buena serie de artículos que explique todo el modelo y cómo trabajar con él?
fuente
IsHandleCreated
existe la verificación. Está intentando cambiar una propiedad de (Enviar un mensaje a) un control que aún no se ha creado. Una cosa que puede hacer en esta situación es poner en cola a los delegados que se envían antes de la creación del control y luego ejecutarlos en elHandleCreated
evento.Respuestas:
Es posible que esté creando sus controles en el hilo incorrecto. Considere la siguiente documentación de MSDN :
Veamos qué significa esto para ti. (Esto sería más fácil de razonar si también viéramos su implementación de SafeInvoke)
Suponiendo que su implementación es idéntica a la referenciada con la excepción de la verificación contra IsHandleCreated , sigamos la lógica:
public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous) { if (uiElement == null) { throw new ArgumentNullException("uiElement"); } if (uiElement.InvokeRequired) { if (forceSynchronous) { uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); }); } else { uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); }); } } else { if (uiElement.IsDisposed) { throw new ObjectDisposedException("Control is already disposed."); } updater(); } }
Considere el caso en el que llamamos
SafeInvoke
desde el subproceso no gui para un control cuyo identificador no se ha creado.uiElement
no es nulo, así que lo comprobamosuiElement.InvokeRequired
. Según los documentos de MSDN (en negrita)InvokeRequired
volveráfalse
porque, aunque se creó en un hilo diferente, ¡el identificador no se ha creado! Esto nos envía a laelse
condición donde verificamosIsDisposed
o inmediatamente procedemos a llamar a la acción enviada ... ¡ desde el hilo de fondo !En este punto, todas las apuestas están desactivadas con respecto a ese control porque su identificador se ha creado en un hilo que no tiene una bomba de mensajes para él, como se menciona en el segundo párrafo. ¿Quizás este es el caso que estás encontrando?
fuente
EndInvoke
después delBeginInvoke
?Encontré el
InvokeRequired
no confiable, así que simplemente usoif (!this.IsHandleCreated) { this.CreateHandle(); }
fuente
Aquí está mi respuesta a una pregunta similar :
fuente
El método en la publicación que vincula a las llamadas
Invoke
/BeginInvoke
antes de verificar si el identificador del control se ha creado en el caso de que se llame desde un hilo que no creó el control.Entonces obtendrá la excepción cuando su método sea llamado desde un hilo que no sea el que creó el control. Esto puede suceder por eventos remotos o elementos de usuario de trabajo en cola ...
EDITAR
Si marca
InvokeRequired
yHandleCreated
antes de llamar a invoke, no debería obtener esa excepción.fuente
Si va a utilizar un
Control
de otro hilo antes de mostrar o hacer otras cosas con elControl
, considere forzar la creación de su identificador dentro del constructor. Esto se hace usando laCreateHandle
función.En un proyecto de subprocesos múltiples, donde la lógica del "controlador" no está en un WinForm, esta función es fundamental en los
Control
constructores para evitar este error.fuente
Agregue esto antes de llamar al método invoke:
while (!this.IsHandleCreated) System.Threading.Thread.Sleep(100)
fuente
Haga referencia al identificador del control asociado en su creador, así:
Nota : Tenga cuidado con esta solución. Si un control tiene un identificador, es mucho más lento hacer cosas como establecer el tamaño y la ubicación del mismo. Esto hace que InitializeComponent sea mucho más lento. Una mejor solución es no poner nada en segundo plano antes de que el control tenga un identificador.
fuente
Tuve este problema con este tipo de formulario simple:
public partial class MyForm : Form { public MyForm() { Load += new EventHandler(Form1_Load); } private void Form1_Load(Object sender, EventArgs e) { InitializeComponent(); } internal void UpdateLabel(string s) { Invoke(new Action(() => { label1.Text = s; })); } }
Luego, para
n
otros subprocesos asíncronos que estaba usandonew MyForm().UpdateLabel(text)
para intentar llamar al subproceso de la interfaz de usuario, pero el constructor no le da ningún identificador a la instancia del subproceso de la interfaz de usuario, por lo que otros subprocesos obtienen otros identificadores de instancia, que sonObject reference not set to an instance of an object
oInvoke or BeginInvoke cannot be called on a control until the window handle has been created
. Para resolver esto, usé un objeto estático para sostener el identificador de la interfaz de usuario:public partial class MyForm : Form { private static MyForm _mf; public MyForm() { Load += new EventHandler(Form1_Load); } private void Form1_Load(Object sender, EventArgs e) { InitializeComponent(); _mf = this; } internal void UpdateLabel(string s) { _mf.Invoke((MethodInvoker) delegate { _mf.label1.Text = s; }); } }
Supongo que está funcionando bien, hasta ahora ...
fuente
var that = this; // this is a form (new Thread(()=> { var action= new Action(() => { something })); if(!that.IsDisposed) { if(that.IsHandleCreated) { //if (that.InvokeRequired) that.BeginInvoke(action); //else // action.Invoke(); } else that.HandleCreated+=(sender,event) => { action.Invoke(); }; } })).Start();
fuente
this
no varía según la invocación, esa técnica de estilo javascript debería ser innecesaria.¿Qué pasa con esto?
public static bool SafeInvoke( this Control control, MethodInvoker method ) { if( control != null && ! control.IsDisposed && control.IsHandleCreated && control.FindForm().IsHandleCreated ) { if( control.InvokeRequired ) { control.Invoke( method ); } else { method(); } return true; } else return false; }
fuente