Como implementar corretamente um BackgroundWorker com atualizações ProgressBar?
-Atualizado - 14/10 também perguntou issoquestão
Para dar uma ideia clara do que está acontecendo e levando em conta os comentários e deste artigoAqui
O que eu realmente quero fazer agora é invocar um novo formulário com uma barra de progresso e executá-lo e animá-lo enquanto meu thread em segundo plano executa meu longo processo no banco de dados e invoca um evento de fechamento de formulário
O trabalhador em segundo plano está configurado aqui
public partial class MainWindow : Window
{
//Declare background workers
BackgroundWorker bw = new BackgroundWorker();
BackgroundWorker bwLoadCSV = new BackgroundWorker();
BackgroundWorker bwProgressBar = new BackgroundWorker();
Então os delegados adicionaram aqui
public MainWindow()
{
bwLoadCSV.WorkerReportsProgress = true;
bwLoadCSV.WorkerSupportsCancellation = true;
bwLoadCSV.DoWork += new DoWorkEventHandler(bwLoadCSV_DoWork);
bwLoadCSV.ProgressChanged += new ProgressChangedEventHandler(bwLoadCSV_ProgressChanged);
bwLoadCSV.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwLoadCSV_RunWorkerCompleted);
A chamada é feita aqui a partir da classe da janela principal
private void CSV_Load_Click(object sender, RoutedEventArgs e)
///Function to read csv into datagrid
///
{
//Turn Cursor to wait
System.Windows.Forms.Cursor.Current = System.Windows.Forms.Cursors.WaitCursor;
//Test connection to sql server
if (CHHoursDataProvider.IsDatabaseOnline() == false)
{
System.Windows.Forms.MessageBox.Show("Can not establish contact with sql server" + "\n" + "Contact IT", "Connection Error");
//Set UI picture
return;
}
//Set a control to update the user here
tbLoadDgStat.Visibility = Visibility.Visible;
tbLoadDgStat.Text = "Getting data templete from Database...";
string FilePath = txFilePath.Text;
if (bwLoadCSV.IsBusy != true)
{
//load the context object with parameters for Background worker
bwCSVLoadContext Context = new bwCSVLoadContext();
Context.Site = cBChSite.Text;
Context.FilePath = txFilePath.Text;
Context.FileName = fileTest;
Context.Wageyear = cbWageYear.Text;
Context.Startdate = ((DateTime)dpStartDate.SelectedDate);
Context.Enddate = ((DateTime)dpEndDate.SelectedDate);
bwLoadCSV.RunWorkerAsync(Context);
}
O trabalhador de segundo plano trabalha
private void bwLoadCSV_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
bwCSVLoadContext Context = e.Argument as bwCSVLoadContext;
worker.ReportProgress((10));
if ((worker.CancellationPending == true))
{
e.Cancel = true;
}
else
{
// Perform a time consuming operation and report progress load csv into datagrid.
Para relatar o trabalho em segundo plano, faço isso. É aqui que eu estou tentando carregar uma nova chamada de formulário ProgressDialog que tem uma barra de progresso, que eu estou tentando definir como Indeterminable, para que ele simplesmente "varre" meu formulário ProgressDialoge para mostrar ao usuário algo que ainda está acontecendo. Eu usei a parte de repórter do trabalho em segundo plano porque acredito que tenha acesso ao thread da janela principal e espero que o método invoke seja chamado a partir do thread da janela principal, mas não tenho certeza
Aqui é o repórter
private void bwLoadCSV_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.Dispatcher.Invoke((MethodInvoker)delegate { tbLoadDgStat.Visibility = Visibility.Visible; });
//tbLoadDgStat.Visibility = Visibility.Visible;
//this.progressBar1.Value = e.ProgressPercentage;//This works but pauses on long steps
if (e.ProgressPercentage == 10)
{
//Try to open a new form with a class ProgressDialog and set the progressbar
// on the frm to IsIndeterminate=true
//THIS IS NOT WORKING
this.Dispatcher.BeginInvoke (new Action(() =>
{ ProgressDialog progressDialog = new ProgressDialog();
progressDialog.SetIndeterminate(true);
}));
//this updates the main form OK
this.Dispatcher.Invoke((MethodInvoker)delegate { tbLoadDgStat.Text = "Getting data templete from Database..."; });
}
else if (e.ProgressPercentage == 20)
{
this.Dispatcher.Invoke((MethodInvoker)delegate { tbLoadDgStat.Text = "Data template retrieved..."; });
}
else
{
if (e.ProgressPercentage % 10 == 0)
{
this.Dispatcher.Invoke((MethodInvoker)delegate { tbLoadDgStat.Text = "Adding Data..." + e.ProgressPercentage.ToString() + "%"; });
}
}
Por fim, o xaml para o formulário ProgressDialog e sua classe
<Window x:Class="Test_Read_CSV.ProgressDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Progress Dialog" Height="115" Width="306" Name="ProgressPopup">
<Grid>
<ProgressBar Height="31" HorizontalAlignment="Left" Margin="12,33,0,0" Name="progressBar1" VerticalAlignment="Top" Width="250" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="7,4,0,0" Name="tbEvent" VerticalAlignment="Top" Width="254" IsReadOnly="True" IsEnabled="False" />
</Grid>
classe
/// <summary>
/// Interaction logic for ProgressDialog.xaml
/// </summary>
public partial class ProgressDialog : Window
{
public ProgressDialog()
{
WindowStartupLocation = WindowStartupLocation.CenterScreen;
InitializeComponent();
}
public ProgressDialog(String Purpose)
{
InitializeComponent();
tbEvent.Text = Purpose;
WindowStartupLocation = WindowStartupLocation.CenterScreen;
}
public void UpdateProgress(int progress)
{
progressBar1.Dispatcher.BeginInvoke(
new Action(() =>
{
progressBar1.Value = progress;
}
));
}
public void SetIndeterminate(bool isIndeterminate)
{
progressBar1.Dispatcher.BeginInvoke(
new Action(() =>
{
if (isIndeterminate)
{
progressBar1.IsIndeterminate = true;
}
else
{
progressBar1.IsIndeterminate = false;
}
}
));
}
}
}
Eu li e fiz um número de tutorial sobre o trabalhador de fundo e até mesmo alguns em tópicos, mas não consigo obter o resultado que eu quero
A idéia é que eu tenho dois longos processos em que estou recebendo um clone datatable do meu bd remoto ou estou atualizando o db do meu aplicativo wpf (.net 4). Enquanto o processo está em execução, eu quero um controle de barra de progresso e atualizá-lo, pela óbvia razão de deixar claro que algum trabalho está acontecendo. Então eu fiz as rotinas de progresso do relatório usual no worker de plano de fundo e ele funciona .... No entanto, no meu thread dowork eu tenho esse comando
CHHoursDataProvider CH = new CHHoursDataProvider();
oTable = CH.CloneCHHours();
isso onde a comunicação com o db é e este comando leva uns bons 60 - 90 segundos em uma conexão remota vpn por isso mesmo se eu fizer isso
CHHoursDataProvider CH = new CHHoursDataProvider();
worker.ReportProgress((10));
oTable = CH.CloneCHHours();
worker.ReportProgress((20));
A janela principal ainda parece congelada e como ela caiu!
Então, tudo que eu quero fazer é no início da chamada para o trabalho de fundo é definir uma barra de progresso em execução e deixá-lo em execução até o final da tarefa. Isso é tudo que eu preciso fazer para terminar meu primeiro projeto e depois de três dias eu ainda não consigo entender isso!
Então eu tentei o seguinte
No progresso do bw alterado e na classe da janela principal
this.progressBar2.IsIndeterminate = true;
No entanto, a animação não inicia até que o segmento Dowork seja concluído.
Em seguida, criei outro trabalhador de segundo plano para atualizar a barra de progresso2, que vinculado a um botão na janela principal estava ok, mas assim que tentei usá-lo do outro trabalhador de plano de fundo ou da classe da janela principal, não corri até o dowork thread tinha concluído no primeiro trabalhador de fundo
Então tentei seguir um método de invocação, mas REALMENTE me perdi com isso!
Então, alguém pode ajudar eu posso imaginar que é algo a ver com a segmentação e trabalhando no segmento errado, etc, mas o que eu faço sobre isso eu não tenho idéia.
Eu posso postar mais código conforme necessário
Ian