c# How to change/access WinForms controls from a different class - c#

So I'm trying to change the text from a WinForms project, from another class than the Form class.
It should work like this:
But instead it does this:
The way I used to do it is pass along the object as a parameter to my other class and from that other class I could change the text. I do the same with the progressbar and it does work there, so it's weird that it works with the progressbar but not the label.
I use this method to change the progressbar:
public void IncreaseProgress(int progBarStepSize, String statusMsg, int currentProject=-1) {
if (currentProject != -1) {
lblStatus.Text = String.Format("Status: {0} | project {1} of {2}",statusMsg,currentProject,ProjectCount);
}
else {
lblStatus.Text = String.Format("Status: {0}",statusMsg);
}
pb.Increment(progBarStepSize);
}
And here is where I use the method:
public void InitialiseFile(List<string> filePaths, int eurojobType)
{
foreach (string sheet in outputSheets) {
switch (sheet) {
case "Summary":
for (int i = 0; i < filePaths.Count; i++) {
var filePath = filePaths[i];
IncreaseProgress(1, "Reading Summary", i);
worksheetIn = excelReader.ReadExcelSummary(filePath);
IncreaseProgress(1, "Writing Summary", i);
excelWriter.WriteExcelSummary(worksheetIn);
}
break;
case "Monthly_Cat1":
for (int i = 0; i < filePaths.Count; i++) {
var filePath = filePaths[i];
IncreaseProgress(1, "Reading Monthly", i);
worksheetIn = excelReader.ReadExcelMonthly(filePath);
IncreaseProgress(1, "Writing Monthly", i);
excelWriter.WriteExcelMonthly(worksheetIn);
}
break;
}
}
IncreaseProgress(1, "Completed!");
}
Now I know this code works because the progressbar increments. And it should jump in the first if-loop because i gets passed as a parameter, which is never -1.
//manager class
private Label lblStatus;
private ProgressBar pb;
public Manager(ProgressBar pb, Label lbl){
this.pb = pb;
lblStatus = lbl;
}
//Form class
Manager mgr = new Manager(progressBar1, lblStatus, projectFilePaths.Count, outputSheets.ToArray(), exportPath);
mgr.InitialiseFile(projectFilePaths, eurjobType);

You can call lblStatus.Refresh(); to force the control to be redrawn, after setting its Text.
But consider Slaks comment:
Don't do blocking work on the UI thread
You can consider using BackgroundWorker or Task.Run or async/await pattern instead.
As an example:
private async void button1_Click(object sender, EventArgs e)
{
await Task.Run(() =>
{
for (int i = 0; i < 10000; i++)
{
this.Invoke(new Action(() =>
{
label1.Text = i.ToString();
label1.Refresh();
}));
}
});
}
This way the numbers increase, the label refreshes and shows the new value, while the UI is responsive and for example you can move your form or click on other button.
You should put your UI related codes in an action fired by Invoke to prevent receiving cross thread operation exception.

Related

How to create and add controls in run time with BackgroundWorker

