HTML - How do I know when all frames are loaded? - c#

I'm using .NET WebBrowser control.
How do I know when a web page is fully loaded?
I want to know when the browser is not fetching any more data. (The moment when IE writes 'Done' in its status bar...).
Notes:
The DocumentComplete/NavigateComplete events might occur multiple times for a web site containing multiple frames.
The browser ready state doesn't solve the problem either.
I have tried checking the number of frames in the frame collection and then count the number of times I get DocumentComplete event but this doesn't work either.
this.WebBrowser.IsBusy doesn't work either. It is always 'false' when checking it in the Document Complete handler.

Here's how I solved the problem in my application:
private void wbPost_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
if (e.Url != wbPost.Url)
return;
/* Document now loaded */
}

My approach to doing something when page is completely loaded (including frames) is something like this:
using System.Windows.Forms;
protected delegate void Procedure();
private void executeAfterLoadingComplete(Procedure doNext) {
WebBrowserDocumentCompletedEventHandler handler = null;
handler = delegate(object o, WebBrowserDocumentCompletedEventArgs e)
{
ie.DocumentCompleted -= handler;
Timer timer = new Timer();
EventHandler checker = delegate(object o1, EventArgs e1)
{
if (WebBrowserReadyState.Complete == ie.ReadyState)
{
timer.Dispose();
doNext();
}
};
timer.Tick += checker;
timer.Interval = 200;
timer.Start();
};
ie.DocumentCompleted += handler;
}
From my other approaches I learned some "don't"-s:
don't try to bend the spoon ... ;-)
don't try to build elaborate construct using DocumentComplete, Frames, HtmlWindow.Load events. Your solution will be fragile if working at all.
don't use System.Timers.Timer instead of Windows.Forms.Timer, strange errors will begin to occur in strange places if you do, due to timer running on different thread that the rest of your app.
don't use just Timer without DocumentComplete because it may fire before your page even begins to load and will execute your code prematurely.

Here's my tested version. Just make this your DocumentCompleted Event Handler and place the code that you only want be called once into the method OnWebpageReallyLoaded(). Effectively, this approach determines when the page has been stable for 200ms and then does its thing.
// event handler for when a document (or frame) has completed its download
Timer m_pageHasntChangedTimer = null;
private void webBrowser_DocumentCompleted( object sender, WebBrowserDocumentCompletedEventArgs e ) {
// dynamic pages will often be loaded in parts e.g. multiple frames
// need to check the page has remained static for a while before safely saying it is 'loaded'
// use a timer to do this
// destroy the old timer if it exists
if ( m_pageHasntChangedTimer != null ) {
m_pageHasntChangedTimer.Dispose();
}
// create a new timer which calls the 'OnWebpageReallyLoaded' method after 200ms
// if additional frame or content is downloads in the meantime, this timer will be destroyed
// and the process repeated
m_pageHasntChangedTimer = new Timer();
EventHandler checker = delegate( object o1, EventArgs e1 ) {
// only if the page has been stable for 200ms already
// check the official browser state flag, (euphemistically called) 'Ready'
// and call our 'OnWebpageReallyLoaded' method
if ( WebBrowserReadyState.Complete == webBrowser.ReadyState ) {
m_pageHasntChangedTimer.Dispose();
OnWebpageReallyLoaded();
}
};
m_pageHasntChangedTimer.Tick += checker;
m_pageHasntChangedTimer.Interval = 200;
m_pageHasntChangedTimer.Start();
}
OnWebpageReallyLoaded() {
/* place your harvester code here */
}

How about using javascript in each frame to set a flag when the frame is complete, and then have C# look at the flags?

I'm not sure it'll work but try to add a JavaScript "onload" event on your frameset like that :
function everythingIsLoaded() { alert("everything is loaded"); }
var frameset = document.getElementById("idOfYourFrameset");
if (frameset.addEventListener)
frameset.addEventListener('load',everythingIsLoaded,false);
else
frameset.attachEvent('onload',everythingIsLoaded);

