How to use event argument outside the event - c#

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.

Related

How to execute a method with a button click

How can I add a function to a button that will run a method I've created. I want to write out an array into a message dialog box with a press of a button, but I don't seem to be getting anywhere so i turned to stackoverflow for some help, since googling didn't really help me with my problem.
static void Tractors(Tractor[] tractors)
{
for (int i = 0; i < tractors.Length; i++)
{
Console.WriteLine((i + 1) + ", " + tractors[i].ToString());
}
}
This is my function that writes out the table of "Tractors".
private void button1_Click(object sender, EventArgs e)
{
}
What should I write into the button1_click method so that it would work?
You need to bind the event handler with the button control and write the logic inside this event handler. If its windows form application you can do like this.
this.button1 = new System.Windows.Forms.Button();
this.button1.Click += new System.EventHandler(this.button1_Click);
private void button1_Click(object sender, EventArgs e)
{
//Call your methods here
}
You would call Tractor() the exactly same way you call Console.WriteLine(). Both are static functions.
However the function is utterly messed up and propably not salvageable. The proper name would be printTractorsToConsole(). As it contains the Console.WriteLine() call, it is strongly tied to Console Applications - avoid tying functions to one Display Technology like that.
You need a more general function that creates and returns a string. You can then send that string to WriteLine(), assign it to Label.Text or wherever else you want the string to be. Strings primarily exist for intput from or output towards the user - and there is too many ways to get it to them.
//Not tested against a compiler, may contain syntax errors
static string TractorArrayToString(Tractor[] tractors){
string output = "";
for (int i = 0; i < tractors.Length; i++)
{
output += (i + 1) + ", " + tractors[i].ToString()) + Environment.NewLine;
}
return output;
}
But even function might be a bad idea, as that function would tie all representations to a single format. Generally you would write that loop directly into the Click Event. But this function looks like it is for printing for debug purposes, so it might work.

can not hit the SerialDataReceivedEvent?

All these comes from the idea that i want to use the SerialPort class in .Net , but the only way is by calling dll . Because i can only get interfaces from the program calling this dll. mycode is below.
i wrote a class about serialport,
public class CommClass
{
public SerialPort _port;
private string _receivedText;
public string receivedText
{
get { return _receivedText; }
set
{
_receivedText = value;
}
}
public CommClass(string _pname)
{
portList = SerialPort.GetPortNames();
_port = new SerialPort(portList[0]);
if (portList.Length < 1)
_port= null;
else
{
if(portList.Contains(_pname.ToUpper()))
{
_port = new SerialPort(_pname);
_port.DataReceived += new SerialDataReceivedEventHandler(com_DataReceived);
}
}
}
private void com_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
string indata = _port.ReadExisting();
receivedText = indata;
}
}
from Bytestoread i can see there r data coming in and i can get data from port.ReadExisting(), but receivedText did not change ,it did not hit the SerialDataReceived event . Is my way wrong?any suggestion?thanks
i created a dll from CommClass ,then i call it in my winform program which has a button and a textbox . Clicking the button , then i initialize the port
public Form1()
{
InitializeComponent();
}
public CommClass mycom;
private void button1_Click(object sender, EventArgs e)
{
mycom = new CommClass("com3");
mycom._port.Open();
textbox.Text=mycom.receivedText;//i add a breakpoint at this line ,
}
when hitting it , i check mycom._port.PortName is "com3", its IsOpen() is "Open" , i use virtual port to send data . i send "1111",then check the mycom._port.BytestoRead is 4, and mycom._port.ReadExisting() is "1111", but mycom.receivedText is null. My puzzle is that i have no idea when the data is coming . How to use the DataReceived event in my winform without code "using System.Io.Ports",just with reference CommClass.dll. Did i make it clear? Thanks for help.
mycom._port.Open();
textbox.Text=mycom.receivedText;//i add a breakpoint at this line ,
That code cannot work, it is a threading race bug. The DataReceived event does not fire instantly after you open the port. It will take a microsecond or so, give or take. A threadpool thread has to get started to fire the event. And of course the device actually has to send something, they usually only do so when you transmit something first.
Which clearly did not happen, your DataReceived event handler has a bug as well. It is not allowed to update the Text property of a control in that event since it runs on a worker thread. Your program will bomb with an InvalidOperationException.
You'll have to write something like this instead:
private void com_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
string indata = _port.ReadExisting();
this.BeginInvoke(new Action(() => {
textbox.AppendText(indata);
}));
}
With the additional stipulation that you must not leave it this way, updating the Text property of a TextBox and making it visible on the screen is an expensive operation that's going to turn your user interface catatonic when the device starts transmitting data at a high rate.

How to display data received from serial port in a textbox without the text disappearing in Visual Studio C#?

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)
{
}
}
}

How to make WebBrowser wait till it loads fully?