I don't know how to implement a method with a separate thread using the BackgroundWorker in WinForms.
I want this method (after every click on a button) to perform:
create ProgressBar (each new one under the previous one)
create Bitmap and BackgroundWorker
set color of every pixel in that Bitmap in the separate thread using BackgroundWorker
display a precentage progress on the ProgressBar
after completing: create a new form with bitmap on the background
after completing: remove the ProgressBar
My code:
List<BackgroundWorker> Workers;
List<ProgressBar> Progress;
int OperationsCount = 0;
private void ShowProgress(int n, int percent)
{
Progress[n].Value = percent;
}
private void Blend(object sender, DoWorkEventArgs e)
{
Bitmap BlendedImage = ... // creates a bitmap
for (int i = 0; i < BlendedImage.Width; i++)
{
for (int j = 0; j < BlendedImage.Height; j++)
{
... //changing colour of every pixel
}
this.Invoke(new Action(()=>ShowProgress((int)e.Argument, (int)(100 * (double)(i/BlendedImage.Width)))));
}
Form BlendedImage_Form = new Form();
BlendedImage_Form.Size = new Size(BlendedImage.Width, BlendedImage.Height);
BlendedImage_Form.BackgroundImage = BlendedImage;
BlendedImage_Form.BackgroundImageLayout = ImageLayout.Stretch;
this.Invoke(new Action(() => BlendedImage_Form.Show()));
}
private void PerformBlending_Button_Click(object sender, EventArgs e)
{
int n = OperationsCount++;
Progress.Add(new ProgressBar());
Progress[n].Size = ...
Progress[n].Location = ...
Progress[n].Maximum = 100;
this.Controls.Add(Progress[n]);
Workers.Add(new BackgroundWorker());
Workers[n].DoWork += new DoWorkEventHandler(Blend);
Workers[n].RunWorkerCompleted += (object _sender, RunWorkerCompletedEventArgs _e) =>
{
//OperationsCount--;
//Progress[n].Dispose();
//this.Controls.Remove(Progress[n]);
//Progress.RemoveAt(n);
};
Workers[n].RunWorkerAsync(n);
}
When I click the button only once then everything seems to be good but when I click the button two times then program:
creates the first ProgressBar which shows progress correctly and the new form and bitmap are displayed also correctly
creates the second ProgressBar but it doesn't show the progress at all and no form and no bitmap is displayed.
PS I'd rather use BackgroundWorker than other tools.
As per your comment here is the solution
public void DoSomething()
{
if (this.InvokeRequired)
{
this.Invoke(new Action(()=> DoSomething()));
}
else
{
// update the ui from here, no worries
}
}
In this code, I am modifying the object on main thread. If the calls made from non-UI thread this will goes in InvokeRequired.
// From this code you given in comment
https://pastebin.com/45jQXCt9
Please try with making instance inside invoke. It should work.

Progress Bar in WPF application for long running WCF service

I have a long running WCF service and a client that consumes it via WPF. Am using a Progress Bar to notify the client of the percentage completion for a particular process (a method in WCF: I need to be able to display the percentage based on the looping counter in the service)
I have used Background Worker to display progress percentage but it does not display the progress correctly. (displays just 0 and 100 not the in between values) Everything works fine in DEBUG mode but not in RELEASE mode! (Progress bar is updated sequentially in DEBUG mode)
I tried using callbacks/wsDualHttpBinding but have some difficulty in getting this incorporated for all clients. So, had to drop this option.
working on async/await
I have googled quite a few links but nothing helps with my problem.
Please guide me on how to get the current/running value from a method that is not complete yet from a WCF service so I could populate the progress bar percentage based on this value. (in between values)
P.S: WCF service uses wsHttpBinding
sample code below:
public Progress()
{
// Start the BackgroundWorker.
myBGWorker.WorkerReportsProgress = true;
myBGWorker.WorkerSupportsCancellation = false;
myBGWorker.DoWork += myBGWorker_DoWork;
myBGWorker.ProgressChanged += myBGWorker_ProgressChanged;
}
public void ShowProgress()
{
myBGWorker.RunWorkerAsync();
}
private void myBGWorker_DoWork(object sender, DoWorkEventArgs e)
{
// fetches a static value from the service
string value = _client.Progress();
int p=0;
for (int i = 1; i <= 100; i++)
{
// Report progress.
p = Convert.ToInt32(_client.Progress());
_logger.Debug("Progress5:" + p.ToString());
myBGWorker.ReportProgress(p, i);
}
}
private void myBGWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.Dispatcher.BeginInvoke(new Action(delegate
{
progressBar1.Value = e.ProgressPercentage;
}), DispatcherPriority.ContextIdle);
}
Just to understand how it could be implemented. This is part of my working code.
.xaml file:
<ProgressBar x:Name="ProgressBarCompare" HorizontalAlignment="Left" Height="20" Margin="10,157,0,0" VerticalAlignment="Top" Width="321"/>
Function with process:
private async void btnCompare_Click(object sender, RoutedEventArgs e)
{
ProgressBarCompare.Value = 0;
lblCompare.Content = "";
List<string> list1= (List<string>)Application.Current.Properties["list1"];
List<string> list2= (List<string>)Application.Current.Properties["list2"];
List<Result> output = new List<Result>();
List<Result> passed = new List<Result>();
int topCount = emailList.Count;
int currentItem = 0;
int topBound = topCount - 1;
while (currentItem < topCount)
{
var hash = await CheckOperation(list1[currentItem]); // this line perform progress bar to be filled
var result = list2.Contains(hash);
//some operations
if (Convert.ToInt32(Math.Ceiling(100d * currentItem / topBound)) < 51)
{
Style style = this.FindResource("LabelTemplateNotFilled") as Style;
lblCompare.Style = style;
}
else
{
Style style = this.FindResource("LabelTemplateFilled") as Style;
lblCompare.Style = style;
}
ProgressBarCompare.Value = Convert.ToInt32(Math.Ceiling(100d * currentItem / topBound));
lblCompare.Content = Convert.ToInt32(Math.Ceiling(100d * currentItem / topBound)) + "%";
currentItem++;
}
lblCompare.Content = "COMPLETE";
}
and core function to that:
private async Task<string> CheckOperation(string input)
{
var result = "";
await Task.Run(() =>
{
//perform some code
});
return result;
}