Can you use jQuery? Then you could easily bind frame ready events on the target frames. See this answer for directions. This blog post also has a discussion about it. Finally there is a plug-in that you could use.
The idea is that you count the number of frames in the web page using:
$("iframe").size()
and then you count how many times the iframe ready event has been fired.

You will get a BeforeNavigate and DocumentComplete event for the outer web page, as well as each frame. You know you're done when you get the DocumentComplete event for the outer webpage. You should be able to use the managed equivilent of IWebBrowser2::TopLevelContainer() to determine this.
Beware, however, the website itself can trigger more frame navigations anytime it wants, so you never know if a page is truly done forever. The best you can do is keep a count of all the BeforeNavigates you see and decrement the count when you get a DocumentComplete.
Edit: Here's the managed docs: TopLevelContainer.

Here's what finally worked for me:
public bool WebPageLoaded
{
get
{
if (this.WebBrowser.ReadyState != System.Windows.Forms.WebBrowserReadyState.Complete)
return false;
if (this.HtmlDomDocument == null)
return false;
// iterate over all the Html elements. Find all frame elements and check their ready state
foreach (IHTMLDOMNode node in this.HtmlDomDocument.all)
{
IHTMLFrameBase2 frame = node as IHTMLFrameBase2;
if (frame != null)
{
if (!frame.readyState.Equals("complete", StringComparison.OrdinalIgnoreCase))
return false;
}
}
Debug.Print(this.Name + " - I think it's loaded");
return true;
}
}
On each document complete event I run over all the html element and check all frames available (I know it can be optimized). For each frame I check its ready state.
It's pretty reliable but just like jeffamaphone said I have already seen sites that triggered some internal refreshes.
But the above code satisfies my needs.
Edit: every frame can contain frames within it so I think this code should be updated to recursively check the state of every frame.

I just use the webBrowser.StatusText method. When it says "Done" everything is loaded!
Or am I missing something?

Checking for IE.readyState = READYSTATE_COMPLETE should work, but if that's not proving reliable for you and you literally want to know "the moment when IE writes 'Done' in its status bar", then you can do a loop until IE.StatusText contains "Done".

Have you tried WebBrowser.IsBusy property?

I don't have an alternative for you, but I wonder if the IsBusy property being true during the Document Complete handler is because the handler is still running and therefore the WebBrowser control is technically still 'busy'.
The simplest solution would be to have a loop that executes every 100 ms or so until the IsBusy flag is reset (with a max execution time in case of errors). That of course assumes that IsBusy will not be set to false at any point during page loading.
If the Document Complete handler executes on another thread, you could use a lock to send your main thread to sleep and wake it up from the Document Complete thread. Then check the IsBusy flag, re-locking the main thread is its still true.

Related

Timer won't tick until form closed

