Pergunta

Quero que uma tela inicial seja exibida enquanto o aplicativo está carregando.Eu tenho um formulário com um controle da bandeja do sistema vinculado a ele.Quero que a tela inicial seja exibida enquanto este formulário é carregado, o que leva um pouco de tempo, pois ele acessa uma API de serviço da Web para preencher alguns menus suspensos.Também quero fazer alguns testes básicos de dependências antes de carregar (ou seja, o serviço da web está disponível, o arquivo de configuração está legível).À medida que avança cada fase da inicialização, quero atualizar a tela inicial com o progresso.

Tenho lido muito sobre threading, mas estou me perdendo sobre onde isso deve ser controlado (o main() método?).Também estou sentindo falta de como Application.Run() funciona, é aqui que os threads para isso devem ser criados?Agora, se o formulário com o controle da bandeja do sistema for o formulário "vivo", o respingo deveria vir daí?Ele não carregaria até que o formulário fosse preenchido?

Não estou procurando um folheto de código, mas sim um algoritmo/abordagem para que eu possa descobrir isso de uma vez por todas :)

Foi útil?

Solução

Bem, para um aplicativo ClickOnce que implantei no passado, usamos o Microsoft.VisualBasic namespace para lidar com o encadeamento da tela inicial.Você pode consultar e usar o Microsoft.VisualBasic assembly de C# no .NET 2.0 e fornece muitos serviços interessantes.

  1. Faça com que o formulário principal seja herdado de Microsoft.VisualBasic.WindowsFormsApplicationBase
  2. Substitua o método "OnCreateSplashScreen" da seguinte forma:

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

Muito simples, ele mostra seu SplashForm (que você precisa criar) durante o carregamento e o fecha automaticamente quando o carregamento do formulário principal é concluído.

Isso realmente torna as coisas simples, e o VisualBasic.WindowsFormsApplicationBase é claro que é bem testado pela Microsoft e possui muitas funcionalidades que podem facilitar muito sua vida no Winforms, mesmo em um aplicativo 100% C#.

No final das contas, é tudo IL e bytecode de qualquer forma, então por que não usá-lo?

Outras dicas

O truque é criar um thread separado responsável pela exibição da tela inicial.
Quando você executa seu aplicativo .net, cria o thread principal e carrega o formulário (principal) especificado.Para ocultar o trabalho árduo, você pode ocultar o formulário principal até que o carregamento seja concluído.

Supondo que Form1 - seja o seu formulário principal e SplashForm seja de nível superior, há um belo formulário inicial:

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

Depois de procurar soluções em todo o Google e SO, este é o meu 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();
    }
}

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

tive problemas com o Microsoft.VisualBasic solução - Funcionou no XP, mas no Windows 2003 Terminal Server, o formulário principal do aplicativo aparecia (após a tela inicial) em segundo plano e a barra de tarefas piscava.E trazer uma janela para o primeiro plano/foco no código é uma outra lata de worms que você pode pesquisar no Google/SO.

Essa é uma pergunta antiga, mas continuei me deparando com ela ao tentar encontrar uma solução de tela inicial encadeada para WPF que pudesse incluir animação.

Aqui está o que eu finalmente juntei:

Aplicativo.XAML:

<Application Startup="ApplicationStart" …

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

Eu recomendo ligar Activate(); diretamente após o último Show(); na resposta fornecida por aku.

Citando MSDN:

A ativação de um formulário o traz para a frente se este for o aplicativo ativo ou pisca a legenda da janela se esse não for o aplicativo ativo.O formulário deve ser visível para que esse método tenha algum efeito.

Se você não ativar seu formulário principal, ele poderá ser exibido atrás quaisquer outras janelas abertas, fazendo com que pareça um pouco bobo.

Eu acho que usando algum método como Aku S ou Pessoal é o caminho a seguir, mas algumas coisas a serem tiradas dos exemplos específicos:

  1. A premissa básica seria mostrar seu splash em um tópico separado o mais rápido possível.Essa é a forma como eu me inclinaria, semelhante ao ilustrado por aku, já que é a forma com a qual estou mais familiarizado.Eu não conhecia a função VB que Guy mencionou.E, mesmo pensando que é um VB biblioteca, ele está certo - no final é tudo IL.Então, mesmo que pareça sujo não é tão ruim assim!:) Acho que você vai querer ter certeza de que o VB fornece um thread separado para essa substituição ou que você mesmo cria um - definitivamente pesquise isso.

  2. Supondo que você crie outro thread para exibir esse splash, você deverá ter cuidado com as atualizações da interface do usuário entre threads.Menciono isso porque você mencionou o progresso da atualização.Basicamente, por segurança, você precisa chamar uma função de atualização (que você cria) no formulário inicial usando um delegado.Você passa esse delegado para o Invocar função no objeto de formulário da sua tela inicial.Na verdade, se você chamar o formulário inicial diretamente para atualizar os elementos de progresso/UI nele, você receberá uma exceção, desde que esteja executando no .Net 2.0 CLR.Como regra geral, qualquer elemento de UI em um formulário deve ser atualizado pelo thread que o criou – é isso que Form.Invoke garante.