Firing events in loop are not updating UI in sequence

I was trying to update status on UI for a Long Running Operating. I've created a demo form based application, task it have multiple rows, each row is having days and default values in each column of datagrid is 0, once computation file computes one iteration for one day it will update UI and set 1 for that day.
I am using threading, delegates and events to implement this and it is working as expected if I put Thread.Sleep(100) between two event calls. If I put "Thread.Sleep(100)" inside last nested for loop then it updates UI as expected but as soon as I remove it and run loop without sleep, then it skips some of the columns on UI and directly update last few/random columns, as you can see in attached image link(Image of output of my code without thread sleep) only last column is getting updated.
If I am not mistaken all the events are getting fired in sequence then they should update UI in sequence too but it's not happening and I don't know why. I don't want to do this Sleep thing because I have around 14 calls in actual application for UI status update and it will run under a loop so if It put sleep(100) then it will cost me a lot, is there any way to do it without SLEEP?
Image of output of my code without thread sleep
public class Class1 : IGenerate
{
public event MessageEventHandler OnMessageSending;
public void LongOperationMethod(BindingList<Status> _statusData)
{
if (OnMessageSending != null)
{
MessageEventArgs me = new MessageEventArgs();
/// Loop for 2-3 Weeks
for (; ; ){
/// Loop for 7 day
for (; ; )
{
/// Calculation on everyday
for (int j = 0; j != 1000; ++j)
{
// to do
}
me.weekNo = k;
me.DayNo = i;
OnMessageSending(me);
}
}
me.Message = "Process completed successfully...";
OnMessageSending(me);
}
else
{
throw new ArgumentException("Event hasn`t been rised, so we cannot continue working.");
}
}
}
**UI file:**
<pre><code>
public partial class Form1 : Form
{
BindingList<Status> _statusData = new BindingList<Status>();
delegate void StringParameterDelegate(string value);
Class1 cls = new Class1();
public Form1()
{
InitializeComponent();
labelProgress.Text = "";
}
private void button1_Click_1(object sender, EventArgs e)
{
for (int i = 1; i <= 2; ++i)
{
_statusData.Add(new Status { Week = "Week" + i, Day1 = 0, Day2 = 0, Day3 = 0, Day4 = 0, Day5 = 0, Day6 = 0, Day7 = 0 });
}
dataGridView1.DataSource = _statusData;
}
private void button2_Click(object sender, EventArgs e)
{
Thread t1 = new Thread(() => StartingThread(_statusData));
t1.Start();
}
void StartingThread(BindingList<Status> _statusData)
{
IGenerate generate = new Class1();
generate.OnMessageSending += new MessageEventHandler(generate_OnMessageSending);
generate.LongOperationMethod(_statusData);
}
private void generate_OnMessageSending(MessageEventArgs e)
{
int weekNo = e.weekNo;
int dayNo = e.DayNo;
this.dataGridView1.BeginInvoke(new MethodInvoker(() => dataGridView1.Rows[e.weekNo].Cells[e.DayNo + 1].Value = 1));
this.labelProgress.BeginInvoke(new MethodInvoker(() => this.labelProgress.Text = e.Message));
}
}
</code></pre>
It looks like you are sending the same instance of MessageEventArgs every time, and just updating that one instance on the background thread. This means that your event handler on the UI thread will retrieve the exact same instance of MessageEventArgs that is being updated in the loop! By the time your UI handler gets the MessageEventArgs, its .weekNo and .DayNo properties could well have been modified by the next iteration of the loop, since they are running on separate threads.
To fix this, create a new instance of MessageEventArgs every time you call OnMessageSending().
Relevant snippet:
MessageEventArgs me = new MessageEventArgs();
me.weekNo = k;
me.DayNo = i;
OnMessageSending(me);

