2008-09-08 6 views
58

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.Pantalla de presentación de subprocesos múltiples en C#?

He estado leyendo mucho sobre el enhebrado, 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 en busca de una limosna código, más de un algoritmo/método por lo que se puede resolver esto de una vez por todas :)

Respuesta

47

Bueno, para una aplicación ClickOnce que desplegué en el pasado, hemos utilizado el espacio de nombres Microsoft.VisualBasic para manejar el enhebrado 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.

  1. Tienen la forma principal hereda de Microsoft.VisualBasic.WindowsFormsApplicationBase
  2. reemplazar el método "OnCreateSplashScreen" de esta manera:

    protected override void OnCreateSplashScreen() 
    { 
        this.SplashScreen = new SplashForm(); 
        this.SplashScreen.TopMost = true; 
    } 
    

muy sencillo, que muestra su SplashForm (que necesita para crear), mientras que la carga está pasando, a continuación, se cierra automáticamente una vez que el la forma principal ha completado la carga.

Esto realmente hace las cosas simples, y VisualBasic.WindowsFormsApplicationBase por supuesto está bien probado por Microsoft y tiene mucha funcionalidad que puede 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?

+2

Esta fue la solución más elegante ya que usa una funcionalidad integrada que la biblioteca de Visual Basic abstrae muy bien. No sabía cómo crear un programa VB ApplicationBase, por lo que se necesitaban más investigaciones ... Añadiré más información para completar. Gracias por todos los comentarios! –

+0

He tenido que arreglar algunas pantallas de bienvenida manuales mal redactadas. Usar la funcionalidad admitida como Guy sugiere es definitivamente la mejor manera de hacerlo. :) –

+1

Buena idea, pero no funciona en el marco compacto. – Damien

6

Una forma simple es el uso 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 el .NET CLR comience su aplicación, se crea un hilo 'principal' y comienza a ejecutar su main() en ese hilo. El Application.Run (myMainForm) al final hace dos cosas:

  1. Inicia 'suministro de mensajes' de Windows, utilizando el hilo que ha estado ejecutando principal() como el hilo de interfaz gráfica de usuario.
  2. Designa su "formulario principal" como el "formulario de cierre" de 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 hay necesidad de engendrar un hilo para cuidar la ventana de bienvenida, y de hecho esta es una mala idea, porque entonces tendrías que usar técnicas de subprocesamiento para actualizar los contenidos del 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.

47

El truco consiste en crear 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.

Suponiendo que Form1 - es su forma principal y SplashForm es de primer nivel, borderles buen formulario de inicio:

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(); 
} 
+1

Me encanta este método, gracias amigo, lo uso a menudo ahora. – Echostorm

+0

Este es probablemente el mejor método que he visto para hacer pantallas de presentación. Funciona BELLAMENTE en .NET compacto con mi aplicación de PC de bolsillo. – Damien

+5

@aku: ¿Es una buena idea usar Application.DoEvents() en la aplicación? Leí mucho acerca de no usar esta función en internet como http://blogs.msdn.com/jfoscoding/archive/2005/08/06/448560.aspx –

7

Creo usando algún método como aku's o Guy's es el camino a seguir, pero un par de cosas que le quita a los ejemplos específicos:

  1. La premisa básica sería la de mostrar su chapoteo en una 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 se trata de una biblioteca VB, tiene razón, al final todo es IL. Por lo tanto, 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.

  2. Suponiendo que crea otro subproceso 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 a 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.

Por último, probablemente optaría por crear el splash (si no se usa 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.

9

Recomiendo llamar Activate(); directamente después de la última Show(); en la respuesta proporcionada por aku.

Citando MSDN:

Activación de una forma lo trae al frente si este es el aplicación activa o parpadea el título de ventana si este no es el aplicación activa. El formulario debe estar visible para que este método tenga algún efecto.

Si no activa su forma principal, que puede aparecer detrás de cualquier otra ventana abierta, haciendo que parezca un poco tonto.

5

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

Yet Another Splash Screen in C#

9

Ésta es una vieja pregunta, pero yo seguía viniendo a través de ella al tratar de encontrar una solución pantalla de bienvenida roscado para WPF que podría incluir la animación.

Esto es lo que en última instancia monté:

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(); 
    } 
14

Después de mirar todo Google y SO de soluciones, 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 - Trabajado encontrar en XP, pero en Windows 2003 Terminal Server, el formulario de solicitud principal aparecería (después de la pantalla de presentación) en segundo plano, y la barra de tareas parpadearía. Y trayendo una ventana al primer plano/foco en el código es otra lata de gusanos para la que puedes buscar Google/SO.

+0

¡Gracias, buena solución! – nightcoder

5
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 lugar pero parece que no puede encontrarlo de nuevo. Simple pero efectivo.

4

Me gusta mucho la respuesta de Aku, pero el código es para C# 3.0 y más, 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 una forma de win más alta llamada formSplash con FormBorderStyle = None. El parámetro TopMost = True del formulario es importante porque la pantalla de presentación puede parecerse a la que aparece y luego desaparece rápidamente si no está en la parte superior. También elijo StartPosition=CenterScreen por lo que parece 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(); 
    } 
3

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 se muestra un controlador de eventos que demora 2 segundos. Esta aplicación se puede usar después de 3 segundos.

Pero suponga que instala una pantalla de presentación usando WindowsFormsApplicationBase. Puede pensar 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 utilice WindowsFormsApplicationBase si su aplicación tiene un controlador en el caso Slown. Puede escribir un código mejor que ejecute el splash en paralelo tanto para el constructor como para el evento Shown.

0

En realidad, aquí no es necesario realizar un mutlithreading.

Deje que su lógica empresarial genere un evento cada vez que quiera actualizar la pantalla de presentación.

Luego, deje que su formulario actualice la pantalla de bienvenida según 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 usted puede tener una agradable pantalla de cambio sin ningún dolor de cabeza con múltiples hilos.

De hecho, con esto incluso puede apoyar, por ejemplo, la imagen 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 
} 
Cuestiones relacionadas