Domanda

Desidero visualizzare una schermata iniziale durante il caricamento dell'applicazione.Ho un modulo a cui è collegato un controllo della barra delle applicazioni.Desidero che la schermata iniziale venga visualizzata durante il caricamento di questo modulo, il che richiede un po' di tempo poiché accede a un'API del servizio Web per popolare alcuni menu a discesa.Voglio anche eseguire alcuni test di base per le dipendenze prima del caricamento (ovvero, il servizio Web è disponibile, il file di configurazione è leggibile).Con il procedere di ogni fase dell'avvio, desidero aggiornare la schermata iniziale con i progressi.

Ho letto molto sul threading, ma mi sto perdendo da dove dovrebbe essere controllato (il file main() metodo?).Mi manca anche il come Application.Run() funziona, è da qui che dovrebbero essere creati i thread per questo?Ora, se il modulo con il controllo della barra delle applicazioni è il modulo "vivo", lo splash dovrebbe provenire da lì?Non verrebbe comunque caricato fino al completamento del modulo?

Non sto cercando una dispensa sul codice, più un algoritmo/approccio in modo da poterlo capire una volta per tutte :)

È stato utile?

Soluzione

Bene, per un'app ClickOnce che ho distribuito in passato, abbiamo utilizzato il file Microsoft.VisualBasic namespace per gestire il threading della schermata iniziale.È possibile fare riferimento e utilizzare il file Microsoft.VisualBasic assembly da C# in .NET 2.0 e fornisce molti servizi interessanti.

  1. Fai ereditare il modulo principale da Microsoft.VisualBasic.WindowsFormsApplicationBase
  2. Sostituisci il metodo "OnCreateSplashScreen" in questo modo:

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

Molto semplice, mostra il tuo SplashForm (che devi creare) mentre il caricamento è in corso, quindi lo chiude automaticamente una volta completato il caricamento del modulo principale.

Questo rende davvero le cose semplici, e il VisualBasic.WindowsFormsApplicationBase è ovviamente ben testato da Microsoft e ha molte funzionalità che possono semplificarti la vita in Winforms, anche in un'applicazione che è al 100% C#.

Alla fine della giornata, è tutto IL e bytecode comunque, quindi perché non usarlo?

Altri suggerimenti

Il trucco sta nel creare un thread separato responsabile della visualizzazione della schermata iniziale.
Quando esegui l'app .net crea il thread principale e carica il modulo (principale) specificato.Per nascondere il duro lavoro puoi nascondere il modulo principale fino al termine del caricamento.

Supponendo che Form1 - sia il tuo modulo principale e SplashForm sia di livello superiore, confina con un bel modulo splash:

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

Dopo aver cercato soluzioni su Google e SO, questa è la mia preferita: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();
    }
}

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

Ho avuto problemi con il Microsoft.VisualBasic soluzione: la ricerca funzionava su XP, ma su Windows 2003 Terminal Server, il modulo dell'applicazione principale veniva visualizzato (dopo la schermata iniziale) in background e la barra delle applicazioni lampeggiava.E portare una finestra in primo piano/mettere a fuoco nel codice è tutta un'altra scatola di worm per cui puoi cercare su Google/SO.

Questa è una vecchia domanda, ma continuavo a incontrarla mentre cercavo di trovare una soluzione per la schermata iniziale con thread per WPF che potesse includere l'animazione.

Ecco cosa alla fine ho messo insieme:

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

Consiglio di chiamare Activate(); direttamente dopo l'ultimo Show(); nella risposta fornita da aku.

Citando MSDN:

L'attivazione di un modulo lo porta sul davanti se questa è l'applicazione attiva o lampeggia la didascalia della finestra se questa non è l'applicazione attiva.Il modulo deve essere visibile affinché questo metodo abbia alcun effetto.

Se non attivi il modulo principale, potrebbe essere visualizzato dietro qualsiasi altra finestra aperta, facendolo sembrare un po' sciocco.

Penso che usando un metodo come aku O Ragazzi è la strada da percorrere, ma un paio di cose da togliere dagli esempi specifici:

  1. La premessa di base sarebbe mostrare il tuo splash su un thread separato il prima possibile.Questo è il modo in cui mi sposterei, simile a quello illustrato da aku, poiché è il modo con cui ho più familiarità.Non ero a conoscenza della funzione VB menzionata da Guy.E, anche se fosse un V.B biblioteca, ha ragione: alla fine è tutto IL.Quindi, anche se sembra sporco non è poi così male!:) Penso che vorrai essere sicuro che VB fornisca un thread separato per quell'override o che ne crei uno tu stesso - cercalo sicuramente.

  2. Supponendo che tu crei un altro thread per visualizzare questo splash, dovrai fare attenzione agli aggiornamenti dell'interfaccia utente tra thread.Ne parlo perché hai menzionato l'aggiornamento dei progressi.Fondamentalmente, per sicurezza, devi chiamare una funzione di aggiornamento (da te creata) nel modulo splash utilizzando un delegato.Passi quel delegato al Invocare funzione sull'oggetto modulo della schermata iniziale.Infatti, se chiami direttamente il modulo splash per aggiornare gli elementi di avanzamento/interfaccia utente su di esso, otterrai un'eccezione a condizione che tu sia in esecuzione su .Net 2.0 CLR.Come regola generale, qualsiasi elemento dell'interfaccia utente in un modulo deve essere aggiornato dal thread che lo ha creato: questo è ciò che assicura Form.Invoke.