There's some strange mistake with timer and forms.
I am making editor for game. Editor has two forms - MainForm and PreviewForm. PreviewForm contains only control for OpenGL output (Custom control based on GLControl from OpenTK), named glSurface.
glSurface has two inline timers (Windows.Forms.Timer) - one for rendering, and one for updating game state. Timers fires in glSurface method Run(double updateRate, double frameRate).
So, I want to show PreviewForm and run updating and rendering from MainForm.
My code is:
PreviewForm = new PreviewForm();
PreviewForm.glSurface.Run(60d, 60d);
PreviewForm.Show(this); //Form is "modal"
Body of Run method:
if (Running)
throw new Exception("Already run");
_updateRate = updateRate;
_renderRate = frameRate;
var renderFrames = Convert.ToInt32(1000/frameRate);
var updateFrames = Convert.ToInt32(1000/updateRate);
RenderTimer.Interval = renderFrames;
UpdateTimer.Interval = updateFrames;
RenderTimer.Start();
UpdateTimer.Start();
Running = true;
Timers is being initialized in OnVisibleChanged event:
protected override void OnVisibleChanged(EventArgs e)
{
...
RenderTimer = new Timer();
UpdateTimer = new Timer();
RenderTimer.Tick += RenderTick;
UpdateTimer.Tick += UpdateTick;
...
}
Weird things start here.
When PreviewForm is showing, nothing happens. BUT when I close that form, both timers fire their events! I have tested for possible cross-thread interaction, but PreviewForm.InvokeRequired and glSurface.InvokeRequired are both false.
Please help me find out what the hell is going on.
In this case declare and initialise and start your timers all within the one code block:
{
.../...
RenderTimer = new Timer();
UpdateTimer = new Timer();
RenderTimer.Tick += RenderTick;
UpdateTimer.Tick += UpdateTick;
var renderFrames = Convert.ToInt32(1000/frameRate);
var updateFrames = Convert.ToInt32(1000/updateRate);
RenderTimer.Interval = renderFrames;
UpdateTimer.Interval = updateFrames;
RenderTimer.Start();
UpdateTimer.Start();
.../...
}
Without seeing the program flow this is the safest option. It appears the variables are local to the OnVisibleChanged event, so I'm not sure how you're not getting a null refernce exception when you're calling them from your if (Running).
The other thing you could do is make them class variables and ensure they are initialised before you use them. Then call start within the if statement.
As for the issue of them starting when the form closes, it's impossible to determine from the code you've shown.
Edit: There's a deeper problem.
You really shouldn't be using system timers to drive your game updates and rendering.
System timers on most platforms have low accuracy that's inadequate for high performance multimedia such as audio and most games. On Windows System.Windows.Forms.Timer uses Win32 timers which have particularly low accuracy, typically resulting in intervals at least 15ms off(see this answer). See this technical breakdown and this overview for more information. Basically, even if your code worked correctly your frames would stutter.
Most games "tick" by running an infinite loop in the main thread, doing the following each time(not necessarily in this order):
Call back into the framework to handle pending OS events.
Track the time difference since the last "tick" so frame-independent timing works.
Update the state of the game(such as physics and game logic, possibly in another thread).
Render the scene based on a previous update(possibly in another thread).
As noted by commenters, the main problem in your timer code is that initialization is split between Run and OnVisibleChanged. I was unable to reproduce the case where the timer fires after a sub form is closed. I suspect some other code you haven't posted is the cause. You'll save yourself a great deal of trouble if you use OpenTK.GameWindow. It handles the loop plumping for you, similar to XNA. This is an example of one way to integrate it with WinForms. See the manual for more information.
In Run, you set Interval and start each timer. No Tick callbacks are set. In OnVisibleChanged, you recreate the timers and assign Tick callbacks. No intervals are set, and the timer's haven't been started.
The timer initialization code in Run is essentially ignored because no tick callbacks are set and OnVisibleChanged recreates the timers. OnVisibleChanged triggers almost immediately after Run, shortly after you call PreviewForm.Show(this).
If you're dead set on using system timers, this should work:
// somewhere before Run(ideally in the initialization of the main form).
RenderTimer.Interval = Convert.ToInt32(1000 / frameRate);
RenderTimer.Tick += RenderTick;
UpdateTimer.Interval = Convert.ToInt32(1000 / updateRate);
UpdateTimer.Tick += UpdateTick;
void Run(double frameRate, double updateRate)
{
// ...
RenderTimer.Start();
UpdateTimer.Start();
// ...
Running = true;
}
// ...
protected override void OnVisibleChanged(EventArgs e)
{
// ...
// Don't initialize timers here.
// ...
}

Repeat Button Tapping

