Este post
é para salvar aqueles que gastam horas desenvolvendo programa em WPF, com todos aqueles componentes para mostrar o que está acontecendo no
processamento, mas que na hora que executa o programa a tela do programa
congela e só realizar a modificação nos valores dos controles ao final do
processamento. Passei por isso! E não foi só uma vez não. :)
Vamos lá...
Seu aplicativo WPF precisa executa tarefas que consomem grandes quantidades de tempo, mas que durante a execução dessa tarefa, que é em um processo separado, terá que permite manter comunicação com a interface do usuário, atualizando controle, enquanto a tarefa é executada.
O Framework possue o objeto BackgroundWorker. O BackgroundWorker é recomendado para executar tarefas que consomem bastante tempo, mas em processo dedicado e separado da Thread principal, deixando o UI interativo com o usuário.
Como implementar?
1. Crie uma variável de classe do tipo BackgroundWorker.
BackgroundWorker bw = new BackgroundWorker ();
Especifique que a operação em background irá permitir o cancelamento e comunicação do progresso.
bw.WorkerSupportsCancellation = true;
bw.WorkerReportsProgress = true;
3.Crie um manipulador de eventos para o evento DoWork.
O manipulador de eventos DoWork é onde você executa a operação que irá demorar. Quaisquer valores que são passados para a operação de fundo são passados via parametro DoWorkEventArgs do objeto que é passado para o manipulador de eventos.
Para informar o progresso da execução do código você precisará fazer uma chamada para o método ReportProgress e passar o percentual da conclusão de 0 a 100. Chamar o método ReportProgress gera disparo do evento ProgressChanged, que trabalhará separadamente.
Para determinar se existe um pedido pendente para cancelar a operação em background, verifique a propriedade CancellationPending do objeto BackgroundWorker. Se a propriedade for verdadeiro, o método CancelAsync foi chamado. Defina o objeto BackgroundWorker de propriedade Cancel como true e interrompa a execução.
Para passar dados de volta para o processo de chamada, defina a propriedade resultado do objeto DoWorkEventArgs que é passado para o manipulador de eventos. Este valor pode ser acessado quando o evento RunWorkerCompleted é acionado no final da operação.
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; (i <= 10); i++)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
System.Threading.Thread.Sleep(500);
worker.ReportProgress((i * 10));
}
}
}
4.Crie um método para o evento ProgressChanged do background.
No método do evento ProgressChanged, adicione código para indicar o progresso e atualizar a interface do usuário.
Para determinar qual a percentagem de a operação estar concluída, verifique a propriedade do objeto ProgressPercentage do paramentro e, e.ProgressChangedEventArgs que foi passado para o manipulador de eventos.
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.tbProgress.Text = (e.ProgressPercentage.ToString() + "%");
}
5.Crie um método para o evento RunWorkerCompleted.
O evento RunWorkerCompleted é gerado quando a thread for concluída. Independente da operação de fundo ter sido concluído com sucesso,erro ou cancelado, ele atualizará a interface do usuário.
Para determinar se ocorreu um erro, verifique a propriedade Erro do objeto RunWorkerCompletedEventArgs que foi passado para o metodo do evento. Se ocorrer um erro, esta propriedade contém as informações de exceção.
Se a operação permite o cancelamento e você quiser verificar se a operação foi cancelada, verifique a propriedade Cancelled do objeto RunWorkerCompletedEventArgs que foi passado para o método. Se a propriedade for verdadeiro, o método CancelAsync é chamado.
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
this.tbProgress.Text = "Canceled!";
}
else if (!(e.Error == null))
{
this.tbProgress.Text = ("Error: " + e.Error.Message);
}
else
{
this.tbProgress.Text = "Done!";
}
}
6. Adicone os métodos dos eventos de BackgroundWorker.
O exemplo a seguir mostra como adicionar os métodos dos eventos DoWork, ProgressChanged, e RunWorkerCompleted.
bw.DoWork +=
new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged +=
new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
7. Para iniciar a execução do código, o método RunWorkerAsync deve ser chamado.
private void buttonStart_Click(object sender, RoutedEventArgs e)
{
if (bw.IsBusy != true)
{
bw.RunWorkerAsync();
}
}
8. Para cancelar a operação, chame o método CancelAsync.
private void buttonCancel_Click(object sender, RoutedEventArgs e)
{
if (bw.WorkerSupportsCancellation == true)
{
bw.CancelAsync();
}
}
Exemplo 1 - WPF
O exemplo a seguir mostra como usar a classe BackgroundWorker. No exemplo, a operação de fundo executa o método do sono e relatórios de progresso para a interface do usuário. O trabalho de fundo é configurado para permitir o cancelamento.
Classe:
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace BackgroundWorker_CS
{
public partial class Page : UserControl
{
private BackgroundWorker bw = new BackgroundWorker();
public Page()
{
InitializeComponent();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
private void buttonStart_Click(object sender, RoutedEventArgs e)
{
if (bw.IsBusy != true)
{
bw.RunWorkerAsync();
}
}
private void buttonCancel_Click(object sender, RoutedEventArgs e)
{
if (bw.WorkerSupportsCancellation == true)
{
bw.CancelAsync();
}
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; (i <= 10); i++)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
System.Threading.Thread.Sleep(500);
worker.ReportProgress((i * 10));
}
}
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
this.tbProgress.Text = "Canceled!";
}
else if (!(e.Error == null))
{
this.tbProgress.Text = ("Error: " + e.Error.Message);
}
else
{
this.tbProgress.Text = "Done!";
}
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.tbProgress.Text = (e.ProgressPercentage.ToString() + "%");
}
}
}
XAML:
Vamos lá...
Seu aplicativo WPF precisa executa tarefas que consomem grandes quantidades de tempo, mas que durante a execução dessa tarefa, que é em um processo separado, terá que permite manter comunicação com a interface do usuário, atualizando controle, enquanto a tarefa é executada.
O Framework possue o objeto BackgroundWorker. O BackgroundWorker é recomendado para executar tarefas que consomem bastante tempo, mas em processo dedicado e separado da Thread principal, deixando o UI interativo com o usuário.
Como implementar?
1. Crie uma variável de classe do tipo BackgroundWorker.
BackgroundWorker bw = new BackgroundWorker ();
Especifique que a operação em background irá permitir o cancelamento e comunicação do progresso.
bw.WorkerSupportsCancellation = true;
bw.WorkerReportsProgress = true;
3.Crie um manipulador de eventos para o evento DoWork.
O manipulador de eventos DoWork é onde você executa a operação que irá demorar. Quaisquer valores que são passados para a operação de fundo são passados via parametro DoWorkEventArgs do objeto que é passado para o manipulador de eventos.
Para informar o progresso da execução do código você precisará fazer uma chamada para o método ReportProgress e passar o percentual da conclusão de 0 a 100. Chamar o método ReportProgress gera disparo do evento ProgressChanged, que trabalhará separadamente.
Para determinar se existe um pedido pendente para cancelar a operação em background, verifique a propriedade CancellationPending do objeto BackgroundWorker. Se a propriedade for verdadeiro, o método CancelAsync foi chamado. Defina o objeto BackgroundWorker de propriedade Cancel como true e interrompa a execução.
Para passar dados de volta para o processo de chamada, defina a propriedade resultado do objeto DoWorkEventArgs que é passado para o manipulador de eventos. Este valor pode ser acessado quando o evento RunWorkerCompleted é acionado no final da operação.
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; (i <= 10); i++)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
System.Threading.Thread.Sleep(500);
worker.ReportProgress((i * 10));
}
}
}
4.Crie um método para o evento ProgressChanged do background.
No método do evento ProgressChanged, adicione código para indicar o progresso e atualizar a interface do usuário.
Para determinar qual a percentagem de a operação estar concluída, verifique a propriedade do objeto ProgressPercentage do paramentro e, e.ProgressChangedEventArgs que foi passado para o manipulador de eventos.
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.tbProgress.Text = (e.ProgressPercentage.ToString() + "%");
}
5.Crie um método para o evento RunWorkerCompleted.
O evento RunWorkerCompleted é gerado quando a thread for concluída. Independente da operação de fundo ter sido concluído com sucesso,erro ou cancelado, ele atualizará a interface do usuário.
Para determinar se ocorreu um erro, verifique a propriedade Erro do objeto RunWorkerCompletedEventArgs que foi passado para o metodo do evento. Se ocorrer um erro, esta propriedade contém as informações de exceção.
Se a operação permite o cancelamento e você quiser verificar se a operação foi cancelada, verifique a propriedade Cancelled do objeto RunWorkerCompletedEventArgs que foi passado para o método. Se a propriedade for verdadeiro, o método CancelAsync é chamado.
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
this.tbProgress.Text = "Canceled!";
}
else if (!(e.Error == null))
{
this.tbProgress.Text = ("Error: " + e.Error.Message);
}
else
{
this.tbProgress.Text = "Done!";
}
}
6. Adicone os métodos dos eventos de BackgroundWorker.
O exemplo a seguir mostra como adicionar os métodos dos eventos DoWork, ProgressChanged, e RunWorkerCompleted.
bw.DoWork +=
new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged +=
new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
7. Para iniciar a execução do código, o método RunWorkerAsync deve ser chamado.
private void buttonStart_Click(object sender, RoutedEventArgs e)
{
if (bw.IsBusy != true)
{
bw.RunWorkerAsync();
}
}
8. Para cancelar a operação, chame o método CancelAsync.
private void buttonCancel_Click(object sender, RoutedEventArgs e)
{
if (bw.WorkerSupportsCancellation == true)
{
bw.CancelAsync();
}
}
Exemplo 1 - WPF
O exemplo a seguir mostra como usar a classe BackgroundWorker. No exemplo, a operação de fundo executa o método do sono e relatórios de progresso para a interface do usuário. O trabalho de fundo é configurado para permitir o cancelamento.
Classe:
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace BackgroundWorker_CS
{
public partial class Page : UserControl
{
private BackgroundWorker bw = new BackgroundWorker();
public Page()
{
InitializeComponent();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
private void buttonStart_Click(object sender, RoutedEventArgs e)
{
if (bw.IsBusy != true)
{
bw.RunWorkerAsync();
}
}
private void buttonCancel_Click(object sender, RoutedEventArgs e)
{
if (bw.WorkerSupportsCancellation == true)
{
bw.CancelAsync();
}
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; (i <= 10); i++)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
System.Threading.Thread.Sleep(500);
worker.ReportProgress((i * 10));
}
}
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
this.tbProgress.Text = "Canceled!";
}
else if (!(e.Error == null))
{
this.tbProgress.Text = ("Error: " + e.Error.Message);
}
else
{
this.tbProgress.Text = "Done!";
}
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.tbProgress.Text = (e.ProgressPercentage.ToString() + "%");
}
}
}
XAML:
Exemplo 2 - Delegate
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
args.Result = CalculationMethod();
};
worker.RunWorkerCompleted += delegate(object s,
RunWorkerCompletedEventArgs args)
{
object result =
args.Result;
};
worker.ProgressChanged += delegate(object s, ProgressChangedEventArgs args)
{
int percentage = args.ProgressPercentage;
};
worker.RunWorkerAsync();
Usando o Dispatcher para acessar os controles
em outro processo
Você pode querer alterar a interface do usuário a partir de um processo de trabalho. Por exemplo, você pode querer habilitar ou desabilitar botões, ou mostrar uma ProgressBar que fornece informações sobre o andamento da execução, que é feito pelo método ReportProgress. O modelo de encadeamento WPF fornece a classe Dispatcher para chamadas de thread cruzado. Ao utilizar o Dispatcher, você pode atualizar de forma segura a interface do usuário de threads de trabalho que estão rodando em background.
Você pode obter uma referência para o objeto Dispacther para um elemento da interface do usuário a partir de sua propriedade Dispatcher.
Você pode querer alterar a interface do usuário a partir de um processo de trabalho. Por exemplo, você pode querer habilitar ou desabilitar botões, ou mostrar uma ProgressBar que fornece informações sobre o andamento da execução, que é feito pelo método ReportProgress. O modelo de encadeamento WPF fornece a classe Dispatcher para chamadas de thread cruzado. Ao utilizar o Dispatcher, você pode atualizar de forma segura a interface do usuário de threads de trabalho que estão rodando em background.
Você pode obter uma referência para o objeto Dispacther para um elemento da interface do usuário a partir de sua propriedade Dispatcher.
System.Windows.Threading.Dispatcher aDisp = Button1.Dispatcher;
Dispatcher fornece dois métodos principais que você irá usar; Invoke e BeginInvoke. Ambos os métodos permite que você chame o método com segurança. O método BeginInvoke permite que você chame o método de forma assíncrona, e o método Invoke permite que você chame o método de forma síncrona.
Vamos dizer que eu tenho uma tarefa demorada e que eu quero executar esta tarefa demorada uma Thread, mas eu também quero mostrar um diálogo de progresso que mostra uma mensagem e a porcentagem da tarefa concluída. Eu também quero permitir que o usuário cancelar o processo a qualquer momento. Então, a primeira coisa que precisa fazer é criar minha janela de diálogo de progresso que tem um controle para mostrar o percentual concluído, uma barra de progresso para mostrar graficamente o progresso, e um botão Cancelar para que o usuário possa cancelar o processo.
O código ficaria assim:
//our bg worker
BackgroundWorker worker;
//our progress dialog window
ProgressDialog pd;
private void btnDispacther_Click(object sender, RoutedEventArgs e)
{
int maxRecords = 1000;
pd = new ProgressDialog();
//hook into the cancel event
pd.Cancel += CancelProcess;
//get our dispatcher
System.Windows.Threading.Dispatcher pdDispatcher = pd.Dispatcher;
//create our background worker and support cancellation
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
for (int x = 1; x < maxRecords; x++)
{
if (worker.CancellationPending)
{
args.Cancel = true;
return;
}
System.Threading.Thread.Sleep(10);
//create a new delegate for updating our progress text
UpdateProgressDelegate update = new UpdateProgressDelegate(UpdateProgressText);
//invoke the dispatcher and pass the percentage and max record count
pdDispatcher.BeginInvoke(update, Convert.ToInt32(((decimal)x / (decimal)maxRecords) * 100), maxRecords);
}
};
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
pd.Close();
};
//run the process then show the progress dialog
worker.RunWorkerAsync();
pd.ShowDialog();
}
//our delegate used for updating the UI
public delegate void UpdateProgressDelegate(int percentage, int recordCount);
//this is the method that the deleagte will execute
public void UpdateProgressText(int percentage, int recordCount)
{
//set our progress dialog text and value
pd.ProgressText = string.Format("{0}% of {1} Records", percentage.ToString(), recordCount);
pd.ProgressValue = percentage;
}
void CancelProcess(object sender, EventArgs e)
{
//cancel the process
worker.CancelAsync();
}
Fonte:
http://elegantcode.com/2009/07/03/wpf-multithreading-using-the-backgroundworker-and-reporting-the-progress-to-the-ui/
https://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx
http://stackoverflow.com/questions/9732709/the-calling-thread-cannot-access-this-object-because-a-different-thread-owns-it
Comentários