I have a method which I am using to enable a user to input time. I have set it up that once a button is pressed it opens a dialog. The user can then select the time, once the user selects the time it is meant to populate a textview however I have found that once the dialog opens the textview is assigned its default value of null. It doesn't wait.
When i have ran tests using Toast messages I can see that the underlying code does work however its the order which is messing me about.
Currently I have attempting to implement the await feature with no avail.
I have attached my code. thanks for any help or advice. I am reasonably new to c# and xamarin by the way.
<<within on create>>
StartTimePickerBTN.Click += async delegate
{
OnCreateDialog().Show();
StartTimeTV.Text = (await UpdateTime()).ToString();
};
private void TimePickerCallback(object sender, TimePickerDialog.TimeSetEventArgs e)
{
hour = e.HourOfDay;
minute = e.Minute;
}
public async Task<string> UpdateTime()
{
string time = string.Format("{0}:{1}", hour, minute.ToString().PadLeft(2, '0'));
return time;
}
You are not awaiting the call to UpdateTime, instead you are calling .ToString() on the Task that is returned, instead of the string.
Your event handler should do
StartTimeTV.Text = (await UpdateTime()).ToString();
I managed to resolve my issue however i'm sure its not the best way but i will post my code below.
The plus is now the TextView is populated at the correct stage and waits until user selection.
Within OnCreate, Please note the bool is a class wide variable
StartTimePickerBTN.Click += delegate
{
SelectStartOrEndTime = true;
OnCreateDialog().Show();
};
EndTimePickerBTN.Click += delegate
{
SelectStartOrEndTime = false;
OnCreateDialog().Show();
};
protected Dialog OnCreateDialog()
{
return new TimePickerDialog(this, TimePickerCallback, hour, minute, false);
}
private void TimePickerCallback(object sender, TimePickerDialog.TimeSetEventArgs e)
{
hour = e.HourOfDay;
minute = e.Minute;
UpdateTime();
}
private void UpdateTime()
{
if (SelectStartOrEndTime == true)
{
StartTimeTV.Text = string.Format("{0}:{1}", hour, minute.ToString().PadLeft(2, '0'));
}
else
{
EndTimeTV.Text = string.Format("{0}:{1}", hour, minute.ToString().PadLeft(2, '0'));
}
}
Related
i make a code in C# where i extract some records from an Access database , but i need the while going to the next iteration to depend on the click of a button. i tried with some Thread or Tasks , but it didn't worked because it blocked the UI which i need it to be seen and clickable.
Here's the code:
bool nextClick = false ;
while (readerSelect.Read())
{
// show the correct panel
if (string.Compare(readerSelect[2].ToString(), "P1") == 0)
{
// panel with type 1
textBoxP1Text1.Text = readerSelect[3].ToString();
textBoxP1Text2.Text = readerSelect[4].ToString();
pictureBoxP1Image.ImageLocation = readerSelect[6].ToString();
}
else
{
// panel with type 2
textBoxP1Text2.Text = readerSelect[5].ToString();
}
//this while need to be kind of infinite so the interation can't be processed and
//so when i need to change iteration i click the buttonNext
while (!nextClick) {
startWhile:;
MethodInvoker mi = delegate () {
if (nextClick)
{
Application.DoEvents();
// System.Windows.Forms.Application.Run();
}
};
this.Invoke(mi);
//break;
goto startWhile;
}
private void buttonNext_Click(object sender, EventArgs e)
{
// click on the next button
nextClick = true;
}
You can use a semaphore within an async task, have the button Release it during each click, and have the while loop await it each time through. Here's a quick example, using a form that has a button1 and a label1 added to it:
public partial class Form1 : Form
{
private readonly SemaphoreSlim signal = new SemaphoreSlim(0, int.MaxValue);
public Form1()
{
this.InitializeComponent();
this.RunLoop();
}
private async void RunLoop()
{
var i = 0;
while (true)
{
this.label2.Text = $"Enqueued: {this.signal.CurrentCount}";
await this.signal.WaitAsync(); // Wait button click async
await Task.Delay(1000); // Simulate work
this.label1.Text = $"Completed: {++i}";
}
}
private void button1_Click(object sender, EventArgs e)
{
this.signal.Release();
this.label2.Text = $"Enqueued: {this.signal.CurrentCount + 1}";
// Or if you want to limit the # people can queue up, then put this whole
// thing in an `if (signal.CurrentCount < myLimit)` block, and optionally
// disable the button once limit has been reached, and re-enable it right
// before the `WaitAsync` call above.
}
}
While Dax Fohl's answer works, it seems like you've got a problem in your design. I think you're violating the Single Responsibility Principle by having too much business logic going on in the Form class.
I'd recommend factoring out the business logic into its own class. Then rather than running through everything in a loop, you simply have the button click event process the next record and display the result. Here's an example of what I mean:
public partial class Form1 : Form
{
private readonly DataProcessor dataProcessor = new DataProcessor();
public Form1()
{
this.InitializeComponent();
}
private void button1Next_Click(object sender, EventArgs e)
{
this.buttonNext.Enabled = false;
this.ProcessNext();
}
private async void ProcessNext()
{
string s = await this.dataProcessor.ProcessNext();
this.textBoxP1Text1.Text = s;
this.buttonNext.Enabled = true;
}
}
public class DataProcessor
{
private readonly Random r = new Random(); // Or reader or whatever.
public async Task<string> ProcessNext() // Just using `string` as an example.
{
await Task.Delay(1000);
return this.r.Next().ToString();
}
}
I think this will be easier to understand and more maintainable in the future. When a new team member looks at semaphore stuff (or your future self), it'll be hard to understand/remember what the point of all that was. Here, you just have a local function that does one thing and is easy to follow.
I am using Windows.UI.Xaml.Controls.ContentDialog to show a confirmation. And based on the response from the first dialog I would (or would not) show another dialog. But, when I am trying to open the second content dialog it throws : "Only a single ContentDialog can be open at any time." error. Even though in the UI, first dialog would be closed but somehow I am still not able to open the second dialog. Any idea?
I have created some code to handle this type of conundrum in my Apps:
public static class ContentDialogMaker
{
public static async void CreateContentDialog(ContentDialog Dialog, bool awaitPreviousDialog) { await CreateDialog(Dialog, awaitPreviousDialog); }
public static async Task CreateContentDialogAsync(ContentDialog Dialog, bool awaitPreviousDialog) { await CreateDialog(Dialog, awaitPreviousDialog); }
static async Task CreateDialog(ContentDialog Dialog, bool awaitPreviousDialog)
{
if (ActiveDialog != null)
{
if (awaitPreviousDialog)
{
await DialogAwaiter.Task;
DialogAwaiter = new TaskCompletionSource<bool>();
}
else ActiveDialog.Hide();
}
ActiveDialog = Dialog;
ActiveDialog.Closed += ActiveDialog_Closed;
await ActiveDialog.ShowAsync();
ActiveDialog.Closed -= ActiveDialog_Closed;
}
public static ContentDialog ActiveDialog;
static TaskCompletionSource<bool> DialogAwaiter = new TaskCompletionSource<bool>();
private static void ActiveDialog_Closed(ContentDialog sender, ContentDialogClosedEventArgs args) { DialogAwaiter.SetResult(true); }
}
To use these Methods, you need to create the ContentDialog and its content in a variable, then pass the variable, and bool into the Method.
Use CreateContentDialogAsync(), if you require a callback in your app code, say if you have a button in your Dialog, and you want wait for a button press, and then get the value from the form in code after the dialog.
Use CreateContentDialog(), if you don't need to wait for the Dialog to complete in your UI Code.
Use awaitPreviousDialog to wait for the previous dialog to complete before showing the next Dialog, or set false, to remove the previous Dialog, then show the next Dialog, say, if you want to show an Error Box, or the next Dialog is more important.
Example:
await ContentDialogMaker.CreateContentDialogAsync(new ContentDialog
{
Title = "Warning",
Content = new TextBlock
{
Text = "Roaming Appdata Quota has been reached, if you are seeing this please let me know via feedback and bug reporting, this means that any further changes to data will not be synced across devices.",
TextWrapping = TextWrapping.Wrap
},
PrimaryButtonText = "OK"
}, awaitPreviousDialog: true);
William Bradley's approach above is good. Just to polish it up a bit, here is an extension method to submit and await the showing of a content dialog; the dialog will be shown after all the other content dialogs that have already been submitted. Note: by the time the user clicks through earlier backlogged dialogs you may no longer want to show the dialog that you have submitted; to indicate this you may pass a predicate that will be tested after the other dialogs have been dismissed.
static public class ContentDialogExtensions
{
static public async Task<ContentDialogResult> EnqueueAndShowIfAsync( this ContentDialog contentDialog, Func<bool> predicate = null)
{
TaskCompletionSource<Null> currentDialogCompletion = new TaskCompletionSource<Null>();
TaskCompletionSource<Null> previousDialogCompletion = null;
// No locking needed since we are always on the UI thread.
if (!CoreApplication.MainView.CoreWindow.Dispatcher.HasThreadAccess) { throw new NotSupportedException("Can only show dialog from UI thread."); }
previousDialogCompletion = ContentDialogExtensions.PreviousDialogCompletion;
ContentDialogExtensions.PreviousDialogCompletion = currentDialogCompletion;
if (previousDialogCompletion != null) {
await previousDialogCompletion.Task;
}
var whichButtonWasPressed = ContentDialogResult.None;
if (predicate == null || predicate()) {
whichButtonWasPressed = await contentDialog.ShowAsync();
}
currentDialogCompletion.SetResult(null);
return whichButtonWasPressed;
}
static private TaskCompletionSource<Null> PreviousDialogCompletion = null;
}
Another way might be to use a SemaphoreSlim(1,1).
"Only a single ContentDialog can be open at a time"
This statement is not entirely true. You can only ShowAsync one ContentDialog at a time. All you need to do is hide the current ContentDialog before opening another one. Then, after the "await ShowAsync" of the second ContentDailog, your simply call "var T = this.ShowAync()" to unhide it. Example:
public sealed partial class MyDialog2 : ContentDialog
{
...
}
public sealed partial class MyDialog1 : ContentDialog
{
...
private async void Button1_Click(object sender, RoutedEventArgs e)
{
// Hide MyDialog1
this.Hide();
// Show MyDialog2 from MyDialog1
var C = new MyDialog2();
await C.ShowAsync();
// Unhide MyDialog1
var T = ShowAsync();
}
}
I know this is slightly old, but one simpler solution instead of going through all this pain is to just register a callback for the ContentDialog_Closed event. By this point you can be sure the previous dialog has been closed, and can open your next dialog. :)
Only a single ContentDialog can be open at any time.
That is a fact. (I was really surprised, but just for a moment)
You can't have more than one at any time and it is more like guideline from Microsoft, because it's really messy to have multiple dialogs on top of each other filled with content.
Try to change your UX to display only one sophisticated ContentDialog and for all other messages use MessageDialog - it supports multiple buttons(only two for phones, but more on desktop) for user response but without Checkboxes or similar "smart"-content stuff.
In my case MessageDialogs were really helpful, but in some areas I used chained ContentDialogs but for that you must await the first one, and open second right after without any exceptions. In your case it seems like ContentDialog was not fully closed when you tried to open next one.
Hope it helps!
I like this answer https://stackoverflow.com/a/47986634/942855, this will allow us ot handle binding all events.
So extended it a little to check the multiple calls to show dialog.
private int _dialogDisplayCount;
private async void Logout_OnClick(object sender, RoutedEventArgs e)
{
try
{
_dialogDisplayCount++;
ContentDialog noWifiDialog = new ContentDialog
{
Title = "Logout",
Content = "Are you sure, you want to Logout?",
PrimaryButtonText = "Yes",
CloseButtonText = "No"
};
noWifiDialog.PrimaryButtonClick += ContentDialog_PrimaryButtonClick;
//await noWifiDialog.ShowAsync();
await noWifiDialog.EnqueueAndShowIfAsync(() => _dialogDisplayCount);
}
catch (Exception exception)
{
_rootPage.NotifyUser(exception.ToString(), NotifyType.DebugErrorMessage);
}
finally
{
_dialogDisplayCount = 0;
}
}
modified predicate
public class Null { private Null() { } }
public static class ContentDialogExtensions
{
public static async Task<ContentDialogResult> EnqueueAndShowIfAsync(this ContentDialog contentDialog, Func<int> predicate = null)
{
TaskCompletionSource<Null> currentDialogCompletion = new TaskCompletionSource<Null>();
// No locking needed since we are always on the UI thread.
if (!CoreApplication.MainView.CoreWindow.Dispatcher.HasThreadAccess) { throw new NotSupportedException("Can only show dialog from UI thread."); }
var previousDialogCompletion = _previousDialogCompletion;
_previousDialogCompletion = currentDialogCompletion;
if (previousDialogCompletion != null)
{
await previousDialogCompletion.Task;
}
var whichButtonWasPressed = ContentDialogResult.None;
if (predicate == null || predicate() <=1)
{
whichButtonWasPressed = await contentDialog.ShowAsync();
}
currentDialogCompletion.SetResult(null);
return whichButtonWasPressed;
}
private static TaskCompletionSource<Null> _previousDialogCompletion;
}
I am struggling to workout how to create something that essentially pauses my while loop until my button1 is pressed, I know about the event handler button1_Click but I don't think that will work in this situation as I have lots of loops nested in each other on my form_load.
Any help would be highly appreciated!
This is a snipped of my code where I want the loop to be 'paused' with the notes:
while (reader2.Read())
{
QuestionSpace = Convert.ToString(reader2["Question Space"]);
label1.Text = QuestionSpace;
if (button1.Click = true) // if the button is clicked)
{
// continue with the while loop (I am going to add an INSERT SQL query in here later)
}
else
{
// pause until the button is pressed
}
}
My whole code for the form:
public partial class CurrentlySetTestForm : Form
{
private int QuestionID { get; set; }
private string QuestionSpace { get; set; }
public CurrentlySetTestForm()
{
InitializeComponent();
}
private void CurrentlySetTestForm_Load(object sender, EventArgs e)
{
string y = GlobalVariableClass.Signedinteacher;
MessageBox.Show(y);
Convert.ToInt32(y);
string connectionString = ConfigurationManager.ConnectionStrings["myconnectionstring"].ConnectionString;
SqlConnection connect = new SqlConnection(connectionString);
connect.Open();
SqlCommand command18 = new SqlCommand("SELECT [QuestionID] FROM QuestionStudentAssociation WHERE ( [StudentID]=#Signedinstudent)", connect);
command18.Parameters.AddWithValue("#Signedinstudent", y);
var reader = command18.ExecuteReader();
while (reader.Read())
{
QuestionID = Convert.ToInt32(reader["QuestionID"]);
SqlCommand command19 = new SqlCommand(#"SELECT [Question Space] FROM Questions WHERE ( [QuestionID] = #currentQID )", connect);
command19.Parameters.AddWithValue("#currentQID", QuestionID);
try
{
var reader2 = command19.ExecuteReader();
while (reader2.Read())
{
QuestionSpace = Convert.ToString(reader2["Question Space"]);
label1.Text = QuestionSpace;
if (button1.Click = true) // if the button is clicked)
{
// continue with the while loop (I am going to add an INSERT SQL query in here later)
}
else
{
// pause until the button is pressed
}
}
}
catch (SyntaxErrorException ex)
{
MessageBox.Show(ex.Message);
}
finally
{
MessageBox.Show("Done one loop");
}
}
}
}
Sounds like your not ready to learn TPL
So maybe a BackgroundWorker , you can paint it on the form
To make the click cancel the background worker have a look at Cancel backgroundworker
I would some time to learn TPL as its going to create a simpler and more elegant solution.
As for pausing I would refactor the code, you should not keep the reader open waiting on the user.
You do want event-driven response to UI events, always. However, I guess that you don't want to split your logic into a state machine by hand (where each event triggers progress to the next state). Well, you're in luck, the C# compiler has some keywords to build state machines automagically so you don't have to manage the details.
There are actually two different mechanisms for continuation-passing style implemented in C#. The old one, yield return, works great if your UI events are pretty much interchangeable (or you're only interested in one). Works like this:
IEnumerator<int> Coroutine;
// this could be a Form_Load, but don't you need to get the user information before making the database connection?
void BeginQuiz_Click( object sender, EventArgs unused )
{
Coroutine = RunQA();
}
IEnumerator<int> RunQA()
{
// connect to DB
// show first question on UI
return ContinueQA();
}
IEnumerator<int> ContinueQA()
{
// you can use a while loop instead if you really want
for( int question = 0; question < questionCount; ++question )
{
// check answer
if (/* too many wrong answers*/) {
// report failure in DB
yield break;
}
// get next question from DB
// show new question on the UI
// wait for UI action
yield return question;
}
// report score in DB
// update UI with completion certificate
}
void AnswerButton_Click( object sender, EventArgs unused )
{
answer = sender;
Coroutine.MoveNext(); // MAGIC HAPPENS HERE
}
void TimeoutTimer_Tick( object sender, EventArgs unused )
{
answer = TimeoutTimer;
Coroutine.MoveNext();
}
The magic comes from yield return. Every time the function reaches yield return, the compiler saves what you were doing. When the button click event comes and calls MoveNext, the compiler generates code that starts where yield return paused everything, and keeps going from there until the next yield return.
Important note, the code inside ContinueQA doesn't start when RunQA() does return ContinueQA(); It actually starts on the first MoveNext(). So split your code between RunQA() and ContinueQA accordingly.
If you need different pause reasons at different places in your code, then async/await will be more helpful.
A better way to handle this would be the use of a timer. This would allow the form to draw it's controls and handle all input, such as clicking the button.
Adjust the timer interval (ms) to your needs.
Another way of doing this would be, as Mehrzad Chehraz said, to use multi-threading.
On a side note, I would strongly recommend condition checks over the try/catch checks if possible.
Enable/Disable the timer using the button and call the loop when the timer ticks.
Example:
Timer loopTimer = new Timer();
private void Form1_Load(object sender, EventArgs e)
{
loopTimer.Interval = 100;
loopTimer.Tick += loopTimer_Tick;
loopTimer.Enabled = true;
}
void loopTimer_Tick(object sender, EventArgs e)
{
//perform the loop here at the set interval
}
private void button1_Click(object sender, EventArgs e)
{
//pause/play the loop
loopTimer.Enabled = !loopTimer.Enabled;
}
So, I'm trying to develop a simple application in visual C# which gets data from serial port and displays it in a textbox (to monitor temperature). I'm acquiring and displaying the data successfully, using the DataReceived event to update a global string variable and a timer to update the text field on my text box, as shown:
private void port_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
try
{
globalVar.updateTemp = port.ReadLine(); //This is my global string
}
catch (IOException)
{
}
catch (InvalidOperationException)
{
}
catch (TimeoutException)
{
}
}
private void timer1_Tick(object sender, EventArgs e)
{
tempDisplayBox.Text = globalVar.updateTemp; //This is my textbox updating
}
The only issue I have is that the value shown in the textbox keeps flashing, making it hard to read. My timer is set to trigger every 10 ms (which should be fast enough, right?). Is there any way to make it more stable? I realize this may be a newb question, but to be fair I am a newb :) Any help is appreciated! Thanks!
Do you really need it updating every 10ms? What about every 500 ms or if not that then 100ms. 100ms will require your update method run 10 times less and therefore update 10 times less. The flickering you are expiriencing is due to the refresh speed. You could create custom method which will only update the temp only when target Label or textBox value is different than source port. But that will only sort the flickering when temp is steady, when temp will start vary it will bring back the flickering. Good luck ;-)
UPDATE
Hi I tried to reproduce the conditions and could not make my textbox nor Label flash. The way I tested it was by assigning int ntick = 0; and then increment the ++ntick; inside of the timer_tick method. The results didn't make any of the controls flash and were updated even every milisecond at some point. I also tried string.Format to put some load on the method. Is your app responsive?
The trick is to use double buffering. This way the operating system will redraw the Control off-screen, and only show the control when it is fully redrawn.
I have had the same problem, and solved it by extending the TextBox control like this:
public FastLogBox()
{
InitializeComponent();
_logBoxText = new StringBuilder(150000);
timer1.Interval = 20;
timer1.Tick += timer1_Tick;
timer1.Start();
SetStyle(ControlStyles.DoubleBuffer, true);
}
void timer1_Tick(object sender, EventArgs e)
{
if (_timeToClear)
{
_logBoxText.Clear();
_timeToClear = false;
}
if (_logQueue.Count <= 0) return;
while (!_logQueue.IsEmpty)
{
string element;
if (!_logQueue.TryDequeue(out element)) continue;
{
_logBoxText.Insert(0, element + "\r\n");
}
}
if (_logBoxText.Length > 150000)
{
_logBoxText.Remove(150000, _logBoxText.Length - 150001);
}
Text = _logBoxText.ToString();
}
public new void Clear()
{
_timeToClear = true;
while (!_logQueue.IsEmpty)
{
string element;
_logQueue.TryDequeue(out element);
}
}
public void AddToQueue(string message)
{
_logQueue.Enqueue(message);
}
}
I also use a timer and a concurrentQueue to avoid using Invoke to update the control from another thread. I also use a StringBuilder to prepare the string before putting it into the TextBox. StringBuilder is faster when building larger strings.
You can use ReadExisting() to read the whole data at a time.
You need to handle DataReceived Event of SerialPort
serialPort1.ReadExisting();
Sample:
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
String myData=serialPort1.ReadExisting();
}
Example Code: Here i would like to show you the code to Read Data(RFID Tag Code which is basically of length 12)
String macid = "";
private void DoWork()
{
Invoke(
new SetTextDeleg(machineExe ),
new object[] { macid });
macid = "";
}
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
string str1;
macid += serialPort1.ReadExisting();
if (macid.Length == 12)
{
macid = macid.Substring(0, 10);
Thread t = new Thread(new ThreadStart(DoWork));
t.Start();
}
}
public void machineExe(string text)
{
TextBox1.Text=text;
}
Thank you so much for the answers! I found a way to work around this issue:
Instead of replacing the contents of my textbox by rewriting the TextBox.Text property - which, as HenningNT implied, refreshes the control and causes the flickering - I'm now using the TextBox.AppendText method. Though, as I want to display only one line of data at a time, I use the textbox in multiline mode and the Environment.NewLine to jump to a new line before appending the text. As for the method of updating, I've gone back to using the timer because with the invoke method was crashing my application when I close the form, for some reason. Also, enabling double buffering didn't do me much good, although I guess I was doing it wrong... It still flickers a bit, but it's much better now :) I know this is not really a perfect solution (much more of a workaround), so I'll keep looking for it. If I find it, I'll be sure to update it here ;) My code:
private void timer1_Tick(object sender, EventArgs e) //Timer to update textbox
{
if (tempDisplayBox.Text != globalVar.updateTemp) //Only update if temperature is different
{
try
{
tempDisplayBox.AppendText(Environment.NewLine);
tempDisplayBox.AppendText(globalVar.updateTemp);
}
catch (NullReferenceException)
{
}
}
}
I'm trying to create a custom download app. Its all working except for the download all button that cant pick up the "percent1" variable from the "DownloadProgressChangedEventArgs". I have instantiated it prior to the mainForm constructor but it wont read the changed value.
Here's the code, partially stripped since most of it isnt relevant to the question:
public partial class Main : Form
{
//Variables (not all, just the one im having issues with)
private double percentage1;
//Main form constructor
public Main(){...}
//Download File Async custom method
public void DldFile(string url, string fileName, string localPath, AsyncCompletedEventHandler completedName, DownloadProgressChangedEventHandler progressName)
{
WebClient webClient = new WebClient();
webClient.DownloadFileAsync(new Uri(url), localPath + "\\" + fileName);
webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(completedName);
webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(progressName);
}
//Button 1 click event to start download
private void btnDld1_Click(object sender, EventArgs e)
{
if (url1 != "" && Directory.Exists(localPath1))
{
_startDate1 = DateTime.Now;
DldFile(url1, fileName1, localPath1, completed1, progress1);
}
//took out the try/catch, other ifs to try and cut it down
}
//Download Progress Changed event for Download 1
public void progress1(object sender, DownloadProgressChangedEventArgs e)
{
percentage1 = e.ProgressPercentage; //THIS IS WHERE I WAS EXPECTING TO UPDATE "percentage1"
progressBar1.Value = int.Parse(Math.Truncate(percentage1).ToString());
}
//Button that starts all downloads click event where all my problems are at the moment
private void btnDldAll_Click(object sender, EventArgs e)
{
//The progress bar that should let me know the global status for all webClients
progressBarAll.Value = (
int.Parse(Math.Truncate(percentage1).ToString()) + //HERE IS MY PROBLEM
int.Parse(Math.Truncate(percentage2).ToString()) + //HERE IS MY PROBLEM
int.Parse(Math.Truncate(percentage3).ToString()) + //HERE IS MY PROBLEM
int.Parse(Math.Truncate(percentage4).ToString()) + //HERE IS MY PROBLEM
int.Parse(Math.Truncate(percentage5).ToString())) / 5; //HERE IS MY PROBLEM
//Checks if the link exists and starts it from the download button click event
if (url1 != "")
{
btnDld1.PerformClick();
}
//Continues for url2, 3, 4, 5 and else
}
}
So this is the shortest way i found of letting you know what im trying to pull off, if there's something missing please let me know, i'll try to add any info as fast as possible.
I have tried to instantiate "progress1" to try and acess its percentage1 variable, but it didnt work. I've tried doing the same thing with the webClient but didnt work either. I have used google and stackflow search to no avail. So im not sure if the question is too dumb, or there's a diferent way to look at the issue thats completely out of my mindset.
So main problem is updating the "percentage1" variable and using it.
There are other problems regarding the "progressBarAll.Value" calculation that will be solved when i can get my hands on the right value. So no need to worry about that if you see it.
Try not to think about 'using the event arguments outside the event'. Think about updating the state of your form.
Use properties to simplify the update logic:
public partial class Main : Form
{
private double percentage1;
private double percentage2;
private double percentage3;
private double percentage4;
private double percentage5;
private double Percentage1
{
get
{
return this.percentage1;
}
set
{
this.percentage1 = value;
this.UpdatePercentageAll(); // this will update overall progress whenever the first one changes
progressBar1.Value = GetValueFromPercentage(value);
}
}
private double Percentage2
// same code as for Percentage1
void UpdatePercentageAll()
{
this.PercentageAll = (this.Percentage1 + this.Percentage2 + this.Percentage3 + this.Percentage4 + this.Percentage5) / 5;
}
static int GetValueFromPercentage(double percentage)
{
return (int)Math.Truncate(percentage);
}
double percentageAll;
private double PercentageAll
{
get
{
return this.percentageAll;
}
set
{
this.percentageAll = value;
progressBarAll.Value = GetValueFromPercentage(value);
}
}
//Download File Async custom method
public void DldFile(string url, string fileName, string localPath, AsyncCompletedEventHandler completedName, DownloadProgressChangedEventHandler progressName)
{
WebClient webClient = new WebClient();
webClient.DownloadFileAsync(new Uri(url), localPath + "\\" + fileName);
webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(completedName);
webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(progressName);
}
//Button 1 click event to start download
private void btnDld1_Click(object sender, EventArgs e)
{
if (url1 != "" && Directory.Exists(localPath1))
{
this.StartDownloadFile1();
}
//took out the try/catch, other ifs to try and cut it down
}
void StartDownloadFile1()
{
this.Percentage1 = 0;
_startDate1 = DateTime.Now;
DldFile(url1, fileName1, localPath1, completed1, progress1);
}
//Download Progress Changed event for Download 1
public void progress1(object sender, DownloadProgressChangedEventArgs e)
{
this.Percentage1 = e.ProgressPercentage; // update property, not field
//this will be done in property setters
//progressBar1.Value = int.Parse(Math.Truncate(percentage1).ToString());
}
// then add similar code for other download buttons
//Button that starts all downloads click event where all my problems are at the moment
private void btnDldAll_Click(object sender, EventArgs e)
{
//Checks if the link exists and starts it from the download button click event
if (url1 != "")
{
this.StartDownloadFile1();
}
//Continues for url2, 3, 4, 5 and else
}
}
I would refactor the code even further, but I think it will be easier for you to understand if the code is closer to the original.
The main idea is to create a set of linked properties which work like mathematical functions. When writing the PercentageX properties I'm kind of saying 'let PercentageAll be the average of all percentages'. Then I have each download update it's own progress. Once any progress is updated it updates the average, and I don't have to rememver that inside the progress changed event handler.
And the last point is updating progress bars from percentage properties. It's quite straightforward: once a percentage is changed, I need to update a bar. If so, why bother writing something like
this.Percentage1 = x;
this.progressBar1.Value = (int)Math.Truncate(x);
In this case I have to remember everywhere that once I change the Percentage1 I have to update the bar. And in my example I just create a strict rule for that which is only in one place and works everytime. So I just cannot forget it. And if I need to change the rule, I need to change only one place, so again I cannot make a mistake.
The technique I demonstrate can be expressed as a well-known rule: 'one rule - one place', which means that you should try to have only single place in code that expresses each logical rule that exists in your program. It is a very important idea, I suggest you learn and use it.