I have a control that allows the user to perform some heavy duty image processing on a specific part of an image and they have arrow buttons to move this area around the image.
as the process is very heavy duty (avg 800ms per run) I have used a repeat button which turns this are into a "Ghost" and only executes the process upon the mouse up event.
This works really well and solves most performance issues relating to this function
HOWEVER
A certain group of users are refusing to learn this method of holding and releasing and persist in tapping the button to move it rather than holding and releasing.
This means that the heavy duty method is being called every time the they tap and as it only moves a small increment each time the method fires, so they end up with a application hang whilst it tries to do > 100 of these 800ms + processes
MY QUESTION
How can I handle this tapping behaviour in the same way as holding and releasing?
I thought about a timer but cant work out how I would detect the difference between a normal tap and the last tap.
Quick, dirty solution: use a Timer.
Each time the user taps the button, stop the timer, increase the number of taps, start the timer. If the timer elapses before the user taps again, then it should do your big work method.
Is this prone to threading issues? Probably. I'm not a threading expert. I would love if a threading expert can come comment on it.
Is this the best solution? Hardly. But it will get you by for a while (until the threading issues come up).
private int _totalTaps = 0;
private const int _tapSequenceThreshold = 250; // Milliseconds
private Timer _tapTimer = new Timer(_tapSequenceThreshold);
private void InitializeTimer()
{
_tapTimer.Elapsed += OnTapTimerElapsed;
}
private void OnTapTimerElapsed(object source, System.Timers.ElapsedEventArgs e)
{
_tapTimer.Stop();
// The `DoBigLogic` method should take the number of taps and
// then do *something* based on that number, calculate how far
// to move it, for example.
DoBigLogic(_totalTaps);
_totalTaps = 0;
}
// Call this each time the user taps the button
private void Tap()
{
_tapTimer.Stop();
_totalTaps++;
_tapTimer.Start();
}
Best solution: this method plus moving this work off the GUI thread. Then you don't have to worry about taps or click-and-hold, you won't block the GUI thread.
If you have to do work that doesn't update the UI (redraw the image, for example) then send the image to the UI, you can make a new thread, then you'll hit an error about 'accessing a UI element from a non-UI thread', just drop the UI code in a Marshal for it.
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
Windows.UI.Core.CoreDispatcherPriority.Normal,
() => { UpdateDisplay(); }
);
Consider monitoring mouse activity and start your heavy duty process after a short period of inactivity.
Consider running the process on a separate thread - this might mean cloning (part of) the image in memory.
Consider preventing the process from being ran multiple times concurrently (if that is possible ie. the process is async).
Consider one of these options:
Disable the button until the process completes, causing that 800ms delay. Users will soon learn to use the hold down method. This would involve the smallest amount of code and put the onus on humans. It also ensures you are not holding up the app with clicks in the buffer or over-using resources.
Put a timer in your button click event:
'Ghost area'
Timer Start ( or reset to zero)
Then the code to call your main work is in the timer elapsed event which will be set to whatever pause you wish. (ie If a user has not clicked again within a second or so)
Then stop the timer
Execute code
You could try using reactive extensions which is available on nuget.
using System;
using System.Windows.Controls;
using System.Windows.Input;
using System.Linq;
using System.Reactive.Linq;
namespace SmoothOutButtonTapping
{
public static class Filters
{
// First we need to combine the pressed events and the released
// events into a single unfiltered stream.
private static IObservable<MouseButtonState> GetUnfilteredStream(Button button)
{
var pressedUnfiltered = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
x => button.PreviewMouseLeftButtonDown += x,
x => button.PreviewMouseLeftButtonDown -= x);
var releasedUnfiltered = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
x => button.PreviewMouseLeftButtonUp += x,
x => button.PreviewMouseLeftButtonUp -= x);
return pressedUnfiltered
.Merge(releasedUnfiltered)
.Select(x => x.EventArgs.ButtonState);
}
// Now we need to apply some filters to the stream of events.
public static IObservable<MouseButtonState> FilterMouseStream(
Button button, TimeSpan slidingTimeoutWindow)
{
var unfiltered = GetUnfilteredStream(button);
// Ironically, we have to separate the pressed and released events, even
// though we just combined them.
// This is because we need to apply a filter to throttle the released events,
// but we don't need to apply any filters to the pressed events.
var released = unfiltered
// Here we throttle the events so that we don't get a released event
// unless the button has been released for a bit.
.Throttle(slidingTimeoutWindow)
.Where(x => x == MouseButtonState.Released);
var pressed = unfiltered
.Where(x => x == MouseButtonState.Pressed);
// Now we combine the throttled stream of released events with the unmodified
// stream of pressed events.
return released.Merge(pressed);
}
}
}
Now we have a stream that will respond immediately whenever a user presses, but will not fire a released event unless the button is released for long enough.
Here is an example of how you could consume the above method. This example simply changes the color of the control while the button is in the Pressed state, but you could easily do whatever you wanted.
using System;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Reactive.Linq;
using System.Threading;
namespace SmoothOutButtonTapping
{
public partial class SmoothTapButtonControl : UserControl
{
public SmoothTapButtonControl()
{
InitializeComponent();
_pressed = new SolidColorBrush(Colors.Lime);
_released = Background;
// Don't forget to call ObserveOn() to ensure your UI controls
// only get accessed from the UI thread.
Filters.FilterMouseStream(button, SlidingTimeoutWindow)
.ObserveOn(SynchronizationContext.Current)
.Subscribe(HandleClicked);
}
// This property indicates how long the button must wait before
// responding to being released.
// If the button is pressed again before this timeout window
// expires, it resets.
// This is handled for us automatically by Reactive Extensions.
public TimeSpan SlidingTimeoutWindow { get; set; } = TimeSpan.FromSeconds(.4);
private void HandleClicked(MouseButtonState state)
{
if (state == MouseButtonState.Pressed)
Background = _pressed;
else
Background = _released;
}
private Brush _pressed;
private Brush _released;
}
}
You can find the complete version of the above examples (project files and xaml included) on my github.
You can prevent program from executing your 800 ms function by setting simple flags(Preferably bool type).
You need to create three events. One Mouse Down, one Mouse Up event and other is Mouse Move event of the button. Set your flag false at declaration. When you click the button, make your flag true in Mouse Down event. And when you lift your mouse click i.e., Mouse Up event make your flag false.
Simple illustration of code.
bool click = false,run_process = true;
mouseDown_event()
{
click = true;
}
mouseUp_event()
{
click = false;
}
mouseMove_event()
{
if(click == true && run_process == true)
{
click = false;
run_process = false;
//call your function
}
}
How can I handle this tapping behaviour in the same way as holding and releasing?
Create a formula which calculates a weight value based last tap frequency, current operation and time between last actual operation; with any other factors I may not be aware of. With the right formula it should be representational to the person who uses the system correctly verses someone who sends multiple clicks.
The weighted value should be passed to an alternate thread which is handling the actual operation to the screen and can handle a blizzard of taps or a single tap without missing a beat, per se.