Adding percentage text to progressbar C#

I have a method that shows when a process bar is in execution and when is successfully completed.
I worked fine, but I would like to add a percentage showing a 100% if is complete and less if it got stuck some where.
I have made several research online but I could not adapt anything to the solution that I am looking for.
This is my code:
private void progressBar()
{
int i;
progressBar1.Minimum = 0;
progressBar1.Maximum = 100;
for (i = 0; i <= 100; i++)
{
progressBar1.Value = i;
}
}
I use the method call on my execution button by calling it with the follow:
progressBar();
Thanks
I have adjust the prograssBar method with the following lines.
The solution works.
Thanks
int percent = (int)(((double)progressBar1.Value / (double)progressBar1.Maximum) * 100);
progressBar1.Refresh();
progressBar1.CreateGraphics().DrawString(percent.ToString() + "%",
new Font("Arial", (float)8.25, FontStyle.Regular),
Brushes.Black,
new PointF(progressBar1.Width / 2 - 10, progressBar1.Height / 2 - 7));
In order to implement the progress in your operation, the operation's length must be calculated first. if it's not possible, you can't show a progress bar for that operation. (maybe only a loading gif)
but if so, There is an interface (IProgress) which can help you implement the progress reports.
First thing you should know, You must do the main task on another thread, and report the progress to the UI Thread. a simple example of this work would be something like this.
Progress.cs
public class Progress<T> : IProgress<T>
{
private readonly Action<T> _progressAction;
public Progress(Action<T> action)
{
_progressAction = action;
}
public void Report(T value)
{
_progressAction?.Invoke(value);
}
}
Your code would be like this, in which the task starts after you click a button named ButtonBase
Progress<int> MyProgressObject { get; set; }
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
MyProgressObject = new Progress<int>(ProgressAction);
ThreadPool.QueueUserWorkItem(TimeConsumingTask);
}
public void TimeConsumingTask(object state)
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);
MyProgressBar.Dispatcher.Invoke(() => ProgressAction(i));
}
}
public void ProgressAction(int progress)
{
MyProgressBar.Value = progress;
}
I know It might look difficult but this is the proper way of doing time consuming tasks and prevent UI block
If you use it as a part of backgroundworker it works perfectly
I added a Label in the middle of the progressbar
And i added last row in my bgw_ProgressChanged method
private void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
p_bar.Value = e.ProgressPercentage;
fnameLbl.Text = e.UserState.ToString();
percentLbl.Text = "%" + (e.ProgressPercentage).ToString();
}
ProgressPercentagevalue comes from the method below
foreach (var item in filebox1)
{
System.IO.File.Move(item, Path.Combine(destdir, Path.GetFileName(item)));
++counter;
int tmp = (int)((counter* 100) / totfiles);
bgw.ReportProgress(tmp, "File transfered : " + Path.GetFileName(item));
Thread.Sleep(100);
}
Totfiles is the number of files that I get from server.
Thread.Sleep(100) let's you see for a short time what is displayed with fnameLbl.Text
int total = ;
int val = ;
double createDivider = total / 100;
int percent = val / createDivider;
this value (percent) is the right percent '%' of total

Thread inside thread trying to invoke and update ui

