When I start program, I need to load some projects and init them. So I make that all in Thread and create a progress bar (next PB). When loading is started everythink is OK, but in one moment PB is stop and jump to end when init will end.
Here Thread which add to progress bar every init bLock.
public void initMapBlocks(int n, int m)
{
this.Dispatcher.Invoke(delegate ()
{
Loader.Visibility = Visibility.Visible;
mainWindow.Visibility = Visibility.Hidden;
});
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
this.Dispatcher.Invoke(delegate ()
{
MapPart mp = new MapPart();
mainCanvas.Children.Add(mp);
Loader.addPoint(1);
});
}
}
this.Dispatcher.Invoke(delegate ()
{
Loader.Visibility = Visibility.Hidden;
mainWindow.Visibility = Visibility.Visible;
});
}
Did it`s possible to get full animation?
-------- Update --------
Here code where i add to progress bar value.
public void addPoint(int x)
{
itemCountAlready += x;
loaderBar.Value += x;
}
----- Update 2 -------
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Thread thread = new Thread(new ThreadStart(() => initMapBlocks(100, 100)));
thread.Start();
}
Your initMapBlocks method produces (MapPart) and consumes (adds loader's value and mainCanvas children) at the same time. You can separate by using System.Collections.Generic.Queue
You can define your queue like this
private Queue<MapPart> myQueue = new Queue<MapPart>();
Instead of these guys
mainCanvas.Children.Add(mp);
Loader.addPoint(1);
You can write simply this for produce
myQueue.Enqueue(mp);
And you can define a DispatcherTimer in your constructor for consume
DispatcherTimer myTimer = new DispatcherTimer {
Interval = TimeSpan.FromMilliseconds(1)
};
myTimer.Tick += Consume;
myTimer.Start();
Your Consume method
private void Consume(object sender, EventArgs args) {
if (myQueue.Count > 0) {
//you don't need `this.Dispatcher.Invoke(delegate...` too
loaderBar.Value += myQueue.Dequeue();
mainCanvas.Children.Add(mp);
}
}
Related
I have a background worker running, which is dynamically making form fields from an xml file. Depending on the size of the xml, it takes some time to load, so I am using a loading bar to report the progress to use so they won't exit out of the program. The program works as intended, it hides the loading panel and shows the form fields when the worker finishes, but while loading, the loading bar won't load. I received no errors.
This is where the report progress is being called:
if (!retrievePath.Equals(""))
{
// create the template with the data from the file
XDocument filledDoc = templateCreator.CreateTemplateWithGivenData2(retrievePath, fileName2);
tempDoc = filledDoc;
XElement root = tempDoc.Root;
// get child forms of return data state and sections
IDataInterface dataInterface = new DataInterfaceImplementation();
IEnumerable<XElement> sections = dataInterface.GetSections(filledDoc);
// Grab forms that aren't empty
IEnumerable<XElement> forms = XmlClass.GetMefForms(filledDoc).Where(u => u.Value != "").ToList();
IEnumerable<XElement> extra = dataInterface.GetSections(filledDoc).Where(u => u.Value != "").ToList();
// get the return header state
elemForms = dataMiddleman.GetSections(filledDoc);
foreach (XElement el in elemForms)
{
if (el.Name.LocalName.Equals("ReturnHeaderState"))
{
createForms(el, 3);
}
}
foreach (XElement el in forms)
{
i = i + 1;
i = (i / forms.Count()) * 100;
if (i == 100)
{
i = (i / (forms.Count() - 1)) * 100;
}
createForms(el, i);
}
private void createForms(XElement x, int i)
{
this.Invoke((MethodInvoker)delegate {
backgroundWorker1.ReportProgress(i);
var pLabel = new ParentLabel(x);
this.leftGroup.Controls.Add(pLabel);
var parentPanel = new CustomPanel(x);
parentPanel.SendToBack();
this.thebox.Controls.Add(parentPanel);
RecursiveTraverse(x, parentPanel);
pLabel.Click += (sender, e) => PLabel_Click(sender, e);
pPanels.Add(parentPanel);
});
}
This is my background worker code:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
loadingPanel.BringToFront();
populateNewFields();
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
loadingBar.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
loadingBar.Value = 100;
Thread.Sleep(100);
loadingPanel.SendToBack();
loadingBar.Value = 0;
}
Your question is about Background worker is not reporting progress Winforms and I hope it's ok if I use a Minimal Reproducible Example to demo how to successfully fire an event when progress occurs on the background thread (which is is one way to achieve the outcome you want) and reducing the complex Xml operations to a "time-consuming black box" to be dealt with as a separate issue.
This Form will provide a means to test the notification using the MockCreateForm method which mimics a form creation by blocking the background worker for 5 ms. I believe your design spec is to send a notification every 100 operations.
Generic event lacks the needed properties so inherit EventArgs to customize the info received (declaring it outside the MainForm class).
public delegate void ProgressEventHandler(ProgressEventArgs e);
public class ProgressEventArgs : EventArgs
{
public ProgressEventArgs(int count, int total)
{
Count = count;
Total = total;
}
public int Count { get; }
public int Total { get; }
}
When the button (actually a CheckBox where Appearance=Button) state is toggled, it calls this worker Task using a CancellationTokenSource and CancellationToken so it can be halted. Every 100 times, the Progress event is fired:
private void btnWorker_CheckedChanged(object sender, EventArgs e)
{
if(btnWorker.Checked)
{
_cts = new CancellationTokenSource();
Task.Run(() =>
{
var formCount = 10000;
for (int i = 0; i < formCount; i++)
{
if(_cts.IsCancellationRequested)
{
return;
}
// Notify every 100 times.
if((i % 100) == 0)
{
Progress?.Invoke(new ProgressEventArgs(count: i, total: formCount));
}
MockCreateForm();
}
Progress?.Invoke(new ProgressEventArgs(count: formCount, total: formCount));
}, _cts.Token);
}
else
{
_cts.Cancel();
labelStatus.Text = "Idle";
}
}
CancellationTokenSource _cts = null;
The only thing left is to consume the event in the MainForm. The only thing that needs to be marshalled back onto the UI thread is the brief moment that Label.Text is being updated.
public MainForm()
{
InitializeComponent();
Progress += (e) =>
{
Invoke((MethodInvoker)delegate
{
labelStatus.Text = $"{e.Count} of {e.Total}";
});
};
}
public event ProgressEventHandler Progress;
What you do on the background thread is up to you. Just put it here:
public void MockCreateForm()
{
Task.Delay(5).Wait();
}
I hope this gets you closer to what you are trying to achieve.
I want to use the ProgressBar and run it from 100% to 0% within 2 seconds. I have written the following function but it does not seem to run properly. Please help! Thank you!
private async void progessbar()
{
for (iValue = 100; iValue >= 0; iValue--)
{
pbTime.Value = iValue;
await Task.Delay(20);
}
}
If you want to animate your progress bar smoothly, you should use a storyboard:
private void AnimateProgressBar()
{
var storyboard = new Storyboard();
var animation = new DoubleAnimation { Duration = TimeSpan.FromSeconds(2), From = 100, To = 0, EnableDependentAnimation = true };
Storyboard.SetTarget(animation, this.ProgressBar);
Storyboard.SetTargetProperty(animation, "Value");
storyboard.Children.Add(animation);
storyboard.Begin();
}
You are changing toolbar multiple times in the same Windows event. Windows updates GUI later when it is idle. Thus you probably see your toolbar jump from 0 to 100% after 2 second wait.
Just add timer to your control, set it when ready to start and do incremental updates.
// In your designer:
this.timer.Enabled = false;
this.timer.Interval = 200;
this.timer.Tick += new System.EventHandler(this.timer_Tick);
// when starting the progress:
pbTime.Value = 0
this.timer.Enabled = true;
private void timer_Tick(object sender, EventArgs e)
{
if (pbTime.Value < 100)
pbTime.Value += 10;
}
I hope this helps
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, System.EventArgs e)
{
// Start the BackgroundWorker.
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
/*Your main code*/
for (int i = 1; i <= 100; i++)
{
// Wait 20 milliseconds.
Thread.Sleep(20);
// Report progress.
backgroundWorker1.ReportProgress(i);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Change the value of the ProgressBar to the BackgroundWorker progress.
progressBar1.Value = e.ProgressPercentage;
// Set the text.
this.Text = e.ProgressPercentage.ToString();
}
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int currentProgress=-1;
while (currentProgress<length)
{
currentProgress=Worker.progress;
backgroundWorker1.ReportProgress(currentProgress);
Thread.Sleep(500);
length = Worker.UrlList.Count;
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
int ix = e.ProgressPercentage;
progressBar1.Value = ix;
lblText.Text =ix+" %";
}
I wrote a program to download page sources by reading a file have about 1000 URLs. so I used Tasks to download pages async. here Worker.progress is the currently executed URL amount. though the debuger hits the backgroundWorker1.ReportProgress(currentProgress); it never enter to the backgroundWorker1_ProgressChanged.
private void StartButton_Click(object sender, EventArgs e)
{
t.makeUrlList(inputFile);
backgroundWorker1 = new BackgroundWorker();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
backgroundWorker1.RunWorkerAsync();
t.RunTasks();
Application.Exit();
}
background worker initializes when start button clicks...
here is where my tasks are created....
public void RunTasks()
{
if (numOfTasks > UrlList.Count)
numOfTasks=UrlList.Count-1;
Task[] t = new Task[numOfTasks];
int j = 0;
while ( j < UrlList.Count-1)
{
for (int i = 0; (i < t.Count())&&(j<UrlList.Count-1); i++)
{
try
{
if (t[i].IsCompleted || t[i].IsCanceled || t[i].IsFaulted)
{
t[i] = Task.Run(() => FindWIN(j));
j++;
progress = j;
}
}
catch (NullReferenceException ex)
{
t[i] = Task.Run(() => FindWIN(j));
j++;
progress = j;
}
}
}
}
If you want to BackgroundWorker supports updating progress information, the value of WorkerReportsProgress should be set to true . If this property is true , the user code can call ReportProgress for initiating event ProgressChanged .
Background worker initialization:-
backgroundWorker1 = new BackgroundWorker();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork+=backgroundWorker1_DoWork;
backgroundWorker1.ProgressChanged+=backgroundWorker1_ProgressChanged;
backgroundWorker1.RunWorkerAsync();
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int currentProgress = -1;
decimal length=1000;
while (currentProgress < length)
{
currentProgress = Worker.progress;
backgroundWorker1.ReportProgress(currentProgress);
Thread.Sleep(500);
length = Worker.UrlList.Count;
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) {
int ix = e.ProgressPercentage;
progressBar1.Value = ix;
lblText.Text = ix + " %";
}
See the demo code below. This is mostly untested, and certainly isn't 'production standard', but it should give you a good start!
It uses a ConcurrentQueue to hold the list of URLs to be processed. This is threadsafe, and makes life a lot easier.
It has a configurable number of urls and tasks. It's best not to make 1000 tasks, but instead have a queue of work items, and a smaller pool of Tasks which 'pull items' off the queue until it's empty. This means you can performance test different numbers of Tasks and find the best value for your problem.
It uses Invoke when updating the progress bar - this avoids the cross-thread exception.
No BackgroundWorker - just TaskFactory and Task
public partial class Form1 : Form
{
private const int UrlCount = 1000;
private const int taskCount = 10;
private ConcurrentQueue<string> urlList;
private List<Task> taskList;
public Form1()
{
InitializeComponent();
}
private void ResetQueue()
{
// fake up a number of strings to process
urlList = new ConcurrentQueue<string>(Enumerable.Range(0, UrlCount)
.Select(i => "http://www." + Guid.NewGuid().ToString() + ".com"));
}
private void button1_Click(object sender, EventArgs e)
{
ResetQueue();
var taskFactory = new TaskFactory();
// start a bunch of tasks
taskList = Enumerable.Range(0, taskCount).Select(i => taskFactory.StartNew(() => ProcessUrl()))
.ToList();
}
void ProcessUrl()
{
string current;
// keep grabbing items till the queue is empty
while (urlList.TryDequeue(out current))
{
// run your code
FindWIN(current);
// invoke here to avoid cross thread issues
Invoke((Action)(() => UpdateProgress()));
}
}
void FindWIN(string url)
{
// your code here
// as a demo, sleep a sort-of-random time between 0 and 100 ms
Thread.Sleep(Math.Abs(url.GetHashCode()) % 100);
}
void UpdateProgress()
{
// work out what percentage of the queue is processed
progressBar1.Value = (int)(100 - ((double)urlList.Count * 100.0 / UrlCount));
}
}
You should set WorkerReportsProgress property of your worker to true on initialization stage.
I am making an application using WPF and C#. What I'm trying to do is to print a lots of shapes on a canvas with some time in between when I push a button. At the moment when I push the button, everything just pops up at once. I have tried to sleep the thread for some time between every "print" but that doesn't help, it just take longer time before everything splash up at once. What I want to achive is that the shapes pop up one at a time with lets say 0.5 seconds in between. The code is the following:
private void Create_Click(object sender, RoutedEventArgs e)
{
Random random = new Random();
for (int i = 0; i < 50; i++)
{
Thread.Sleep(500);
Path f = FlowerFactory.createFlower(FlowerBP, true);
Canvas.SetLeft(f, random.Next(0, 1650));
Canvas.SetTop(f, random.Next(0,1000));
DrawBoard.Children.Add(f);
}}
You need to, first, run the loop in a background thread so that it doesn't block the UI from updating; and second, send the UI-rendering tasks back to the UI thread. For the first, you can use Task.Factory.StartNew, and for the second, use Dispatcher.Invoke:
Random random = new Random();
Task.Factory.StartNew(() =>
{
for (int i = 0; i < 50; i++)
{
Thread.Sleep(500);
Dispatcher.Invoke(DispatcherPriority.Render, new Action(() =>
{
Path f = FlowerFactory.createFlower(FlowerBP, true);
Canvas.SetLeft(f, random.Next(0, 1650));
Canvas.SetTop(f, random.Next(0,1000));
DrawBoard.Children.Add(f);
}));
}
});
It was bad decision to sleep your main thread that responsible for GUI.
Try to use DispatchTimer. For example:
DispatcherTimer m_dispatcherTimer = new DispatcherTimer();
int m_count = 50;
private void Init()
{
m_dispatcherTimer.Tick += new EventHandler(OnTick);
m_dispatcherTimer.Interval = new TimeSpan(0, 0, 1);
}
private void Create_Click(object sender, RoutedEventArgs e)
{
m_count = 50;
m_dispatcherTimer.Start();
}
private void OnTick(object sender, EventArgs e)
{
// Draw your shapes here
if(0>=--m_count)
{
m_dispatcherTimer.Stop();
}
}
using System.Reactive.Concurrency;
using System.Reactive.Linq;
...
var subscription = Observable.Interval(TimeSpan.FromSeconds(0.5)).Take(50).Subscribe(x=>CreateRandomFlower);
For performance i have to replace DispatcherTimer with a BackGroundWorker to handle a intensive query that runs every 5 sec by using a Threading Timer.
I no longer get any result when implementing the following code, most of the times my application shuts down as well.
public void CaculateTimeBetweenWegingen()
{
if (listWegingen.Count > 1)
msStilstand = (DateTime.Now - listWegingen[listWegingen.Count - 1]).TotalSeconds;
if(msStilstand >= minKorteStilstand)
{
stopWatch.Start();
if (msStilstand >= minGroteStilstand)
{
FillDuurStilstandRegistrationBtn();
if (zelfdeStilstand == false)
{
CreateRegistrationButton();
zelfdeStilstand = true;
}
if (msStilstand <= maxGroteStilstand){
//....
}
}
}
else //new weging
{
if (stopWatch.IsRunning == true)
{
timerStilstand.Stop();
stopWatch.Stop();
//huidige registrationBtn
if (GlobalObservableCol.regBtns.Count > 1)
{
GlobalObservableCol.regBtns[GlobalObservableCol.regBtns.Count - 1].StopWatchActive = false;
GlobalObservableCol.regBtns[GlobalObservableCol.regBtns.Count - 1].DuurStilstand =
String.Format("{0:D2}:{1:D2}:{2:D2}", stopWatch.Elapsed.Hours, stopWatch.Elapsed.Minutes, stopWatch.Elapsed.Seconds);
}
}
zelfdeStilstand = false;
}
}/*CaculateTimeBetweenWegingen*/
public void CreateRegistrationButton()
{
InitializeDispatcherTimerStilstand();
RegistrationButton btn = new RegistrationButton(GlobalObservableCol.regBtns.Count.ToString());
btn.RegistrationCount = GlobalObservableCol.regBtnCount;
btn.Title = "btnRegistration" + GlobalObservableCol.regBtnCount;
btn.BeginStilstand = btn.Time;
GlobalObservableCol.regBtns.Add(btn);
GlobalObservableCol.regBtnCount++;
btn.DuurStilstand = String.Format("{0:D2}:{1:D2}:{2:D2}", 0, 0, 0);
}
public void InitializeDispatcherTimerWeging()
{
worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(Worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync();
}
void Worker_DoWork(object sender, DoWorkEventArgs e)
{
TimerCallback callback = MyTimerCallBack;
timerWegingen = new Timer(callback);
timerWegingen.Change(0, 5000);
}
public void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
worker.RunWorkerAsync();
}
private void MyTimerCallBack(object state)
{
DisplayWegingInfo();
CaculateTimeBetweenWegingen();
}
The button gets refilled with new values every 1 sec trough a other timer. "DuurStilstand" is a dependency property
private void FillDuurStilstandRegistrationBtn()
{
TimeSpan tsSec = TimeSpan.FromSeconds(stopWatch.Elapsed.Seconds);
TimeSpan tsMin = TimeSpan.FromMinutes(stopWatch.Elapsed.Minutes);
TimeSpan tsHour = TimeSpan.FromMinutes(stopWatch.Elapsed.Hours);
if (GlobalObservableCol.regBtns.Count >= 1
&& GlobalObservableCol.regBtns[GlobalObservableCol.regBtns.Count - 1].StopWatchActive == true)
{
GlobalObservableCol.regBtns[GlobalObservableCol.regBtns.Count - 1].DuurStilstand =
String.Format("{0:D2}:{1:D2}:{2:D2}", tsHour.Hours, tsMin.Minutes, tsSec.Seconds);
}
}
All the above code is written in a separate c# class.
How exactly do i make this code work with BackGroundWorker and how / where to update the GUI with Dispatcher/Invoke. Been trying for a long while and i cannot seem to solve this issue atm.
I have also seen that the Complete method of the BackGroundWorker can be used to update the GUI, but not sure how exactly. The buttons get created and saved in a ObservableCollection.
public static ObservableCollection<RegistrationButton> regBtns = new ObservableCollection<RegistrationButton>();
Some examples would be most useful. Since i know more or less what has to been done but not exactly sure how to implement it.
Best Regards,
Jackz
I don't understand the meaning of your app but you'll be able to update UI like this
public void worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.ApplicationIdle, new Action(() =>
{
//do your stuff
}));
}
Maybe the Rendering event should help you to deal with the UIThread.
CompositionTarget.Rendering += (s, args) =>
{
//do your stuff
};
Hope it help