Terminating a Form while COM DataReceived Event keeps fireing. C#

I'm currently having a problem, which seems to be related to closing a Form, while a scale, which is connected through a Serial Connection keeps sending data (about 3 packages per sek).
I handle new data over the DataReceived-Event (handling itself might be uninteresting for this issue, since I'm just matching data) Keep an eye on the COM_InUse variable and the allowFireDataReceived check.):
private void COMScale_DataReceived(object sender, EventArgs e)
{
if (allowFireDataReceived)
{
//set atomar state
COM_InUse = true;
//new scale:
if (Properties.Settings.Default.ScaleId == 1)
{
strLine = COMScale.ReadTo(((char)0x2).ToString());
//new scale:
Regex reg = new Regex(Constants.regexScale2);
Match m = reg.Match(strLine);
if (m.Success)
{
strGewicht = m.Groups[1].Value + m.Groups[2];
double dblComWeight;
double.TryParse(strGewicht, out dblComWeight);
dblScaleActiveWeight = dblComWeight / 10000;
//add comma separator and remove zeros
strGewicht = strGewicht.Substring(0, 1) + strGewicht.Substring(1, 2).TrimStart('0') + strGewicht.Substring(3);
strGewicht = strGewicht.Insert(strGewicht.Length - 4, ",");
//write to textbox
ThreadSafeSetActiveScaleText(strGewicht);
COMScale.DiscardInBuffer();
//MessageBox.Show(dblScaleActiveWeight.ToString(), "dblScaleActiveWeight");
}
}
//free atomar state
COM_InUse = false;
}
}
The COM_InUse variable is a global bool and "tells" if there is a current process of handling data.
The allowFireDataReceived is also a global bool and if set to false will lead to no extra handling of the data which has been sended.
My problem now is the following:
It seems that Eventhandling is a separate Thread, which leads to a deadlock on klicking the Cancel-Button since the COM_InUse will never turn to false, even if the Event was handled (see end of COMScale_DataReceived, where COM_InUse is set to false).
While setting allowFireDataReceived = false works perfectly (no handling any more), as I said: the while loop will not terminate.
private void bScaleCancel_Click(object sender, EventArgs e)
{
allowFireDataReceived = false;
while (COM_InUse)
{
;
}
if (!COM_InUse)
{
ret = 1;
SaveClose();
}
}
When I comment out the while-block I have to click twice on the button, but it works without a crash. Since this very user unfriendly, I'm searching for an alternative way to safely close the window.
Info:
Simply closing (without checking if the COM-Data was processed) lead to a fatal crash.
So, maybe someone can explain to me what exactly causes this problem or can provide a solution to this. (Maybe one would be to trigger the Cancel-Clicking Event again, but that is very ugly)
Greetings!
I count on you :)
//edit:
Here is the current code of
private void ThreadSafeSetActiveScaleText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.lScaleActive.InvokeRequired)
{
SafeActiveScaleTextCallback d = new SafeActiveScaleTextCallback(ThreadSafeSetActiveScaleText);
this.Invoke(d, new object[] { text });
}
else
{
this.lScaleActive.Text = text;
}
}
ThreadSafeSetActiveScaleText(strGewicht);
Yes, the DataReceived event runs on a threadpool thread. You already knew that, you wouldn't have called it "ThreadSafe" otherwise. What we can't see is what is inside this method. But given the outcome, it is highly likely that you are using Control.Invoke().
Which is going to cause deadlock when you loop on COM_InUse in code that runs on the UI thread. The Control.Invoke() method can only complete when the UI thread has executed the delegate target method. But the UI thread can only do that when it is idle, pumping the message loop and waiting for Windows messages. And invoke requests. It cannot do this while it looping inside the Click event handler. So Invoke() cannot complete. Which leaves the COM_InUse variable for ever set to true. Which leaves the Click event handler forever looping. Deadlock city.
The exact same problem occurs when you call the SerialPort.Close() method, the port can only be closed when all events have been processed.
You will need to fix this by using Control.BeginInvoke() instead. Make sure the data is still valid by the time the delegate target starts executing. Pass it as an argument for example, copying if necessary.
Closing the form while the scale is unrelentingly sending data is in general a problem. You'll get an exception when you invoke on a disposed form. To fix this, you'll need to implement the FormClosing event handler and set e.Cancel to true. And unsubscribe the DataReceived event and start a timer. Make the Interval a couple of seconds. When the timer Ticks, you can close the form again, now being sure that all data was drained and no more invokes can occur.
Also note that calling DiscardInBuffer() is only good to randomly lose data.