Infine, probabilmente opterei per creare lo splash (se non si utilizza l'overload VB) nel metodo main del codice.Per me questo è meglio che avere la forma principale che esegue la creazione dell'oggetto ed essere così strettamente legata ad esso.Se adotti questo approccio, suggerirei di creare una semplice interfaccia implementata dalla schermata iniziale, qualcosa come IStartupProgressListener, che riceve aggiornamenti sull'avanzamento dell'avvio tramite una funzione membro.Ciò ti consentirà di scambiare facilmente dentro e fuori entrambe le classi secondo necessità e disaccoppia bene il codice.Il modulo splash può anche sapere quando chiudersi se si avvisa quando l'avvio è completo.

Un modo semplice è usare qualcosa di simile come 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

Quando .NET CLR avvia l'applicazione, crea un thread "principale" e inizia a eseguire main() su quel thread.Application.Run(myMainForm) alla fine fa due cose:

  1. Avvia il "message pump" di Windows, utilizzando il thread che ha eseguito main() come thread della GUI.
  2. Designa il tuo "modulo principale" come "modulo di chiusura" per l'applicazione.Se l'utente chiude il modulo, allora Application.Run() termina e il controllo ritorna a main(), dove puoi eseguire qualsiasi arresto desideri.

Non è necessario generare un thread per occuparsi della finestra splash, e in effetti questa è una cattiva idea, perché in tal caso dovresti utilizzare tecniche thread-safe per aggiornare il contenuto splash da main().

Se hai bisogno di altri thread per eseguire operazioni in background nella tua applicazione, puoi generarli da main().Ricorda solo di impostare Thread.IsBackground su True, in modo che moriranno quando termina il thread principale/GUI.Altrimenti dovrai provvedere a terminare tu stesso tutti gli altri thread, altrimenti manterranno viva la tua applicazione (ma senza GUI) quando termina il thread principale.

Ho pubblicato un articolo sull'incorporazione della schermata iniziale nell'applicazione su codeproject.È multithread e potrebbe essere di tuo interesse

Ancora un'altra schermata iniziale in C#

private void MainForm_Load(object sender, EventArgs e)
{
     FormSplash splash = new FormSplash();
     splash.Show();
     splash.Update();
     System.Threading.Thread.Sleep(3000);
     splash.Hide();
}

L'ho preso da qualche parte su Internet ma non riesco a trovarlo di nuovo.Semplice ma allo stesso tempo efficace.

Mi piace molto la risposta di Aku, ma il codice è per C# 3.0 e versioni successive poiché utilizza una funzione lambda.Per gli utenti che necessitano di utilizzare il codice in C# 2.0, ecco il codice che utilizza il delegato anonimo anziché la funzione lambda.Hai bisogno di un winform più in alto chiamato formSplash con FormBorderStyle = None.IL TopMost = True Il parametro del modulo è importante perché la schermata iniziale potrebbe apparire come appare e poi scomparire rapidamente se non è in alto.Scelgo anch'io StartPosition=CenterScreen quindi sembra cosa farebbe un'app professionale con una schermata iniziale.Se vuoi un effetto ancora più cool, puoi usare il TrasparencyKey proprietà per creare uno splash screen di forma irregolare.

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

Non sono d'accordo con le altre risposte che raccomandano WindowsFormsApplicationBase.Nella mia esperienza, può rallentare la tua app.Per essere precisi, mentre esegue il costruttore del modulo in parallelo con la schermata iniziale, posticipa l'evento Shown del modulo.

Considera un'app (senza schermata iniziale) con un costruttore che impiega 1 secondo e un gestore eventi su Shown che impiega 2 secondi.Questa app è utilizzabile dopo 3 secondi.

Ma supponiamo di installare una schermata iniziale utilizzando WindowsFormsApplicationBase.Potresti pensare MinimumSplashScreenDisplayTime di 3 secondi è sensato e non rallenterà la tua app.Ma provalo, la tua app ora impiegherà 5 secondi per caricarsi.


class App : WindowsFormsApplicationBase
{
    protected override void OnCreateSplashScreen()
    {
        this.MinimumSplashScreenDisplayTime = 3000; // milliseconds
        this.SplashScreen = new Splash();
    }

    protected override void OnCreateMainForm()
    {
        this.MainForm = new Form1();
    }
}

E

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

Conclusione:non usare WindowsFormsApplicationBase se la tua app ha un gestore dell'evento Slown.Puoi scrivere un codice migliore che esegua lo splash in parallelo sia al costruttore che all'evento Shown.

In realtà il multithreading qui non è necessario.

Lascia che la logica del tuo business generi un evento ogni volta che desideri aggiornare la schermata iniziale.

Quindi lascia che il tuo modulo aggiorni la schermata iniziale di conseguenza nel metodo agganciato all'eventhandler.

Per differenziare gli aggiornamenti puoi attivare eventi diversi o fornire dati in una classe ereditata da EventArgs.

In questo modo puoi avere una bella schermata iniziale modificabile senza alcun mal di testa multithreading.

In realtà con questo puoi anche supportare, ad esempio, l'immagine GIF su un modulo splash.Affinché funzioni, chiama Application.DoEvents() nel tuo gestore:

private void SomethingChanged(object sender, MyEventArgs e)
{
    formSplash.Update(e);
    Application.DoEvents(); //this will update any animation
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top