Finalmente, eu provavelmente optaria por criar o splash (se não estiver usando a sobrecarga de VB) no método principal do seu código.Para mim, isso é melhor do que fazer com que o formulário principal execute a criação do objeto e esteja tão fortemente vinculado a ele.Se você adotar essa abordagem, sugiro criar uma interface simples que a tela inicial implemente - algo como IStartupProgressListener - que recebe atualizações de progresso de inicialização por meio de uma função de membro.Isso permitirá que você troque facilmente qualquer classe conforme necessário e desacople bem o código.O formulário inicial também pode saber quando fechar se você notificar quando a inicialização for concluída.

Uma maneira simples é usar algo assim 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

Quando o .NET CLR inicia seu aplicativo, ele cria um thread 'principal' e começa a executar seu main() nesse thread.O Application.Run(myMainForm) no final faz duas coisas:

  1. Inicia a 'bomba de mensagens' do Windows, usando o thread que está executando main() como thread da GUI.
  2. Designa seu 'formulário principal' como o 'formulário de desligamento' do aplicativo.Se o usuário fechar esse formulário, o Application.Run() termina e o controle retorna para o seu main(), onde você pode fazer qualquer desligamento que desejar.

Não há necessidade de gerar um thread para cuidar da janela inicial e, na verdade, isso é uma má ideia, porque então você teria que usar técnicas seguras de thread para atualizar o conteúdo inicial de main().

Se você precisar de outros threads para realizar operações em segundo plano em seu aplicativo, poderá gerá-los a partir de main().Apenas lembre-se de definir Thread.IsBackground como True, para que eles morram quando o thread principal/GUI terminar.Caso contrário, você mesmo terá que providenciar o encerramento de todos os outros threads, ou eles manterão seu aplicativo ativo (mas sem GUI) quando o thread principal terminar.

Publiquei um artigo sobre incorporação de tela inicial no aplicativo no codeproject.É multithread e pode ser do seu interesse

Mais uma tela inicial em C#

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

Peguei isso da Internet em algum lugar, mas não consigo encontrá-lo novamente.Simples, mas eficaz.

Gosto muito da resposta do Aku, mas o código é para C# 3.0 e superior, pois usa uma função lambda.Para pessoas que precisam usar o código em C# 2.0, aqui está o código usando delegado anônimo em vez da função lambda.Você precisa de um winform superior chamado formSplash com FormBorderStyle = None.O TopMost = True O parâmetro do formulário é importante porque a tela inicial pode parecer que aparece e depois desaparece rapidamente se não estiver no topo.eu também escolho StartPosition=CenterScreen então parece o que um aplicativo profissional faria com uma tela inicial.Se quiser um efeito ainda mais legal, você pode usar o TrasparencyKey propriedade para criar uma tela inicial de formato 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();
  }

Não concordo com as outras respostas que recomendam WindowsFormsApplicationBase.Na minha experiência, isso pode tornar seu aplicativo lento.Para ser mais preciso, embora ele execute o construtor do seu formulário em paralelo com a tela inicial, ele adia o evento Shown do seu formulário.

Considere um aplicativo (sem tela inicial) com um construtor que leva 1 segundo e um manipulador de eventos em Shown que leva 2 segundos.Este aplicativo pode ser usado após 3 segundos.

Mas suponha que você instale uma tela inicial usando WindowsFormsApplicationBase.Você pode pensar MinimumSplashScreenDisplayTime de 3 segundos é sensato e não retardará seu aplicativo.Mas experimente, seu aplicativo levará 5 segundos para carregar.


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

Conclusão:não use WindowsFormsApplicationBase se seu aplicativo tiver um manipulador no evento Slown.Você pode escrever um código melhor que execute o splash em paralelo ao construtor e ao evento Shown.

Na verdade, a leitura múltipla aqui não é necessária.

Deixe sua lógica de negócios gerar um evento sempre que desejar atualizar a tela inicial.

Em seguida, deixe seu formulário atualizar a tela inicial de acordo com o método conectado ao manipulador de eventos.

Para diferenciar atualizações você pode disparar eventos diferentes ou fornecer dados em uma classe herdada de EventArgs.

Dessa forma, você pode ter uma boa tela inicial em mudança sem qualquer dor de cabeça com multithreading.

Na verdade, com isso você pode até suportar, por exemplo, uma imagem gif em um formulário inicial.Para que funcione, chame Application.DoEvents() em seu manipulador:

private void SomethingChanged(object sender, MyEventArgs e)
{
    formSplash.Update(e);
    Application.DoEvents(); //this will update any animation
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top