I've got a Loading page that starts up (typically I have my home page load it on startup but I've swapped it recently). I have an excel file with tons of data in it. I have a class for dumping that data. I also have a class for each of the lists I want to create. The loading page should be updating it's percentage as it goes.
I was able to get it to work in the example below.
public partial class Window1 : Window
{
public MainController main = new MainController();
public ExcelImporter tempExcel = new ExcelImporter();
List<Character.MainRace> listRaces = new List<Character.MainRace>();
List<string> tempString = new List<string>();
public Window1()
{
InitializeComponent();
BackgroundWorker worker = new BackgroundWorker();
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.WorkerReportsProgress = true;
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerAsync();
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBarTest.Value = e.ProgressPercentage;
textBlockPercent.Text = e.ProgressPercentage.ToString() + "%";
textBlockTest.Text = (string)e.UserState;
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
tempExcel.Create();
tempExcel.SetSheet("Races");
var worker = sender as BackgroundWorker;
worker.ReportProgress(0, String.Format("Process Iteration 1."));
List<string> myRaceName = tempExcel.GetRangeValue("A2", "A46");
List<string> myRaceShortDescription = tempExcel.GetRangeValue("N2", "N46");
for (int raceSelection = 0; raceSelection < myRaceName.Count - 1; raceSelection++)
{
Character.MainRace tempRace = new Character.MainRace();
tempRace.Race = myRaceName[raceSelection];
double percentage = ((double)raceSelection / (double)myRaceName.Count) * 100.0;
Thread.Sleep(10);
worker.ReportProgress((int)percentage, String.Format("Processing " + myRaceName[raceSelection]));
tempRace.ShortDescription = myRaceShortDescription[raceSelection];
listRaces.Add(tempRace);
}
worker.ReportProgress(100, "Done Processing");
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("All Done!");
progressBarTest.Value = 0;
textBlockTest.Text = "";
}
}
There's really two places where I need the UI to update. Each time I'm loading a new set of data for each loader like Human, elf, etc for race and fighter, paladin, thief, etc for class. As well as when I move on to the next set of data like Classes Race, Feats, etc. (though to keep this a bit shorter I only included showing just 1 for race.
I'm pretty new to coding in general especially wpf. I've been going through bits a pieces of tutorials. I've tried using INotifyproperty,background worker, dispatcher, binding and not binding the content. I'm just not getting it. Everything works separately but not together. From what I understand, it's because the UI thread which is the main thread getting held up because of my long process.
As Jeroen mentioned, we need an example. You're trying to update a WPF UI based on the contents of [cringe] hard-coded Excel ranges. I'm guessing the workbook import part works, but the UI is freezing? or not updating?
First, try using a DataGrid to display your data on the UI. Bind its ItemsSource to a List (or ObservableCollection) of a class that holds all your pertinent data nicely. The DataGrid should do the rest as the AutoGenerateColumns is true by default.
Then, for further help, you'll have to attempt something and provide a reproducible example of what's failing. Showing the implementation of your BackgroundWorker would provide some clues on holding the main UI thread.
Related
/Disclaimer, this is my first time working with WPF and with multi-threading, so bear with me if I am making some big mistakes/
So I have an application with a tabcontrol. In one of the Tabs I intend to load in a visio file via the usual windows form host + Visio viewer activeX control etc... And it works perfectly. The only issue is that when I load in the document the UI freezes for 20 seconds (as I am loading in rather huge files). As I read this is because my application is running on a simple thread. So I was trying to implement a background worker to keep the UI reactive while the background thread is running.
When I instantiate my UserControl then I add to the Tab (Initialpath is the filepath of the visio file):
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += Worker_DoWork;
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
worker.RunWorkerAsync(initialpath);
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
DiagramView UCworker;
UCworker = new DiagramView((string)e.Argument);
e.Result = UCworker;
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
UC = (DiagramView)e.Result;
this.Host.Child = UC;
}
And When it creates the new DiagramView:
public DiagramView(string path)
{
InitializeComponent();
this.Resize += new EventHandler(this.UpdateSize);
this.viewer = new AxVisioViewer.AxViewer();
this.Controls.Add(this.viewer);
this.viewer.CreateControl();
this.viewer.Load(path);
this.viewer.HighQualityRender = false;
this.viewer.BackColor = Color.White;
this.viewer.PageTabsVisible = true;
this.viewer.ContextMenuEnabled = false;
this.viewer.PropertyDialogEnabled = false;
this.viewer.ToolbarVisible = true;
this.viewer.OnSelectionChanged += Viewer_OnSelectionChanged;
}
And for this line:
this.viewer = new AxVisioViewer.AxViewer();
I get: : 'ActiveX control 'f8cf7a98-2c45-4c8d-9151-2d716989ddab' cannot be instantiated because the current thread is not in a single-threaded apartment.'
I read that the backgroundworker is not capable to modify the UI elements (correct me if I am wrong)
I also saw this thread: Single-threaded apartment - cannot instantiate ActiveX control
But I am not sure how to implement it (this STA apartment state business) and when I tried, the visio viewer simple crashed when trying to open the document.
I need some guideline how to approach this issue, cause my goal would be to having a loading page to display with an animation until the Document is finished loading/rendering so I can display it.
Thank you in advance for the answers.
UPDATE: I also tried the following approach:
public partial class TabDiagramView : UserControl, INotifyPropertyChanged
{
public delegate void DisplayVisio(DiagramView view);
public DisplayVisio DelegateM;
DiagramView UC;
public TabDiagramView()
{
InitializeComponent();
DelegateM = new DisplayVisio(DisplayV);
Thread t = new Thread(RT);
t.SetApartmentState(ApartmentState.STA);
t.IsBackground = true;
t.Start();
}
#region Thread
private void RT()
{
DiagramView UCworker;
UCworker = new DiagramView(initialpath);
Dispatcher.Invoke(DelegateM, UCworker);
}
private void DisplayV (DiagramView DiagV)
{
UC = DiagV;
this.Host.Child = DiagV;
}
But in this case I get the following message on UC and the this.Host.chilld=DiagV when I am in the DisplayV method: System.InvalidOperationException: 'Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.'
It's simply impossible to load a UI control from another thread. However you could solve the freezing problem if the library had provided a thread-safe approach (e.g. AxViewer.LoadAsync) so the only way to make viewer.Load bearable for the user is to delay viewer.Load operation until the window is loaded:
string _path;
public DiagramView(string path)
{
InitializeComponent();
_path = path;
this.Resize += new EventHandler(this.UpdateSize);
this.viewer = new AxVisioViewer.AxViewer();
this.Controls.Add(this.viewer);
this.viewer.CreateControl();
this.viewer.HighQualityRender = false;
this.viewer.BackColor = Color.White;
this.viewer.PageTabsVisible = true;
this.viewer.ContextMenuEnabled = false;
this.viewer.PropertyDialogEnabled = false;
this.viewer.ToolbarVisible = true;
this.viewer.OnSelectionChanged += Viewer_OnSelectionChanged;
this.Loaded += Viewer_Loaded;
}
private void Viewer_Loaded(object sender, RoutedEventArgs e)
{
viewer.Load(_path);
}
And more importantly, delete the background worker code. It doesn't do any good here.
I have a C# WinForms application with a tab control and several tabs. One of the tabs contains a data grid control - it only has about 10 elements in it but the data is populated by querying multiple servers and thus is slow to load.
When I run my application and select the tab with the datagrid control, the application appears to hang, while its trying to query all the servers and populate the grid.
Instead of hanging I'd like the application to be responsive and for it to display a "please wait..." message which will disappear after the datagrid is populated.
What I've tried to do is create a background worker as such:
if (tabctrl.SelectedTab == tabctrl.TabPages["tabServices"])
{
this.dgrdServices.RowPrePaint += new DataGridViewRowPrePaintEventHandler(dgrdServices_RowPrePaint);
this.dgrdServices.CellContentClick += new DataGridViewCellEventHandler(dgrdServices_CellClick);
BackgroundWorker bw = new BackgroundWorker();
lblLoading.Visible = true;
bw.RunWorkerAsync();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
PopulateServicesDataGrid();
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
lblLoading.Visible = false;
}
private void PopulateServicesDataGrid()
{
int x = 0;
foreach (Service Service in Globals.Services)
{
// Add a row to the datagrid for each service
this.dgrdServices.Rows.Add();
// Update the current service status
Service.Status = Service.Query(Service.Server, Service.Name);
if (Service.Status == "running")
{
this.dgrdServices.Rows[x].Cells[0].Value = Properties.Resources.green_dot;
this.dgrdServices.Rows[x].Cells[4].Value = Properties.Resources.stop_enabled;
}
else
{
this.dgrdServices.Rows[x].Cells[0].Value = Properties.Resources.grey_dot;
this.dgrdServices.Rows[x].Cells[4].Value = Properties.Resources.start_enabled;
}
this.dgrdServices.Rows[x].Cells[1].Value = Service.Server.ToUpper();
this.dgrdServices.Rows[x].Cells[2].Value = Service.FreindlyName;
this.dgrdServices.Rows[x].Cells[3].Value = Service.Status;
this.dgrdServices.Rows[x].Cells[5].Value = "Uninstall";
this.dgrdServices.Rows[x].Cells[6].Value = Service.Name;
x++;
}
}
PopulateServicesDataGrid() contains code which iterates through some objects and queries several different servers for service status.
When I try and run the above though the grid doesn't get populated. If I don't use a background worker and just call PopulateServicesDataGrid directly it does work (albeit the app hangs).
Why isn't the background worker/datagrid populate working?
In your PopulateServicesDataGrid I imagine you're interacting with a UI control, which doesn't work out because the background worker is operating on a different thread than your UI context. You'll need to work out a mechanism to do the work in a way that returns the information you want to put in the grid and then back in your UI thread context (RunWorkerCompleted), populate the grid with the information you come up with in DoWork.
Anytime you're using a background worker, you'll need to split out your interactions with the UI controls, and after the backgroundworker completes resume interaction with your UI.
You're also hooking up the events after calling RunWorkerAsync, hook up your events first then call RunWorkerAsync.
Edit to reflect comment with an example:
Rough example of how you could do this, based on the code I see.
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
QueryServices()
}
private void QueryServices()
{
foreach (Service Service in Globals.Services)
{
Service.Status = Service.Query(Service.Server, Service.Name);
}
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
PopulateServicesDataGrid();
lblLoading.Visible = false;
}
private void PopulateServicesDataGrid()
{
//Do everything else you are doing originally in this method minus the Service.Query calls.
}
Method bw_DoWork running in another thread from ThreadPool. Accessing WinForms object from other threads requires synchronization. The best way to do this - use AsyncOperationManager. You should create AsyncOperation in GUI thread and use it inside PopulateServicesDataGrid to send or post results.
Another way - update DataGrid by prepared data inside bw_RunWorkerComplete - it's already synchronized by BackgroundWorker component.
More modern way to do the same - use async tasks, but it requires base level of TPL knowledge.
The name of the question is: "Updating the GUI from background worker", but the correct name world be: "Updating the GUI from background worker OR reporting multiple-variables (other than an integer) from background worker"
Please let me explain my situation. In a program I have a background worker which analyses the information.As the result of this analysis - form GUI elements should be populated with necessary data. In GUI I would like to update
2 datagridviews
1 listbox
5 labels
As I understand - I can only natively report 1 int value via ReportProgress() method of background worker.
So the question is - how can I pass a List<> ( + some other variables: string, int) via ReportProgress()? Basically - i want to update the GUI with the information but "1 integer" just won't do.. So either it should be possible to pass multiple variables via an ReportProgress() OR i can use an Invoke from inside the BackgroundWorker itself to update the GUI.. Personally I don't like the Invoke approach... What's your opinion?
Here is my code (see the comments):
private void button9_Click(object sender, EventArgs e) // start BW
{
bw.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
bw.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.RunWorkerAsync(10);
}
private void button10_Click(object sender, EventArgs e) // cancel BW
{
bw.CancelAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int count = (int)e.Argument;
for (int i = 1; i <= count; i++)
{
if (bw.CancellationPending)
{
e.Cancel = true;
break;
}
List<List<string>> list_result = new List<List<string>>();
list_result = Proccess();
bw.ReportProgress(list_result.Count()); // right now I can only return a single INT
/////////// UPDATE GUI //////////////
// change datagridview 1 based on "list_result" values
// change datagridview 2
// change listbox
// change label 1
// change label ..
Thread.Sleep(20000);
}
MessageBox.Show("Complete!");
e.Result = sum;
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
prog_count++;
listBox1.Items.Add("Count: (" + prog_count.ToString() + "/20). Found: " + e.ProgressPercentage.ToString() + ".");
}
There's a UserState parameter when calling ReportProgress.
var list_result = new List<List<string>>();
new backgroundWorker1.ReportProgress(0, list_result);
The parameter type is an object so you'll have to cast it back to the type you need:
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var userState = (List<List<string>>)e.UserState;
}
The tricky issue with this is, how do you determine whether you're passing back a List, or a list of lists, or a single string, number, etc. You'll have to test for each possibility in the ProgressChanged event.
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var myList = e.UserState as List<List<string>>;
if (myList != null)
{
// use list
return;
}
int myNumber;
if (Int32.TryParse(e.UserState.ToString(), out myNumber))
{
// use number
return;
}
var myString = e.UserState.ToString();
// use string
}
Alternatively, you could create a class that holds all the values you need (or use Tuple), run everything in the background to populate that class, then pass that to the RunWorkerCompleted event, and update your UI all at once from there.
I have written two very easy methods that enable you to invoke your code (only if required) and you only need to write your code once. I think this makes Invoke much friendlier to use:
1) BeginInvoke
public static void SafeBeginInvoke(System.Windows.Forms.Control control, System.Action action)
{
if (control.InvokeRequired)
control.BeginInvoke(new System.Windows.Forms.MethodInvoker(() => { action(); }));
else
action();
}
2) Invoke
public static void SafeInvoke(System.Windows.Forms.Control control, System.Action action)
{
if (control.InvokeRequired)
control.Invoke(new System.Windows.Forms.MethodInvoker(() => { action(); }));
else
action();
}
It can be called like this:
SafeInvoke(textbox, () => { textbox.Text = "text got changed"; });
Alternatively you could just
System.Windows.Forms.Form.CheckForIllegalCrossThreadCalls = false;
(which only changes behaviour in debug mode btw) and look if you run into problems.More often than not you actually don't. It took me quite some time to find cases very Invoke is really required for things not to get messed up.
The basic pattern for updating the UI from another thread is:
If controlItem.InvokeRequired Then
controlItem.Invoke(Sub() controlItem.Text = textUpdateValue)
Else
controlItem.Text = textUpdateValue
End If
This could update your list of controls without requiring you to pass anything through ReportProgress. If you would like to update your control from within the thread, I don't believe you need to check InvokeRequired, because it will always be required. However, best practices might be to expose the setting of a control via a property and then to do the full check so you can call it from anywhere.
c# .net Winforms,IDE: VS 2010
I having two windows from F1, F2.
F1 is the caller, and F2 is the form which I want to load in thread because it is having lots of rich controls on it.
I am able to load the f2 in child thread but it just get visible and goes, because its on child thread.(See Case 1 Code)
Case 1 Code
private void button1_Click(object sender, EventArgs e)
{
StartProgress();
Thread th = new Thread(new ThreadStart(LoadForm));
th.Start();
}
private void StartProgress()
{
progressBar1.Maximum = 100;
progressBar1.Step = 1;
for (int i = 0; i <= 100; i++)
{
label1.Text = i + "%";
progressBar1.PerformStep();
Thread.Sleep(10);
label1.Refresh();
}
}
private void LoadForm()
{
Form2 f2 = new Form2();
f2.Show();
}
}
Then I did reverse that is I loaded progress bar in child thread and loaded f2 on main thread.(See Case 2 Code)
//Code Case2:
case 2 when progress bar is on child thread.
private void button1_Click(object sender, EventArgs e)
{
LoadForm();
Thread th = new Thread(new ThreadStart(StartProgress));
th.Start();
LoadForm();
}
private void StartProgress()
{
progressBar1.Maximum = 100;
progressBar1.Step = 1;
for (int i = 0; i <= 100; i++)
{
label1.Text = i + "%";
progressBar1.PerformStep();
Thread.Sleep(10);
label1.Refresh();
}
}
private void LoadForm()
{
Form2 f2 = new Form2();
f2.Show();
}
but case 2 having 2 problems.
Problem 1: It loads the f2 as usual with flicking.
Problem 2: Cross Thread Opration progressbar1
//Pls suggest how to load the f2 in background and show it after the progress bar is loaded.
If you need to load a secondary window in the background, a better approach would be to load the data in the background of your current UI and then pass that information to the new Window.
public void btnNewWindow_Click(object sender, EventArgs args)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += WorkerOnDoWork;
worker.ProgressChanged += WorkerOnProgressChanged;
worker.RunWorkerCompleted += WorkerOnRunWorkerCompleted;
worker.RunWorkerAsync();
}
private List<string> _data = new List<string>();
private void WorkerOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
{
//Work has finished. Launch new UI from here.
Form2 f2 = new Form2(_data);
f2.Show();
}
private ProgressBar progressBar1;
void WorkerOnProgressChanged(object sender, ProgressChangedEventArgs e)
{
//Log process here
}
private void WorkerOnDoWork(object sender, DoWorkEventArgs e)
{
//Perform work you need to load the data.
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(50);
_data.Add("Test" + i);
}
}
I put this together real quick in WPF but you should be able to make this work with the same principles. I will reiterate that you shouldn't use a second UI thread. Load the data up front and then pass along to the window when ready and available.
If all you want to do is avoid the flicker, create F2 as hidden, and only show it once its initialization is complete.
It's been a while since I did any Windows Forms programming, but I guess you can show the form in your _Load event handler.
You shouldn't have more than one User Interface (UI) thread. The complexity to keep the data between your interface will be quite difficult and often make the application Not Thread Safe. Which will cause error after error for you.
The primary goal should keep the User Interface (UI) thread responsive. This way as the user interacts with your application the interface is responsive as the application performs computational task.
Here is a great article on keeping your UI thread responsive here.
You may want to note what Microsoft says here:
Note: Not all changes to the UI are necessarily done on the UI thread.
There's a separate render thread that can apply UI changes that won't
affect how input is handled or the basic layout. For example many
animations and transitions that inform users that a UI action has
taken place can actually run on this render thread. But if it's your
code that is changing UI elements somewhere in your pages, it's best
to assume that your code could have the potential to block the UI
thread, unless you're familiar with those APIs or subsystems and know
for certain they don't affect the UI thread.
Update:
I didn't realize you were using Windows Forms, the article listed above is for XAML. You can use this article to help build a responsive UI with computational task here for Windows Forms. (Link is for WPF, you'll need to port for Windows Forms).
I'm a fairly new to .net and I'm still struggling to understand a lot things, and right now I'm trying to accomplish something relatively simple but I've failed every single time, I would like to add a Thread to my program, this Thread would be responsible to perform the Upload operations to a web server and keep my program responsive providing the feedback of the operations to my users by updating a ListView, where the users would see all the status of the file uploads.
I don't know how to put this Thread inside the program to make it responsive, I couldn't find any examples so far and I'm trying to find a little sample to show me the use of Thread and WinForms in action.
What you'll want to use is a BackgroundWorker. It's specifically designed for exactly this purpose.
private void button1_Click(object sender, EventArgs e)
{
BackgroundWorker bgw = new BackgroundWorker();
bgw.DoWork += (_, args) => LongRunningTask(bgw);
bgw.WorkerReportsProgress = true;
bgw.ProgressChanged += (_, args) =>
{
textbox1.Text = args.ProgressPercentage.ToString();
};
bgw.RunWorkerCompleted += bgw_RunWorkerCompleted;
bgw.RunWorkerAsync();
}
private void LongRunningTask(BackgroundWorker bgw)
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);//placeholder for real work
bgw.ReportProgress(i);
}
}
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//do stuff when completed.
}
A key point to note is that the DoWork event runs in a background thread, but the other events all run in the UI thread. The BackgroundWorkder class takes care of ensuring that all on its own.