C# Step by Step Execution

I'm building an app that uses and scanner API and a image to other format converter. I have a method (actually a click event) that do this:
private void ButtonScanAndParse_Click(object sender, EventArgs e)
{
short scan_result = scanner_api.Scan();
if (scan_result == 1)
parse_api.Parse(); // This will check for a saved image the scanner_api stores on disk, and then convert it.
}
The problem is that the if condition (scan_result == 1) is evaluated inmediatly, so it just don't work.
How can I force the CLR to wait until the API return the convenient result.
NOTE
Just by doing something like:
private void ButtonScanAndParse_Click(object sender, EventArgs e)
{
short scan_result = scanner_api.Scan();
MessageBox.Show("Result = " + scan_result);
if (scan_result == 1)
parse_api.Parse(); // This will check for a saved image the scanner_api stores on disk, and then convert it.
}
It works and display the results.
Is there a way to do this, how?
Thank you very much!
UPDATE:
Theres an event on the scanner API:
Public Event EndScan() // Occurs when the scanned the image.
But I don't know how to use it. Any Idea?
That really depends on how the API works. If scanner_api.Scan() is blocking, then it will sit at that line waiting for a result. Once it gets the result, the if will evaluate. This can cause your UI to become unresponsive, so you often have to implement some sort of threading to do it in the background. I'm guessing from your question that isn't the way this API works.
Another way this could work is with polling. You check every so often to see what the result is. You don't want to check constantly and use up all your resources (such as CPU), so you check at an interval. Sheldon's answer with a Timer achieves this.
At least one more way this may work is with a callback. You send the API a callback function to call when the status has updated. This can be implemented as events (delegate) you tie into or a regular delegate you pass as a parameter. You'll often see these implemented as "OnStatusChanged", "OnCompleted", etc.
Basically, it's down to what the API supports. Polling usually works, the others have to be supported. Check your API documentation for examples if possible.
You can use a timer (see MSDN: Timer class) that periodically checks whether the scan already completed or not.
You can alternatively use an asynchronous call that calls back when the scanning process is finished. Note that this is the more complicated way.
One way would be with a timer. Set the timer to check every few seconds to check the value in scan_result (which would need to be promoted to a class-level variable for this to work).
So, something like:
public class Scanning
{
private System.Timers.Timer aTimer;
short scan_result;
public Scanning()
{
aTimer = new System.Timers.Timer(1000);
// Hook up the Elapsed event for the timer.
aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
}
private void ButtonScanAndParse_Click(object sender, EventArgs e)
{
aTimer.Enabled = true;
scan_result = scanner_api.Scan();
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
if (scan_result == 1)
{
aTimer.Enabled = false;
parse_api.Parse(); // This will check for a saved image the scanner_api stores on disk, and then convert it.
}
}
}
(This is untested, of course. YMMV.)

