c# - traves - realizar llamadas seguras para subprocesos en controles de formularios windows forms
Pantalla de presentación de subprocesos múltiples en C#? (12)
Bueno, para una aplicación ClickOnce que implementé en el pasado, usamos el espacio de nombres Microsoft.VisualBasic para manejar el subproceso de la pantalla de bienvenida. Puede hacer referencia y usar el ensamblado Microsoft.VisualBasic desde C # en .NET 2.0 y ofrece una gran cantidad de buenos servicios.
- Tener la forma principal heredada de Microsoft.VisualBasic.WindowsFormsApplicationBase
Sustituya el método "OnCreateSplashScreen" de la siguiente manera:
protected override void OnCreateSplashScreen() { this.SplashScreen = new SplashForm(); this.SplashScreen.TopMost = true; }
Muy sencillo, muestra tu SplashForm (que necesitas crear) mientras se está cargando, luego lo cierra automáticamente una vez que la forma principal ha terminado de cargarse.
Esto realmente hace las cosas simples, y VisualBasic.WindowsFormsApplicationBase es, por supuesto, bien probado por Microsoft y tiene una gran cantidad de funcionalidades que pueden hacer su vida mucho más fácil en Winforms, incluso en una aplicación que es 100% C #.
Al final del día, todo es IL y bytecode de todos modos, entonces ¿por qué no usarlo?
Quiero que se muestre una pantalla de bienvenida mientras se carga la aplicación. Tengo un formulario con un control de bandeja de sistema atado a él. Quiero que aparezca la pantalla de presentación mientras se carga este formulario, lo que lleva un poco de tiempo ya que está accediendo a una API del servicio web para completar algunos menús desplegables. También quiero hacer algunas pruebas básicas para las dependencias antes de cargarlas (es decir, el servicio web está disponible, el archivo de configuración es legible). A medida que avanza cada fase de la puesta en marcha, quiero actualizar la pantalla de inicio con el progreso.
He estado leyendo mucho sobre cómo enhebrar, pero me estoy perdiendo de dónde debe controlarse esto (¿el método main ()?). También me falta cómo funciona Application.Run()
, ¿es aquí donde deberían crearse los hilos para esto? Ahora, si el formulario con el control de la bandeja del sistema es el formulario "vivo", ¿debería venir el chapoteo de allí? ¿No se cargará hasta que se complete el formulario de todos modos?
No estoy buscando un folleto de código, más un algoritmo / enfoque para que pueda resolver esto de una vez por todas :)
Creo que usar algún método como el de aku''s o el de Guy''s es el camino a seguir, pero hay un par de cosas que tomar de los ejemplos específicos:
La premisa básica sería mostrar tu splash en un hilo separado tan pronto como sea posible. Esa es la forma en que me inclinaría, similar a lo que aku se ilustra, ya que es la forma en que estoy más familiarizado. No estaba al tanto de la función de VB que Guy mencionó. Y, aunque pensaban que era una biblioteca de VB , tenía razón, al final todo era IL. Entonces, incluso si se siente sucio, ¡ no es tan malo! :) Creo que querrás asegurarte de que o bien VB proporciona un hilo separado para esa anulación o que crees una tú mismo: definitivamente investiga eso.
Suponiendo que crea otro hilo para mostrar este splash, deberá tener cuidado con las actualizaciones de UI de subprocesos cruzados. Lo menciono porque mencionó el progreso de actualización. Básicamente, para estar seguro, debe llamar a una función de actualización (que crea) en el formulario de presentación mediante un delegado. Pasa ese delegado a la función Invoke en el objeto de formulario de la pantalla de bienvenida. De hecho, si llama al formulario de bienvenida directamente para actualizar los elementos de progreso / UI, obtendrá una excepción siempre que se ejecute en .Net 2.0 CLR. Como regla general, cualquier elemento de UI en un formulario debe actualizarse mediante el hilo que lo creó, eso es lo que asegura Form.Invoke.
Finalmente, probablemente optaría por crear el splash (si no usaba la sobrecarga de VB) en el método principal de su código. Para mí, esto es mejor que hacer que la forma principal realice la creación del objeto y que esté tan estrechamente ligada a él. Si adoptas ese enfoque, te sugiero que crees una interfaz simple que implemente la pantalla de bienvenida, algo así como IStartupProgressListener, que recibe actualizaciones de progreso de la puesta en marcha a través de una función de miembro. Esto le permitirá intercambiar fácilmente cualquier clase según sea necesario, y desacopla muy bien el código. El formulario de bienvenida también puede saber cuándo cerrarlo si notifica cuando finaliza la puesta en marcha.
Después de buscar en todas las soluciones de Google y SO, este es mi favorito: http://bytes.com/topic/c-sharp/answers/277446-winform-startup-splash-screen
FormSplash.cs:
public partial class FormSplash : Form
{
private static Thread _splashThread;
private static FormSplash _splashForm;
public FormSplash() {
InitializeComponent();
}
/// <summary>
/// Show the Splash Screen (Loading...)
/// </summary>
public static void ShowSplash()
{
if (_splashThread == null)
{
// show the form in a new thread
_splashThread = new Thread(new ThreadStart(DoShowSplash));
_splashThread.IsBackground = true;
_splashThread.Start();
}
}
// called by the thread
private static void DoShowSplash()
{
if (_splashForm == null)
_splashForm = new FormSplash();
// create a new message pump on this thread (started from ShowSplash)
Application.Run(_splashForm);
}
/// <summary>
/// Close the splash (Loading...) screen
/// </summary>
public static void CloseSplash()
{
// need to call on the thread that launched this splash
if (_splashForm.InvokeRequired)
_splashForm.Invoke(new MethodInvoker(CloseSplash));
else
Application.ExitThread();
}
}
Program.cs:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
// splash screen, which is terminated in FormMain
FormSplash.ShowSplash();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// this is probably where your heavy lifting is:
Application.Run(new FormMain());
}
}
FormMain.cs
...
public FormMain()
{
InitializeComponent();
// bunch of database access, form loading, etc
// this is where you could do the heavy lifting of "loading" the app
PullDataFromDatabase();
DoLoadingWork();
// ready to go, now close the splash
FormSplash.CloseSplash();
}
Tuve problemas con la solución Microsoft.VisualBasic
- Worked find en XP, pero en Windows 2003 Terminal Server, el formulario de solicitud principal se mostraba (después de la pantalla de inicio) en segundo plano, y la barra de tareas parpadeaba. Y trayendo una ventana al primer plano / foco en el código es otra lata de gusanos para la que puedes buscar Google / SO.
El truco consiste en crear un hilo separado responsable de mostrar la pantalla de bienvenida.
Cuando ejecuta su aplicación, .net crea el hilo principal y carga el formulario especificado (principal). Para ocultar el trabajo duro, puede ocultar la forma principal hasta que finalice la carga.
Asumiendo que Form1 - es su forma principal y SplashForm es de nivel superior, bordea la forma de bienvenida agradable:
private void Form1_Load(object sender, EventArgs e)
{
Hide();
bool done = false;
ThreadPool.QueueUserWorkItem((x) =>
{
using (var splashForm = new SplashForm())
{
splashForm.Show();
while (!done)
Application.DoEvents();
splashForm.Close();
}
});
Thread.Sleep(3000); // Emulate hardwork
done = true;
Show();
}
En realidad, el mutlithreading aquí no es necesario.
Deje que su lógica empresarial genere un evento cada vez que quiera actualizar la pantalla de inicio.
Luego, deje que su formulario actualice la pantalla de bienvenida de acuerdo con el método enganchado a eventhandler.
Para diferenciar las actualizaciones, puede disparar diferentes eventos o proporcionar datos en una clase heredada de EventArgs.
De esta manera, puedes tener una pantalla de bienvenida agradable y sin ningún dolor de cabeza con múltiples subprocesos.
De hecho, con esto puedes incluso apoyar, por ejemplo, imágenes gif en forma de salpicadura. Para que funcione, llama a Application.DoEvents () en tu controlador:
private void SomethingChanged(object sender, MyEventArgs e)
{
formSplash.Update(e);
Application.DoEvents(); //this will update any animation
}
Esta es una vieja pregunta, pero la encontré al intentar encontrar una solución de pantalla de bienvenida para WPF que pudiera incluir animación.
Esto es lo que finalmente reconstruí:
App.XAML:
<Application Startup="ApplicationStart" …
App.XAML.cs:
void ApplicationStart(object sender, StartupEventArgs e)
{
var thread = new Thread(() =>
{
Dispatcher.CurrentDispatcher.BeginInvoke ((Action)(() => new MySplashForm().Show()));
Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
// call synchronous configuration process
// and declare/get reference to "main form"
thread.Abort();
mainForm.Show();
mainForm.Activate();
}
Me gusta mucho la respuesta de Aku, pero el código es para C # 3.0 y superior, ya que usa una función lambda. Para las personas que necesitan usar el código en C # 2.0, aquí está el código que utiliza un delegado anónimo en lugar de la función lambda. Necesita un formulario de win topmost llamado formSplash
con FormBorderStyle = None
. El parámetro TopMost = True
del formulario es importante porque la pantalla de bienvenida puede parecerse a la que aparece y luego desaparece rápidamente si no está en la parte superior. También elijo StartPosition=CenterScreen
para que parezca lo que una aplicación profesional haría con una pantalla de bienvenida. Si desea un efecto aún más frío, puede usar la propiedad TrasparencyKey
para crear una pantalla de presentación irregular.
private void formMain_Load(object sender, EventArgs e)
{
Hide();
bool done = false;
ThreadPool.QueueUserWorkItem(delegate
{
using (formSplash splashForm = new formSplash())
{
splashForm.Show();
while (!done)
Application.DoEvents();
splashForm.Close();
}
}, null);
Thread.Sleep(2000);
done = true;
Show();
}
No estoy de acuerdo con las otras respuestas que recomiendan WindowsFormsApplicationBase
. En mi experiencia, puede ralentizar tu aplicación. Para ser precisos, mientras ejecuta el constructor de su formulario en paralelo con la pantalla de bienvenida, pospone el evento Mostrado de su formulario.
Considere una aplicación (sin pantalla de salpicaduras) con un constructor que demora 1 segundo y un controlador de eventos en Se muestra que demora 2 segundos. Esta aplicación se puede usar después de 3 segundos.
Pero supongamos que instala una pantalla de bienvenida utilizando WindowsFormsApplicationBase
. Puede pensar que MinimumSplashScreenDisplayTime
de 3 segundos es sensato y no ralentizará su aplicación. Pero, pruébalo, tu aplicación tardará 5 segundos en cargarse.
class App : WindowsFormsApplicationBase
{
protected override void OnCreateSplashScreen()
{
this.MinimumSplashScreenDisplayTime = 3000; // milliseconds
this.SplashScreen = new Splash();
}
protected override void OnCreateMainForm()
{
this.MainForm = new Form1();
}
}
y
public Form1()
{
InitializeComponent();
Shown += Form1_Shown;
Thread.Sleep(TimeSpan.FromSeconds(1));
}
void Form1_Shown(object sender, EventArgs e)
{
Thread.Sleep(TimeSpan.FromSeconds(2));
Program.watch.Stop();
this.textBox1.Text = Program.watch.ElapsedMilliseconds.ToString();
}
Conclusión: no use WindowsFormsApplicationBase
si su aplicación tiene un controlador en el evento Slown. Puede escribir un código mejor que ejecute el splash en paralelo tanto para el constructor como para el evento Shown.
Publiqué un artículo sobre la incorporación de pantalla de bienvenida en la aplicación en codeproject. Es multiproceso y podría ser de su interés
Recomiendo llamar a Activate();
directamente después del último Show();
en la respuesta proporcionada por aku.
Citando MSDN:
Activar un formulario lo pone al frente si esta es la aplicación activa, o muestra el título de la ventana si esta no es la aplicación activa. El formulario debe ser visible para que este método tenga algún efecto.
Si no activa su formulario principal, puede mostrarse detrás de cualquier otra ventana abierta, lo que lo hace parecer un poco tonto.
Una forma simple es utilizar algo así como main ():
<STAThread()> Public Shared Sub Main()
splash = New frmSplash
splash.Show()
'' Your startup code goes here...
UpdateSplashAndLogMessage("Startup part 1 done...")
'' ... and more as needed...
splash.Hide()
Application.Run(myMainForm)
End Sub
Cuando .NET CLR inicia su aplicación, crea un hilo "principal" y comienza a ejecutar su main () en ese hilo. El Application.Run (myMainForm) al final hace dos cosas:
- Inicia el ''bombeo de mensajes'' de Windows, utilizando el hilo que ha estado ejecutando main () como el hilo de la GUI.
- Designa su "formulario principal" como el "formulario de cierre" para la aplicación. Si el usuario cierra ese formulario, el Application.Run () finaliza y el control vuelve a su main (), donde puede hacer el apagado que desee.
No es necesario generar un hilo para cuidar la ventana de bienvenida, y de hecho esta es una mala idea, porque entonces tendría que usar técnicas seguras para subprocesos para actualizar el contenido de splash desde main ().
Si necesita otros subprocesos para realizar operaciones en segundo plano en su aplicación, puede generarlos desde main (). Solo recuerde configurar Thread.IsBackground en True, para que se mueran cuando termine el hilo principal / GUI. De lo contrario, deberá hacer los arreglos para terminar todos sus otros hilos usted mismo o mantendrá su aplicación activa (pero sin GUI) cuando finalice el hilo principal.
private void MainForm_Load(object sender, EventArgs e)
{
FormSplash splash = new FormSplash();
splash.Show();
splash.Update();
System.Threading.Thread.Sleep(3000);
splash.Hide();
}
Lo recibí de Internet en algún lado, pero no puedo encontrarlo de nuevo. Simple pero efectivo.