What if your event handler has to be nonstatic?
ASP.NET Progress
Collapse
X
-
Navyjax2, could you please provide more information about the problem you are facing?
There is the INotifyProperty Changed.Propert yChanged Event. When this event is raised it indicates that the property has been changed.
-Frinny -
Bare with me, I'm fairly new to delegates, interfaces, and events, even though I've been doing C# for almost 2 yrs and VB/VB .NET since VB6.
Basically I wanted to do an event whenever a variable's value changed. A public variable value (essentially a counter) is increased as a DataTable is parsed through a foreach loop. The variable's value changing would trigger an event that would change the innerHTML of a span tag on the page (which has to happen within a nonstatic function), to give an indication of what row we were on out of the total - creating a progress counter.
Something like:
Code:private long _rowCount = CSV.rowCount; public delegate void PrivateVariableChanged(object sender, EventArgs e); public event PrivateVariableChanged SelectedChanged { add { fileLinkArea.InnerHtml = "Processed " + CSV.rowCount + " of " + CSV.rowTotal; } remove { } }; private void NotifyPrivateVariableChanged(String info) { if (SelectedChanged != null) { SelectedChanged(this, new PrivateVariableChangedEventArgs(info)); } } public long RowCount { get { return this._rowCount; } set { if (value != this._rowCount) { this._rowCount = value; NotifyPrivateVariableChanged("RowCount"); } } }
Thanks,
TomComment
-
I think that the easiest way to handle this is to make the public variable private and provide a public Property instead. That way your property could either raise an event that notifies all other classes of the change...or it could change the innerHTML (not sure why you're doing this but ok) of the element.
It's nice when encapsulation works for you :)
-FrinnyComment
-
So what changed property would it be firing on? And is that the right syntax for the event?
I was trying this another way, too - as the foreach loop processes and increments the counter I have, couldn't I just set the innerHTML updating to occur there? But the thing is, _Default defaultClass = new _Default() won't instantiate the _Default class where my page is, so that it could run a function there that would take care of the innerHTML updating. Is there a way around this? If so, I might not even need events at all...
Thanks,
TomComment
-
What is really confusing me is the that you're setting the innerHTML.
First of all this isn't typically done in ASP.NET any more. Instead the Text property is usually used (for example: myLabel.Text = "1 of 5")
The other thing that is confusing me is that your C# code is executed on the server...this means that the user will not see the change to the innerHTML (or even the Text property) until the response to the request is sent to the browser....in which case, why don't you just do your loop and when you're finished, set the innerHTML (or Text property) of the control?
-FrinnyComment
-
Well, what I was doing was setting a span on the page equal to this text. I wanted the span to be able to show the progress to a user ("Row 1 of 14298 processed", etc.) as the DataTable is being looped through. I found that, even if I put the code into the _Default class (to prevent instantiation issues) and had it try and update the field "updateText " as the counter increased, it doesn't show on the page, at least while debugging it and letting it run a couple of loops - probably for the very reason you are saying - me not waiting til the end probably prevented it from displaying, but I want it to do it as it's going, not wait til the end.
So I thought about how you might be able to do this with JavaScript, since I know that JavaScript can do this, but adding an attribute to the button click that initiates this whole thing in Page_Load():
Code:btnDBExport.Attributes.Add("onclick", "sendIt(" + data + ")");
Code:function sendIt(data) { document.getElementById('updateText').innerHTML = data; }
Code:data = "Row " + rowCount + " of " + dt.Rows.Count + " processed.";
-TomLast edited by Frinavale; Dec 21 '09, 02:11 PM. Reason: Please post code in [code] ... [/code] tags. Added code tags.Comment
-
This is actually a little more complicated than you would think.
I'm sure that there may be another way to do this but this is the first thing that comes to my mind....
When you click the button that starts the processing, also start a Ajax Timer control that posts back to the server after a given amount of time has passed.
Have the loop set a Session variable so that when the Ajax Timer posts back to the server the method that handles the tick event can retrieve the value and display it in the web page.
I suggest using an ASP.Net Label control instead of your typical HTML <span> because this control renders as a <span> and lets you manipulate the Text and style aspects (along with a bunch of other properties) in your VB.NET/C# server code.
Place the Label that is going to display the text inside an UpdatePanel so that only that section of the page is updated....like wise place the GridView and the Button in an UpdatePanel so only that section of the page is being processed when you click the button.
-FrinnyComment
-
I've moved your question into a thread of it's own because this question is straying off topic from the original question.
New questions should not be posted onto the end of old questions.
This thread has been split off of:
-FrinnyComment
-
This sounds like it would work but I'm trying to add the Timer control in code behind after the button is clicked and it isn't really working
Code:protected void btnRunUpdateNow_Click(object sender, EventArgs e) { Timer timerControl = new Timer(); timerControl.Tick += new EventHandler<EventArgs>(timerControl_Tick); timerControl.Interval = 3000; ....... }
Comment
-
Instead of adding the event handler in this code try moving it to your declaration.
For example:
Code:<asp:Timer ID="timerControl" runat="server" OnTick="timerControl_Tick" Interval="3000" ></asp:Timer>
The thing is that I was a little mistaken earlier....you can't really enable the timer in your button click event because you need to send the request back to the client before you start the looping. You're probably going to have to send 2 requests: the first one starts the timer, the second starts the looping.
Or you need to figure out a way to send the request back to the client while the server continues to loop.
-FrinnyComment
-
Timer
Thanks for the reply. I actually tried setting up the Timer control this way (disabling it and enabling it on button click) Didn't really work. I was trying to do something like this.
Code:<asp:Timer ID="Timer1" runat="server" Interval="3000" Enabled="false" OnTick="Timer1_Tick"> </asp:Timer> <asp:UpdatePanel ID="upProgress" runat="server" > <ContentTemplate> <div> <asp:Label ID="Progress" runat="server" Text="Sample"></asp:Label> </div> </ContentTemplate> </asp:UpdatePanel>
Code:protected void Timer1_Tick(object sender, EventArgs e) { Session["Progress"] = "Started processing..."; Progress.Text = (string)Session["Progress"]; }
Code:protected void btnRunUpdateNow_Click(object sender, EventArgs e) { Timer1.Enabled = true; myClass.RunUpdate(); }
Inside myClass housed in an assembly, I was trying to update progress using the Session variable.
Code:public void RunUpdate() { ..... HttpContext.Current.Session["Progress"] = _Orders.Count.ToString(); }
Comment
-
I just tried to implement what I was suggesting and it doesn't work.
This comes back to a problem that I got stuck on about a year ago (or so) that I had forgotten about...
Apparently (I'm not sure if this is a Cassini thing or not) the server code will not process more than one request from the same webpage at the same time.
For example:- Button posts back to server and a long process is started
- TimerControl posts back to server
The TimerControl postback won't be processed until the button click event is finished processing. This defeats the whole purpose of Asynchronous. I was very annoyed when I discovered this and never did find out why this was happening, nor did I find out how to get around this.
-FrinnyComment
-
I think I'm getting closer to finding an answer.
Apparently you have to specify your page is Async in the @Page directive. Then you have to implement Begin and End methods for the things are going to take a long time so that they are executed asynchronously. ..
I'm reading Wicked Code:Asynchrono us Pages in ASP.NET 2.0 in an attempt to solve this problem.
-FrinnyComment
-
Upon further reading of the article I discovered the asynchronous page was simply for processing more than one thing in different threads before the response is sent to the browser. It was an interesting read but doesn't help with the problem.
So, I have something that sort of works....it works the very first time the page is loaded but after that it doesn't.
This is what I have in my ASPX page:
Code:<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestingProgress.aspx.cs" Inherits="WebApplication1.TestingProgress" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Testing</title> </head> <body> <form id="form1" runat="server"> <asp:ScriptManager ID="sm" runat="server"></asp:ScriptManager> <div> <asp:UpdatePanel ID="upnl" runat="server"> <ContentTemplate> <asp:Timer ID="timerControl" runat="server" Enabled="false" Interval="500" OnTick="timerControl_Tick"></asp:Timer> <asp:LinkButton ID="startTimer" runat="server" OnClick="startTimer_Click" Text="Start Timer" style="display:none;"></asp:LinkButton> <asp:Label ID="processingMessage" runat="server"></asp:Label> <asp:Button runat="server" ID="btnLongProcess" Text="start processing" OnClientClick="return startTimerBeforePosting();" OnClick="btnLongProcess_Click" /> <script type="text/javascript"> function startTimerBeforePosting() { setTimeout(waitBeforeStartingTimer, 1000); return true; } function waitBeforeStartingTimer() { __doPostBack('<%=startTimer.ClientID %>', ''); } </script> </ContentTemplate> </asp:UpdatePanel> </div> </form> </body> </html>
Code:namespace WebApplication1 { public partial class TestingProgress: System.Web.UI.Page { private static int loopIteration = 0; private int max = 10; protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { loopIteration = 0; } } protected void timerControl_Tick(object sender, EventArgs e) { /*int iteration = 0; if (Session["loopIteration"] != null) { iteration = (int)Session["loopIteration"]; } processingMessage.Text = iteration.ToString() +"/"+ max.ToString(); if (loopIteration >= max-1) { timerControl.Enabled = false; processingMessage.Text = "Done. Timer stopped in tick."; }*/ processingMessage.Text = loopIteration.ToString() + "/" + max.ToString(); if (loopIteration >= max) { timerControl.Enabled = false; processingMessage.Text += " Done. Timer stopped in tick."; } } protected void startTimer_Click(object sender, EventArgs e) { timerControl.Enabled = true; processingMessage.Text = "Timer started..."; } protected void btnLongProcess_Click(object sender, EventArgs e) { Session["loopIteration"] = 0; loopIteration = 0; for (loopIteration = 0; loopIteration < max; loopIteration++) { //Session["loopIteration"] = loopIteration; System.Threading.Thread.Sleep(1000); } timerControl.Enabled = false; processingMessage.Text = "Done. Timer stopped in button click."; } } }
The JavaScript in the ASPX page calls a function that preforms a postback for a hidden LinkButton on the page a second after the button is clicked. The code that handles the LinkButton click event enables the timer. So the Timer is started after button click the process begins.
The button click process sets a static integer to 0 and then increments the static integer by one in a loop that puts the main thread to sleep for a second each loop.
In the timer tick event, the static integer is displayed on the page along indicating how much more it has to go before the process is done.
This works the first time the page is ever loaded.
Once the button has been clicked and the processing stuff has been displayed it stops working.
What do I mean by that?
Well, if you click the button again the JavaScript waits for one second and fires the postback for the hidden LinkButton...th e thing is that the click event for the link button is not fired until after the button click event is complete. This means that the timer is started after the looping process is done which is not really useful.
I don't know why this is the case...
Aside from that, another thing that I found is that you can't use Session in this solution. I never realized how slow accessing Session was until this experiment. I tried locking session every time I accessed it thinking that the timer tick event implementation couldn't access Session since the button click event was using it...but this didn't help. You can see the Session attempt commented out in my above posted code.
And, I don't think that using a static variable is going to really be feasible in your real-world application, but Session wasn't working...so I tried something else....which "kind of" worked.
I'm not sure what to suggest...
-FrinnyComment
Comment