I have a C# form with a web browser control on it.
I am trying to visit different websites in a loop.
However, I can not control URL address to load into my form web browser element.
This is the function I am using for navigating through URL addresses:
public String WebNavigateBrowser(String urlString, WebBrowser wb)
{
string data = "";
wb.Navigate(urlString);
while (wb.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
data = wb.DocumentText;
return data;
}
How can I make my loop wait until it fully loads?
My loop is something like this:
foreach (string urlAddresses in urls)
{
WebNavigateBrowser(urlAddresses, webBrowser1);
// I need to add a code to make webbrowser in Form to wait till it loads
}
Add This to your code:
webBrowser1.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(webBrowser1_DocumentCompleted);
Fill in this function
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
//This line is so you only do the event once
if (e.Url != webBrowser1.Url)
return;
//do you actual code
}
After some time of anger of the crappy IE functionality I've came across making something which is the most accurate way to judge page loaded complete.
Never use the WebBrowserDocumentCompletedEventHandler event
use WebBrowserProgressChangedEventHandler with some modifections seen below.
//"ie" is our web browser object
ie.ProgressChanged += new WebBrowserProgressChangedEventHandler(_ie);
private void _ie(object sender, WebBrowserProgressChangedEventArgs e)
{
int max = (int)Math.Max(e.MaximumProgress, e.CurrentProgress);
int min = (int)Math.Min(e.MaximumProgress, e.CurrentProgress);
if (min.Equals(max))
{
//Run your code here when page is actually 100% complete
}
}
Simple genius method of going about this, I found this question googling "How to sleep web browser or put to pause"
According to MSDN (contains sample source) you can use the DocumentCompleted event for that. Additional very helpful information and source that shows how to differentiate between event invocations can be found here.
what you experiencend happened to me . readyStete.complete doesnt work in some cases. here i used bool in document_completed to check state
button1_click(){
//go site1
wb.Navigate("site1.com");
//wait for documentCompleted before continue to execute any further
waitWebBrowserToComplete(wb);
// set some values in html page
wb.Document.GetElementById("input1").SetAttribute("Value", "hello");
// then click submit. (submit does navigation)
wb.Document.GetElementById("formid").InvokeMember("submit");
// then wait for doc complete
waitWebBrowserToComplete(wb);
var processedHtml = wb.Document.GetElementsByTagName("HTML")[0].OuterHtml;
var rawHtml = wb.DocumentText;
}
// helpers
//instead of checking readState . we get state from DocumentCompleted Event via bool value
bool webbrowserDocumentCompleted = false;
public static void waitWebBrowserToComplete(WebBrowser wb)
{
while (!webbrowserDocumentCompleted )
Application.DoEvents();
webbrowserDocumentCompleted = false;
}
form_load(){
wb.DocumentCompleted += (o, e) => {
webbrowserDocumentCompleted = true;
};
}

How can I display multiple images in a loop in a WP7 app?

In my (Silverlight) weather app I am downloading up to 6 seperate weather radar images (each one taken about 20 mins apart) from a web site and what I need to do is display each image for a second then at the end of the loop, pause 2 seconds then start the loop again. (This means the loop of images will play until the user clicks the back or home button which is what I want.)
So, I have a RadarImage class as follows, and each image is getting downloaded (via WebClient) and then loaded into a instance of RadarImage which is then added to a collection (ie: List<RadarImage>)...
//Following code is in my radar.xaml.cs to download the images....
int imagesToDownload = 6;
int imagesDownloaded = 0;
RadarImage rdr = new RadarImage(<image url>); //this happens in a loop of image URLs
rdr.FileCompleteEvent += ImageDownloadedEventHandler;
//This code in a class library.
public class RadarImage
{
public int ImageIndex;
public string ImageURL;
public DateTime ImageTime;
public Boolean Downloaded;
public BitmapImage Bitmap;
private WebClient client;
public delegate void FileCompleteHandler(object sender);
public event FileCompleteHandler FileCompleteEvent;
public RadarImage(int index, string imageURL)
{
this.ImageIndex = index;
this.ImageURL = imageURL;
//...other code here to load in datetime properties etc...
client = new WebClient();
client.OpenReadCompleted += new OpenReadCompletedEventHandler(wc_OpenReadCompleted);
client.OpenReadAsync(new Uri(this.ImageURL, UriKind.Absolute));
}
private void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
if (e.Error == null)
{
StreamResourceInfo sri = new StreamResourceInfo(e.Result as Stream, null);
this.Bitmap = new BitmapImage();
this.Bitmap.SetSource(sri.Stream);
this.Downloaded = true;
FileCompleteEvent(this); //Fire the event to let the app page know to add it to it's List<RadarImage> collection
}
}
}
As you can see, in the class above I have exposed an event handler to let my app page know when each image has downloaded. When they have all downloaded I then run the following code in my xaml page - but only the last image ever shows up and I can't work out why!
private void ImageDownloadedEventHandler(object sender)
{
imagesDownloaded++;
if (imagesDownloaded == imagesToDownload)
{
AllImagesDownloaded = true;
DisplayRadarImages();
}
}
private void DisplayRadarImages()
{
TimerSingleton.Timer.Stop();
foreach (RadarImage img in radarImages)
{
imgRadar.Source = img.Bitmap;
Thread.Sleep(1000);
}
TimerSingleton.Timer.Start(); //Tick poroperty is set to 2000 milliseconds
}
private void SingleTimer_Tick(object sender, EventArgs e)
{
DisplayRadarImages();
}
So you can see that I have a static instance of a timer class which is stopped (if running), then the loop should show each image for a second. When all 6 have been displayed then it pauses, the timer starts and after two seconds DisplayRadarImages() gets called again.
But as I said before, I can only ever get the last image to show for some reason and I can't seem to get this working properly.
I'm fairly new to WP7 development (though not to .Net) so just wondering how best to do this - I was thinking of trying this with a web browser control but surely there must be a more elegant way to loop through a bunch of images!
Sorry this is so long but any help or suggestions would be really appreciated.
Mike
You can use a background thread with either a Timer or Sleep to periodically update your image control.
Phạm Tiểu Giao - Threads in WP7
You'll need to dispatch updates to the UI with
Dispatcher.BeginInvoke( () => { /* your UI code */ } );
Why don't you add the last image twice to radarImages, set the Timer to 1000 and display just one image on each tick?

Categories

Resources