I have a problem in my WPF app. I have a custom CircularProgressBar. When I retrieve data from database it takes a few seconds.
I would like to show the CircularProgressBar in my app while the data is retrieved.
This code runs the CircularProgressBar :
CircularProgressBar cb = new CircularProgressBar();
stk.Children.Add(cb);
ThreadStart thStart = delegate()
{
ThreadStart inv = delegate()
{
stk.Children.Remove(cb);
};
this.Dispatcher.Invoke(inv, null);
};
Thread myThread = new Thread(thStart);
myThread.Start();
in my custom class (Printer).
And where I call this window:
Printer p = new Printer();
p.Show();
//Code For retrieve Data from DataBase
p.close();
So this happens : CircularProgressBar shows for a few seconds and it not running. Where is my bug?
You can simply use background worker:
private BackgroundWorker bw = new BackgroundWorker();
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 bw_DoWork(object sender, DoWorkEventArgs e)
{
//load data from database
System.Threading.Thread.Sleep(1);
worker.ReportProgress(progressbar_value);
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Progress.value= progressbar_value;
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//progress completed
}
This is not how you do stuff in wpf -
use a model to populate the data from the db and than bind CircularProgressBar visibility to
the state you're in (hide it when you completed the task).
all this boilerplate code should be in xaml.
If I were you, I would simplify life by using databinding with dependencyproperties.
What are the steps to follow.
1) Create a dependency property called IsBusyProperty of type bool in your custom progressbar.
2) Register a delegate to its value change event (this is done when you create the dependency property).
3) You can now bind this IsBusyProperty to a status in your code that says hey I am busy.
4) When the value is set to true you get your progressbar to start its magic.
5) When it is set to false you stop the magic.
It is far simpler to create a control with a storyboard that rotates, so long as your ui is not locked it will rotate then simply kill it afterward.
Try this
Related
Scenario: I have a DataGrid in my application in which onclikcing each row, the values get populated in the texboxes below. I'll update the values and when clicking save the process is taking more time to complete.
So i have written a backgroundworker to make the process run asynchronously. When each row is clikced an instance of a backgroundworker is created and the process is accomplished. During that update the user will select the second row of the grid and update that values. So this will create another instance and the process will run in background.
Now when both the update process is running if the user selects the first row of the grid there should be a message showing "The process is still running".
//Code:
' OnClick of the event
var bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += delegate {
SaveDetails();
};
bw.RunWorkerCompleted += delegate {
MessageBox.Show("Completed");
};
bw.RunWorkerAsync();
'Save method
public void SaveDetails()
{
for (int i = 0; i < 10;i++ )
{
System.Threading.Thread.Sleep(5000);
MessageBox.Show("Hi");
}
}
How can i access the previously created backgroundworker instance and check the status of the process and display the messgage?
Note: There may be many process running simultaneously, so i should be able to access any process.
Is this possible?
You can store any BackgrounWorker you create in a List to refer to them at any moment, but alternatively you can create a tasks Queue and store there any pending process, so you have only one BackgroundWorker at any time.
Storing the BackgroundWorkers in a List
Create a List(Of BackgroundWorker) which can be accesed anywhere you need in your code, for example. Every time you create a new BackgroundWorker, add it to the List:
mylist.Add(bw)
You a lot of options to access the correct backgroundworker later on. The easiest one is to create your own class which will have an identificator (the row of the DataGrid, for example) and the backgroundworker. This way, your list will be of this class instead of BackgroundWorkers:
myClass.BackgroundWorkerProperty = bw
myClass.id = myId
myList.Add(myClass)
Using a Queue to run the tasks
Create a Queue with a type which has the information to run the task. For example, the row of the DataGrid, if that's enought, that will be type Integer (the index), then add it everytime the backgroundworker is running:
myQueue.Add(myRow)
Everytime the backgroundworker finish, check the Queue and run the next task stored.
You can use the Tag property of the DataGridViewRow:
var bw = new BackgroundWorker();
row.Tag = bw;
So you can access it.
If you are using .NET 4.5 i would suggest to use Async/Await rather than Background thread to solve your problem, because its programming style is easy and efficient as given below:
public partial class Form1 : Form
{
List<Task<bool>> taskList = new List<Task<bool>>();
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
Task<bool> task = Task.Run(() => SaveDetails());
MessageBox.Show(task.Id + " started.");
taskList.Add(task);
var isSuccess = await task;
}
public bool SaveDetails()
{
for (int i = 0; i < 10; i++)
{
System.Threading.Thread.Sleep(5000);
//MessageBox.Show("Finishing.");
}
return true;
}
private void button2_Click(object sender, EventArgs e)
{
foreach (var task in taskList)
{
if (task.IsCompleted == true)
MessageBox.Show(task.Id + " Completed.");
}
}
}
i have created a second button click event to access and display all currently completed tasks.
Also, please make a note that you can use task.Id as the row identifier of your grid row.
Hope that helps.
well, that's rather easy. put the BackgroundWorker as a member in your form and then you can access it from everywhere:
BackgroundWorker bg;
public Form1()
{
bg = new BackgroundWorker();
}
private void button1_Click(object sender, EventArgs e)
{
if(bg.IsBusy)
{
// show the message
}
else
{
bw.WorkerReportsProgress = true;
bw.DoWork += delegate {
SaveDetails();
};
bw.RunWorkerCompleted += delegate {
MessageBox.Show("Completed");
};
bw.RunWorkerAsync();
}
}
I am developing an application which will actively read from a file and analyze and display information in that file to the UI in real time.
Everything I have read tells me I should use some kind of threading to do this. I have explored background workers and also tried creating a separate thread at run time and using that thread to update all of the UI elements.
The problem comes in when I can't (or shouldn't) be making cross thread calls, as the original UI elements were created on a different thread.
Is there a way to create these UI elements on the thread that will be updating them? What is the best way to do this?
edit: There was a reply to this post (which is now gone) explaining how I should do this. After updating my code with the method described
This is the updated code I have used. Everything is fine until I add in the file system watcher. As soon as I add that I get the same error about not making cross thread calls.
Session is a class I created that parses through the log file
private Session s1 = new Session("");
private FileSystemWatcher fsw;
private OpenFileDialog ofd1 = new OpenFileDialog();
private BackgroundWorker bgw;
private bool logActive = false;
public frmMain()
{
InitializeComponent();
bgw = new BackgroundWorker();
bgw.WorkerReportsProgress = true;
bgw.ProgressChanged += HandleProgressChanged;
bgw.DoWork += HandleDoWork;
fsw = new FileSystemWatcher(#"H:\Logs", "*.txt");
fsw.SynchronizingObject = this;
fsw.IncludeSubdirectories = false;
fsw.EnableRaisingEvents = true;
fsw.NotifyFilter = NotifyFilters.Size;
fsw.Changed += new FileSystemEventHandler(fsw_OnChanged);
}
private void frmMain_Load(object sender, EventArgs e)
{
ofd1.Filter = "log files (*.txt)|*.txt|All files (*.*)|*.*";
ofd1.FilterIndex = 2;
ofd1.RestoreDirectory = true;
}
private void fsw_OnChanged(object source, System.IO.FileSystemEventArgs e)
{
bgw.RunWorkerAsync();
}
// this runs on the UI thread
// here's where you update the UI based on the information from the event args
private void HandleProgressChanged(object sender, ProgressChangedEventArgs e)
{
for (int i = s1.previousLineNumber; i < s1.GetMessageCount(); i++)
{
ListViewItem lvi = new ListViewItem((s1.GetMessage(i).date).ToString());
lvi.SubItems.Add(s1.GetMessage(i).type.ToString());
lvi.SubItems.Add(s1.GetMessage(i).data);
listView1.Items.Add(lvi);
}
}
// this runs on a background thread; you cannot modify UI controls here
private void HandleDoWork(object sender, DoWorkEventArgs e)
{
s1.ParseLiveFile();
bgw.ReportProgress(100);
}
In order to update the UI you should use Invoke or BeginInvoke.
void LengthyProcessInThread()
{
...
foreach(var item in file)
{
Invoke(delegate() {
.. Update UI here.
});
}
}
Invoke is a method on a control, Eg. the form containing the UI.
I wish you good luck with your quest.
I'm using a BackgroundWorker to periodically check a hardware switch. Due to it is connected via a slow RS485 network, I have to delay the next status update.
On switch Status change I want to update an OK/nOK Picture Box. This is realized as a green OK pictureBox over a nOK pictureBox. No real work is done here.
For expandability I decided to use the Backgroundworker. Finally I want to have a hidden worker, which
provides globally the Status of three switches and
updates on StatusChange the PictureBoxes.
Problem description
Once the BackgroundWorker is started, it works as expected. However the GUI freezes.
What did I try?
The MSDN BackgroundWorker Class Note 1
says, that GUI should be updated via ProgressChanged. I tried to raise this Event by Worker_Switch.ReportProgress(fakeProgress++) and failed. The PictureBox wasn't updated anymore.
Snippet from designer
this.Worker_Switch = new System.ComponentModel.BackgroundWorker();
//
// Worker_Switch
//
this.Worker_Switch.WorkerSupportsCancellation = true;
this.Worker_Switch.DoWork += new System.ComponentModel.DoWorkEventHandler(this.Worker_Switch_DoWork);
Snippet from Main Form
delegate void SetEventCallback(object sender, DoWorkEventArgs e); // Threadsafe calls for DoWork
private void btnBackgroundworker_Click(object sender, EventArgs e)
{
if (!Worker_Switch.IsBusy)
{
Worker_Switch.RunWorkerAsync();
}
}
private void Worker_Switch_DoWork(object sender, DoWorkEventArgs e)
{
// Worker Thread has no permission to change PictureBox "pictureBoxSwitchrightOK"
// Therefore this method calls itsself in the MainThread, if necessary.
while (!Worker_Switch.CancellationPending)
{
if (this.pictureBoxSwitchrightOK.InvokeRequired) // Worker Thread
{
System.Threading.Thread.Sleep(400);
SetEventCallback myCall = new SetEventCallback(Worker_Switch_DoWork);
this.Invoke(myCall, new object[] { sender, e });
}
else // Main Thread
{
// Turns OK Picture Box invisible, if nOk State (Switch pushed)
pictureBoxSwitchrightOK.Visible = SwitchOK("right"); // true: OK (green)
this.Refresh();
}
}
private bool SwitchOK(string rightOrLeft) // select one of the switches
{ (...)} // gets hardware switch status
Edit: Special Thanks to laszlokiss88 (3 possibilities) and JMK (for simplicity with System.Windows.Forms Timer from toolbox)
This alternative from Toolbox also worked:
this.timer_Switch.Enabled = true;
this.timer_Switch.Interval = 400;
this.timer_Switch.Tick += new System.EventHandler(this.timer_Switch_Tick);
private void timer_Switch_Tick(object sender, EventArgs e)
{
motorSwitchControl.Init(); // globally available Switch status
SwitchRight = SwitchOK("right");
SwitchRightOK.Visible = SwitchRight;
SwitchLeft = SwitchOK("left"); // globally available Switch status
SwitchLeftOK.Visible = SwitchLeft;
SwitchAllOK = SwitchRight & SwitchLeft;
this.Refresh();
}
a) Is it correct, that the Sleep() actually happens in the Worker Thread?
- no Main Thread
b) What is going wrong, if I manipulate user interface objects in DoWork? (Contrary to MSDN Note)
- works in Main Thread?
c) What is the correct way to periodically update a PictureBox? DoWork, ProgressChanged, RunWorkerCompleted...?
- Three possibilities from laszlokiss88 answer.
You can update the UI from the DoWork event via the Dispatcher, or Control.Begininvoke(winforms), or you can do it via the ProgressChanged event of the BackgroundWorker:
public MainWindow()
{
InitializeComponent();
var bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerAsync();
}
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// You are in the main thread
// Update the UI here
string data = (string)e.UserState;
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
// You are in a worker thread
(sender as BackgroundWorker).ReportProgress(0, "right");
}
For a start you should almost never have a need to put an active background thead to sleep. I am also not sure why you are constructing/defining the delegate this way, try some thing like
public delegate void UpdatePictureBox();
myDelegate = new UpdatePictureBox(UpdatePictureboxMethod);
then you have a method UpdatePictureBoxMethod
private void UpdatePictureBoxMethod()
{
this.pictureBox1.Image = Properties.Resources.SomeImage;
}
or something simalar, where you pass in the image to update to.
Alternatively you could use the (bgWorker as BackgroundWorker).ReportProgress(progress, object); method. So from the background thread you call
(bgWorker as BackgroundWorker).ReportProgress(progressBarValue, infoBall);
where here class IfoBall will hold all your important information
class InfoBall
{
public int nProgressBar { get; set; }
public int nMaxProgressBar { get; set; }
public Image image { get; set; }
}
then you can pass this object back to the UI thread and do your updates
void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// On UI thread.
InfoBall someBall = (InfoBall)e.UserState;
this.pictureBox1.Image = someBall.image;
// etc...
}
I hope this helps.
I have a MainFrame window with imageViewer control on it. Also there is my dll which calculates changes for the image all was working fine before I decided to add ProgressDialog.(( The Idea was - firstly I am loading the image via dll to main frame (this still OK). Then if user clicks button then show ProgressDialog and in worker.DoWork create new image via the same dllwrapper class (I am using "new")
All seems to be ok but when i am trying to set my currentImage property of imageviewer control (this is nothing more then setter for Image)it show me this error!
This is the code of my userButtonClickHandler from where I am launching ProgressDialog:
void OnThumbnailClick(object sender, RoutedEventArgs e)
{
pd = new ProgressDlg();
pd.Cancel += CancelProcess;
int max = 1000;
System.Windows.Threading.Dispatcher pdDispatcher = pd.Dispatcher;
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
LibWrap lwrap = new LibWrap();//!NEW instance for dll wrapper!
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
imageViewer.CurrentImage = lwrap.engine2(BitmapFrame.Create(MyPrj.App.draggedImage));//ERROR IS HERE!!!//The calling thread cannot access this object because a different thread owns it.
//what process??
};
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
pd.Close();
};
worker.RunWorkerAsync();
pd.ShowDialog();
}
There is function from the same MainFrame class for canceling (There is OK too)
void CancelProcess(object sender, EventArgs e)
{
worker.CancelAsync();
}
This is class for ProgressDlg (it has nothing more then progress bar and cancel button):
public partial class ProgressDlg : Window
{
public ProgressDlg()
{
InitializeComponent();
}
public string ProgressText
{
set
{
this.lblProgress.Content = value;
}
}
public int ProgressValue
{
set
{
this.progress.Value = value;
}
}
public event EventHandler Cancel = delegate { };
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
Cancel(sender, e);
}
}
}
I am working with this problem for (almost) two days and still couldn't find the solution. Help me please if you have an idea.
1 UPDATE
It seems to me that you was right about this threads - when I am trying to load previously loaded(initial) image (from the main thread) -it loads OK but if I am trying libWrap it fails due to processes conflict!
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
imageViewer.Width = 1000;//work!
imageViewer.CurrentImage = MyPrj.App.draggedImage;//Work!
imageViewer.CurrentImage = lwrap.engine2(BitmapFrame.Create(MyPrj.App.draggedImage));//Fail =(!
}
2 UPDATE
I have tried this construction OnThumbnailClick
Application.Current.MainWindow.Dispatcher.BeginInvoke(new Action(() =>
{
imaeViewer.CurrentImage = lwrap.engine2(BitmapFrame.Create(FXPhotoStudio.App.draggedImage));
}
This caused same error/ Perhaps it will be correct to pass this value in MainThread (UI)? But I have no idea how.( I couldnot use serializers - becouse it is rapidly calling operation and this images are temporary/
WPF cannot alter items that were created on another thread.
So if you create an ImageViewer on one thread, you cannot alter it's properties on another thread.
Instead, use the Dispatcher, which is WPF's internal message queue for the main UI thread, to update your objects.
Or, use Henk's Answer to do your work on another thread, but return the result to the main thread so it can update your ImageViewer's properties
You need at least these changes:
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
args.Result = lwrap.engine2(BitmapFrame.Create(MyPrj.App.draggedImage));
};
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
if (args.Error != null)
{ ... } // handle error
else if (args.Cancelled)
{ ... } // handle Cancel
else
{
imageViewer.CurrentImage = args.Result;
}
pd.Close();
}
I'm not sure if it's enough but try again.
The imageViewer was created on the main thread of the application (which is appropriate because it is a UI control). UI controls can ONLY be accessed by the thread which created it, and that thread must have its own dispatcher (by which I mean message loop).
Remove the threading code, and it will work.
If you want this to popup the window and then show the image when the conversion completes, you will have to store the returned image in a variable until you return to the main thread, and then make the assignment to the imageViewer.
In my program [C# + winforms]. I have progress bar & listview.
Through one method i am performing some operations & then updating data in Listview. The no of records added is the value i am setting for ProgressBar.value property. What i want here is, According to value of progress bar, it should show its progress. However the progress bar is not getting updated. Only at the end of method execution progress bar shows entire progress i.e. 100 %
Can someone help me in this regard?
Thanks,
Amit
It sounds like you are blocking the UI thread - i.e. you haven't released the system to do any painting.
A hacky answer is to inject Application.DoEvents() into your code - but this is risky, and has problems with re-entrancy etc; and it is just a bit hacky.
A better option may be to do the processing on a BackgroundWorker, periodically switching to the UI thread to update things (Control.Invoke) - but this may be tricky if you are adding lots of items to a ListView.
Full example (although you might want to batch the UI updates - not a row at a time):
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
class MyForm : Form
{
BackgroundWorker worker;
ListView list;
Button btn;
ProgressBar bar;
public MyForm()
{
Text = "Loader";
worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.ProgressChanged += worker_ProgressChanged;
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
list = new ListView();
list.Dock = DockStyle.Fill;
Controls.Add(list);
btn = new Button();
btn.Text = "Load";
btn.Dock = DockStyle.Bottom;
Controls.Add(btn);
btn.Click += btn_Click;
bar = new ProgressBar();
bar.Dock = DockStyle.Top;
Controls.Add(bar);
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
btn.Enabled = true;
}
void btn_Click(object sender, EventArgs e)
{
worker.RunWorkerAsync();
btn.Enabled = false;
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
string newRow = "Row " + i.ToString();
worker.ReportProgress(i, newRow);
Thread.Sleep(100);
}
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
list.Items.Add((string)e.UserState);
bar.Value = e.ProgressPercentage;
}
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.Run(new MyForm());
}
}
Really Sorry Friends,
Actually, I was assiging value to ProgressBar.value field but didnt use update() method. I used that & my problem got resolved.
Thanks all for your replies
As Marc said, you want to make sure that you spin off a new thread to do your long running computation. That way the User Interface thread (which is the one that has to do all the screen updates) can redraw the progres bar whenever you change the percent complete.
It's important to note that only the UI thread can update the interface. So, once you are running on a separate thread, you have to go through an extra hoop to make sure that your UI change is processed on the UI thread. If you aren't sure what thread you are running on, you can check the value of InvokeRequired (if your class is a System.Windows.Form) to see if you are actualy in the UI thread.
To get your command processed on the UI thread, use the Control.Invoke() function to make sure the update is processed on the UI thread for the control you are working with.
In my sample code below I'm creating a delegate function type and declaring the invoked function in advance....I've not done it with any of the cool C# 3.5 functions, but I bet you could work up a lamba expression to do the same thing.
private void bCreateInvoices_Click(object sender, EventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(CreateInvoices);
worker.RunWorkerAsync(this);
}
// Here is the long running function that needs to update the progress bar
public void CreateInvoices(object sernder, DoWorkEventArgs e)
{
int totalChecked = CountCheckedServiceOrders();
int totalCompleted = 0;
foreach (...data to process...) {
totalCompleted++;
if (InvokeRequired) {
Invoke(new Change(OnChange), "status text",
totalCompleted, totalChecked);
}
}
}
// this code updates the status while a background thread works
private delegate void Change(string status, int complete, int total);
private void OnChange(string status, int complete, int total)
{
if (status == null) {
progressBar.Visible = false;
lStatus.Text = "Task complete";
progressBar.Value = 0;
} else {
progressBar.Visible = true;
progressBar.Minimum = 0;
progressBar.Maximum = total;
progressBar.Value = complete;
lStatus.Text = status;
}
}
Take a look at the MSDN Control.InvokeRequired manual page and the MSDN Control.Invoke manual page for some more info.
The ProgressBar.Value must be between 0 and 100.
My guess is that your problem is that you're updating the ListView on the GUI thread. That means you'll need to call Application.DoEvents() after changing the ProgressBar.Value property.
It would be best to run on a BackgroundWorker and use the ProgressChanged event to handle the ProgressBar update.
Here's another question about the same topic.