I have on winform with a textbox and on textchanged executes a background thread:
private void txtFathersLast_TextChanged(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(_ => WaitWhileUserTyping());
}
private void WaitWhileUserTyping()
{
var keepWaiting = true;
while (keepWaiting)
{
_keyPressed = false;
Thread.Sleep(TypingDelay);
keepWaiting = _keyPressed;
}
Invoke((MethodInvoker)(ExecuteSearch));
_waiting = false;
}
private void ExecuteSearch()
{
Thread.Sleep(200);
Task.Factory.StartNew(() =>
{
using (DataReference.SearchWCF search = new DataReference.SearchWCF())
{
_similaritySearchResults = search.SearchPersonBySimilarity(txtFathersLast.Text, txtMothersLast.Text, txtName.Text, DateTime.Now, 10);
}
}).ContinueWith(t=>{
if (this.InvokeRequired)
{
this.BeginInvoke(new Action(() =>
{
if (_similaritySearchResults != null && _similaritySearchResults.Tables["data"].Rows.Count > 0)
{
DataTable dt = _similaritySearchResults.Tables["data"];
Infragistics.Win.Misc.UltraTile newTile = null;
for (int index = 0; index < dt.Rows.Count; index++)
{
newTile = new Infragistics.Win.Misc.UltraTile("Person X");
newTile.Control = new CustomControls.Controls.PersonResult("123", "123", index + 150);
newTile.Tag = new Guid("90D27721-7315-4B86-9CFD-4F7D02921E9A");
newTile.DoubleClick += TileDoubleClick;
tilePanel.Tiles.Add(newTile);
}
}
}));
}
else
{
if (_similaritySearchResults != null && _similaritySearchResults.Tables["data"].Rows.Count > 0)
{
DataTable dt = _similaritySearchResults.Tables["data"];
Infragistics.Win.Misc.UltraTile newTile = null;
for (int index = 0; index < dt.Rows.Count; index++)
{
newTile = new Infragistics.Win.Misc.UltraTile("Person X");
newTile.Control = new CustomControls.Controls.PersonResult("123", "123", index + 150);
newTile.Tag = new Guid("90D27721-7315-4B86-9CFD-4F7D02921E9A");
newTile.DoubleClick += TileDoubleClick;
tilePanel.Tiles.Add(newTile);
}
}
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
This is working fine, the application goes to a database then get results and update the UI, adding tiles to a control depending of the number of records returned by database.
Now, the problem comes when I try to add another background thread into my custom control:
new CustomControls.Controls.PersonResult("123", "123", index + 150);
The code for the control is:
protected override void InitLayout()
{
// if I comment this then everything works fine
// but if I leave this, then the UI freezes!!
GetPictureAsync();
base.InitLayout();
}
/// <summary>
///
/// </summary>
private void GetPictureAsync()
{
// This line needs to happen on the UI thread...
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
Random sleep = new Random();
System.Threading.Thread.Sleep(sleep.Next(1000,3000));
if (this.pbPhoto.InvokeRequired)
{
this.pbPhoto.Invoke(new Action(() =>
{
this.Load(#"E:\Photos\" + PhotoId.ToString() + ".jpg");
//this.pbPhoto.Image = Utility.Common.GetResourceImage("woman_sample.jpg");
}));
}
else
{
this.Load(#"E:\Photos\" + PhotoId.ToString() + ".jpg");
//this.pbPhoto.Image = Utility.Common.GetResourceImage("woman_sample.jpg");
}
}, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
}
So the problem seems to be that I first execute a thread for looking when to start search, then inside that thread I run another thread in order to get data from database, and then each control updated in the UI will run another thread to get a picture and update a picturebox.
Anyone knows how to solve this? or a way to work around this?
When you call
new CustomControls.Controls.PersonResult("123", "123", index + 150)
Is "123" a literal string, or are they being read from UI controls. For example,
new CustomControls.Controls.PersonResult(txtFathersName.Text", txtMothersName.Text, index + 150)
i cant test it right now, but isnt accessing the Text property not allowed from a thread other than the one that created the control?
I think the problem lies in you forcing the Task in GetPictureAsync to execute on UI thread and then you are calling Thread.Sleep(). This update UI in Task using TaskScheduler.FromCurrentSynchronizationContext question tackles the same problem as you are having. I would rewrite your code as:
private void async GetPictureAsync()
{
Random sleep = new Random();
await TaskEx.Delay(sleep.Next(1000,3000));
this.Load(#"E:\Photos\" + PhotoId.ToString() + ".jpg");
}

Categories