C# timer issue

Hello I'm currently having an issue with a timer in a program I'm developing. The timer runs and calls methods which retrieve Windows Management Information from remote PC's after a set period of time and repeat this.
The first time the timer calls these all is well, however the second time, after the timer has completed its task, it loops through itself again and the third time it runs it does it 3 times etc. The for loop in the code below works fine its the timer itself.
So any help would be appareciated and if you require any further details please let me know.
Below is my code:
private void tmrStore_Tick(object sender, EventArgs e)
{
string ipAdd;
ipAdd = "127.0.0.1";
List<TblServer> Server;
WMIInfo localDB = new WMIInfo("Data Source=|DataDirectory|\\WMIInfo.sdf");
Server = localDB.TblServer.ToList();
if (Server.Count == 0)
{
}
else
{
for (int counter = 0; counter < Server.Count; counter++)
{
CPUStore cpu = new CPUStore();
cpu.Store(Server[counter].IpAdd);
HDDStore hdd = new HDDStore();
hdd.Store(Server[counter].IpAdd);
MemStore mem = new MemStore();
mem.Store(Server[counter].IpAdd);
//delete items over 24 hours old
}
}
Try disabling the timer before performing the management task, then reenabling:
tmrStore.Enabled = false;
try{
// do stuff
}finally{
tmrStore.Enabled = true;
}
The cause of the problem is probably that the body of your timer handler takes longer to execute than your Timer.Ticks value, so your timer events start to stack on top of each other.
You might also consider putting this code in a thread instead of a timer, so that it's independent of your user interface.
My first guess is that you are setting your Timer.Tick event in a place that is being executed multiple times. I would try searching for "tmrStore.Tick +=" to see where all the methods are being added to the event.
Right I've resolved the issue its because I had a class I was using to write the retrieved information into text boxes and within that I called a new instance of the form to gain access to the text boxes doh!
Thanks for your help though guys no doubt I'll be back soon for some more lol

Categories

Resources