I'm trying to build a button that displays a loading circle and loads some data in a C# application.
I have a boolean value called Loading that tracks the state of the current data that is being loaded.
I have a delegate called LoadHandler that returns a boolean value based on whether the loading is complete or not, I also have an event that can be subscribed to in a separate class that takes the LoadHandler signature and handles the actual loading.
I have a private Load() function that gets called when an event handler for input is called in the base class, this is called on a new Thread.
Here's the code for the LoadEntry class (Keep in mind this is just a button that displays a loading circle when hit, and delegates loading to a separate class)
public class LoadingEntry : ButtonEntry
{
private readonly object objLock = new object();
/// <summary>
/// The loading circle texutre
/// </summary>
Texture2D loadingReticle;
/// <summary>
/// Tracks the rotation of the loading reticle.
/// </summary>
float loadingRotation;
/// <summary>
/// Tells whether we are loading or not
/// </summary>
public bool Loading { get; private set; }
public LoadingEntry(string buttonText)
: base(buttonText)
{
}
/// <summary>
/// Handler that will return true when loading information is complete
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <returns>True if loading is complete</returns>
public delegate bool LoadHandler(object sender, EventArgs e);
/// <summary>
/// The LoadHandler event that should be subscribed to in a menu class
/// </summary>
public event LoadHandler onLoad;
private void Load()
{
if (onLoad != null)
{
lock (objLock)
{
Loading = true;
while (true)
{
Loading = onLoad(this, EventArgs.Empty);
if (!Loading)
break;
}
}
}
}
protected internal override void OnSelectEntry(PlayerIndex playerIndex)
{
Thread t = new Thread(new ThreadStart(Load));
t.Start();
base.OnSelectEntry(playerIndex);
}
public override void Draw(MenuScreen screen, bool isSelected, GameTime gameTime, bool fade)
{
if (Loading)
{
loadingReticle = screen.ScreenManager.LoadingReticle;
Vector2 origin = new Vector2(loadingReticle.Width / 2, loadingReticle.Height / 2);
loadingRotation += 0.01f;
screen.ScreenManager.SpriteBatch.Draw(loadingReticle, position, null, Color.White, loadingRotation, origin, 1f, SpriteEffects.None, 1f);
}
else base.Draw(screen, isSelected, gameTime, fade);
}
The base class ButtonEntry shouldn't be important.
And here's what is subscribed to onLoad in another class: (just for practice, this is where actual loading code would go)
int sampleTics = 0;
bool sampleLoad_onLoad(object sender, System.EventArgs e)
{
sampleTics++;
if (sampleTics < 1000)
return true;
return false;
}
My question is am I using mulit-threading properly? The code doesn't even display the circle, I haven't done much testing yet because i'm going to bed. Is the lock necessary in this situation? Is there a better way to do this that i'm not seeing?
bool is thread safe according to the documentation. See this post for a similar question. As suggested by the post you also need to declare your boolean variable volatile so that its value is not cached. You can't declare properties volatile so you'll need to add a backing property and declare that volatile.
It turns out that I needed to call Thread.Sleep(1); in order to see the results. Here's the complete code:
private void Load()
{
if (onLoad != null)
{
loading = true;
while (true)
{
loading = onLoad(this, EventArgs.Empty);
if (!loading)
break;
Thread.Sleep(1);
}
}
}
Related
I have a question in Windows Forms on setting timer when the user is idle or Inactive. I need the timer to set even on any Mouse Events. If the user makes any moment then I need to reset the timer. So this is the requirement. Here goes the code.
using System;
using System.Windows.Forms;
using Timer = System.Windows.Forms.Timer;
namespace FormsTimerSetup.Globals
{
public class SetApplicationTimeOut : Form
{
#region
/// <summary>
/// Private Timer Property
/// </summary>
private static Timer _timer;
/// <summary>
/// Timer Property
/// </summary>
public static Timer Timer
{
get
{
return _timer;
}
set
{
if (_timer != null)
{
_timer.Tick -= Timer_Tick;
}
_timer = value;
if (_timer != null)
{
_timer.Tick += Timer_Tick;
}
}
}
#endregion
#region Events
public event EventHandler UserActivity;
#endregion
#region Constructor
/// <summary>
/// Default/Parameterless SetApplicationTimeOut Constructor
/// </summary>
public SetApplicationTimeOut()
{
KeyPreview = true;
FormClosed += ObservedForm_FormClosed;
MouseMove += ObservedForm_MouseMove;
KeyDown += ObservedForm_KeyDown;
}
#endregion
#region Inherited Methods
/// <summary>
///
/// </summary>
/// <param name="e"></param>
protected virtual void OnUserActivity(EventArgs e)
{
// Invoking the UserActivity delegate
UserActivity?.Invoke(this, e);
}
/// <summary>
///
/// </summary>
public void SetTimeOut()
{
// postpone auto-logout by 30 minutes
_timer = new Timer
{
Interval = (30 * 60 * 1000) // Timer set for 30 minutes
};
Application.Idle += Application_Idle;
_timer.Tick += new EventHandler(Timer_Tick);
}
#endregion
#region Private Methods
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ObservedForm_MouseMove(object sender, MouseEventArgs e)
{
OnUserActivity(e);
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ObservedForm_KeyDown(object sender, KeyEventArgs e)
{
OnUserActivity(e);
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ObservedForm_FormClosed(object sender, FormClosedEventArgs e)
{
FormClosed -= ObservedForm_FormClosed;
MouseMove -= ObservedForm_MouseMove;
KeyDown -= ObservedForm_KeyDown;
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void Application_Idle(object sender, EventArgs e)
{
_timer.Stop();
_timer.Start();
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void Timer_Tick(object sender, EventArgs e)
{
_timer.Stop();
Application.Idle -= Application_Idle;
MessageBox.Show("Application Terminating");
Application.Exit();
}
#endregion
}
}
I have implemented the code but unsure whether it is the right way of doing it.
Any leads would be appreciated.
Q: "I need the timer to set on any Mouse Events...if the user makes any movement then I need to reset the timer...Any leads would be appreciated."
A: I'll try to offer a few leads that I hope you find useful. You say you want MouseMove event to reset the timer but there's a problem: Any time a child Control has focus, it's the child that receives the mouse event and the Main Form doesn't. This is fixable.
The short answer: "Implement the IMessageFilter interface on the main window class so that the Timer is Reset when Mouse Movement is Detected." Adding a MessageFilter can intercept the mouse messages before they are sent to the focused control.
So, now I have to give you all the details so here's the long answer: It starts by adding IMessageFilter interface to our main Form1 like this:
public partial class Form1 : Form, IMessageFilter
The IMessageFilter requires our class to implement just one method:
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case WM_MOUSEMOVE:
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Commit f9367d7c added at OP's request
case WM_KEYDOWN:
// This makes WakeUp persist if user is typing in the textbox.
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TimeOutState = TimeOutState.WakeUp;
break;
}
return false; // Do not suppress downstream message
}
const int // WinOS Messages
WM_KEYDOWN = 0x0100,
WM_MOUSEMOVE = 0x0200;
You can see that now any mouse movement sets our app's TimeOutState back to 'WakeUp'.
enum TimeOutState{ WakeUp, Sleeping, Warning, Exit }
We only need one Timer and every tick interval (here set to 5 seconds) of the timer reduces the state by one. If the mouse doesn't move, it decrements all the way down and finally exits.
Here's a 60-second video of running the app for 60 seconds. You can see changes occur either every 5 seconds or when the mouse moves. If you'd like to run the sample you can clone the latest commit from our GitHub repo.
Here are the rest of the details:
The MessageFilter needs to be connected. Since we need our Form to have its window handle, so we do this here and start the timer:
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
// When our main window is ready for messages, add the MessageFilter
Application.AddMessageFilter(this);
// ...and start the timer for the first time.
TimeOutState = TimeOutState.WakeUp;
}
We need to instantiate the Timer, but only once in the CTor:
public Form1()
{
InitializeComponent();
_wdt = new Timer();
_wdt.Interval = 5000; // Use a very short time-out for this demo
_wdt.Tick += _wdt_Tick;
}
Timer _wdt; // Watch-Dog Timer
The Timer.Tick needs to be handled:
private void _wdt_Tick(object sender, System.EventArgs e)
{
// A tick reduces the TimeOutState by 1
TimeOutState = (TimeOutState)(TimeOutState - 1);
}
Finally, handle the state changes of TimeOutState and show our messages.
TimeOutState TimeOutState
{
get => _timeOutState;
set
{
switch (value)
{
case TimeOutState.WakeUp:
_wdt.Stop();
_wdt.Start();
break;
case TimeOutState.Exit:
_wdt.Stop();
Application.Exit();
return;
}
if (value != _timeOutState) // If state changes, write message
{
Debug.WriteLine(value.ToString(), _timeOutState.ToString());
// In a timer callback that changes the UI, it's
// best to post the action in the message queue.
BeginInvoke((MethodInvoker)delegate
{
textBox1.AppendText(_timeOutState.ToString());
if (TimeOutState == TimeOutState.Warning)
{
textBox1.AppendText(
": Closing in " + (_wdt.Interval / 1000).ToString() + " seconds.");
}
textBox1.AppendText(Environment.NewLine);
textBox1.Select(textBox1.TextLength, 0);
});
}
_timeOutState = value;
}
}
TimeOutState _timeOutState = (TimeOutState)(-1); // Initialize to invalid state
I have used IMessageFilter very reliably in my own apps and I'm confident suggesting it to you as one alternative for answering your post.
I won't go much deeper into your code but I would like to directly approach the issue.
I think a 'roundabout' would work in this case.
For e.g. You can check whenever the mouse moves and compare it with the initial position.
Add this above Initialize Component();
GlobalMouseHandler gmh = new GlobalMouseHandler();
gmh.TheMouseMoved += new MouseMovedEvent(gmh_TheMouseMoved);
Application.AddMessageFilter(gmh);
Then add this:
void gmh_TheMouseMoved()
{
if(XY==false)
{
MouseX = Convert.ToInt32(Cursor.Position.X);
MouseY = Convert.ToInt32(Cursor.Position.Y);
}
else
{
MouseX1 = Convert.ToInt32(Cursor.Position.X);
MouseY1 = Convert.ToInt32(Cursor.Position.Y);
XY = true;
if(MouseX1==MouseX && MouseY1==MouseY)
{
if(yourTimerNameHere.Enabled==false)
{
yourTimerNameHere.Start();
}
}
else
{
yourTimerNameHere.Stop();
yourTimerNameHere.Start();
}
}
}
Add this outside the class of your form:
public delegate void MouseMovedEvent();
public class GlobalMouseHandler : IMessageFilter
{
private const int WM_MOUSEMOVE = 0x0200;
public event MouseMovedEvent TheMouseMoved;
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == WM_MOUSEMOVE)
{
if (TheMouseMoved != null)
{
TheMouseMoved();
}
}
return false;
}
}
Next create 4 ints named MouseX = 0, MouseY = 0, MouseX1 = 0 and MouseY1 = 0 and a bool XY = false;
So actually, whenever the cursor moves, the position gets recorded and gets compared with the next. So you can check if the mouse is idle or no!
Pls note that I haven't tested this code so feel free to revert back for any errors.
I got a control instance My_Control.
Depending on the current step different sub controls should displayed within that control.
public partial class Teach_All : User_Control
{
/// <summary>
/// this event gets called on an index change
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Current_index_index_changed(object sender, CIndex_Updater.Index_Changed_Eventargs e)
{
//remove old Controll
int old_index = e.new_index - 1;
if (old_index >= 0)
{
if (InvokeRequired)
{
this.BeginInvoke((MethodInvoker)(() => Controls.Remove(teach_steps[old_index])));
}
else
{
this.Controls.Remove(teach_steps[old_index]);
}
}
//do some stuff....
//Display next control
if (InvokeRequired)
{
this.BeginInvoke((MethodInvoker)(() => Controls.Add(teach_steps[e.new_index])));
}
else
{
this.Controls.Add(teach_steps[e.new_index]);
}
}
}
However the Control add causes an invalid thread Access exception. So the Invoke does not work as expected. This Event can be fired form any thread, therefore i believe i Need an Invoke.
Could you give me some hints on how to get this invoke to work?
I was trying to implement some sort of handler to the Accelerometer.Shaken event and then I discovered that this event isn't supported yet on Windows 10, as can be seen from the answer here.
Now, even though the shaken event doesn't work, the Accelerometer.ReadingChanged event works just fine.
So I was thinking, would it be possible to manually detect a shake gesture from that data? I mean, it's probably possible, but I really wouldn't know where to start, does anyone have an idea?
You get the X, Y and Z coordinates every x milliseconds, there must be some way to calculate a shake gesture from that data.
Thanks for your help!
Never mind, I ended up writing my own class to solve the problem, I'll leave it here in case it gets useful for someone else:
/// <summary>
/// A static class that manages the Shaken event for the Accelerometer class
/// </summary>
public static class CustomAccelerometer
{
// The minimum acceleration value to trigger the Shaken event
private const double AccelerationThreshold = 2;
// The minimum interval in milliseconds between two consecutive calls for the Shaken event
private const int ShakenInterval = 500;
private static bool _AccelerometerLoaded;
private static Accelerometer _DefaultAccelerometer;
/// <summary>
/// Gets the current Accelerometer in use
/// </summary>
private static Accelerometer DefaultAccelerometer
{
get
{
if (!_AccelerometerLoaded)
{
_AccelerometerLoaded = true;
_DefaultAccelerometer = Accelerometer.GetDefault();
}
return _DefaultAccelerometer;
}
}
private static DateTime _ShakenTimespan = DateTime.Now;
private static bool _Enabled;
/// <summary>
/// Gets or sets whether or not the Accelerometer is currently enabled and can raise the Shaken event
/// </summary>
public static bool Enabled
{
get { return _Enabled; }
set
{
if (_Enabled != value && DefaultAccelerometer != null)
{
if (value) DefaultAccelerometer.ReadingChanged += _DefaultAccelerometer_ReadingChanged;
else DefaultAccelerometer.ReadingChanged -= _DefaultAccelerometer_ReadingChanged;
}
_Enabled = value;
}
}
// Handles the ReadingChanged event and raises the Shaken event when necessary
private static void _DefaultAccelerometer_ReadingChanged(Accelerometer sender, AccelerometerReadingChangedEventArgs args)
{
double g = Math.Round(args.Reading.AccelerationX.Square() + args.Reading.AccelerationY.Square() + args.Reading.AccelerationZ.Square());
if (g > AccelerationThreshold && DateTime.Now.Subtract(_ShakenTimespan).Milliseconds > ShakenInterval)
{
_ShakenTimespan = DateTime.Now;
Shaken?.Invoke(null, EventArgs.Empty);
}
}
/// <summary>
/// Raised whenever the Accelerometer detects a shaking gesture
/// </summary>
public static event EventHandler Shaken;
}
To use it, just enable it and subscribe to its event:
CustomAccelerometer.Enabled = true;
CustomAccelerometer.Shaken += (s, e) =>
{
Debug.WriteLine("The device was shaken!");
};
EDIT: forgot to add the Square method code, here it is:
/// <summary>
/// Returns the square of the given double value
/// </summary>
/// <param name="value">The input value</param>
public static double Square(this double value) => value * value;
I'm trying to register an event to the MainPage.xaml.cs like this :
public partial class MainPage : PhoneApplicationPage
{
public static ICanvas CurrentCanvas;
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
//HERE I TRY TO REGISTER FOR MY EVENT
((WP8Canvas)CurrentCanvas).Redraw += WP8EventHandler_RedrawCanvas;
//HERE I TRY TO REGISTER FOR MY EVENT/
System.Threading.ThreadStart start = new System.Threading.ThreadStart(launchProcessA);
System.Threading.Thread t = new System.Threading.Thread(start);
t.Name = "ProcessA Thread";
t.Start();
}
/// <summary>
/// Listen to WP8Canvas repaint() methods
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void WP8EventHandler_RedrawCanvas(object sender, EventArgs e)
{
wpCanvas.InvalidateArrange();
Debug.WriteLine("Redraw Canvas Event");
}
}
WP8Canvas.cs
public class WP8Canvas : WP8Displayable, ICanvas
{
public Canvas canvas ;
public event EventHandler Redraw;
protected object reference;
public WP8Canvas(object reference)
{
this.reference = reference;
MainPage.CurrentCanvas = this;
Debug.WriteLine("WP8Canvas instance");
}
public void repaint()
{
RedrawCanvas();
}
/// <summary>
/// Raise event if repaint() method is hit
/// </summary
private void RedrawCanvas()
{
//Null check makes sure the main page is attached to the event
if (this.Redraw != null)
this.Redraw(new object(), new EventArgs());
}
}
Between my comment //HERE I TRY TO REGISTER FOR MY EVENT I try to register an non-instantiated object WP8Canvas but of course a null exception occurs. It's my Thread t who controls the creation of WP8Canvas object but this happens during run-time and I don't know when.
QUESTION : How can I register my WP8Canvas Redraw event to the MainPage without facing that null pointer exception.
If you have no control over the launchProcessA, then a way to reach this result is to use a property:
public WP8Canvas CurrentWP8Canvas
{
get
{
return this.CurrentCanvas as WP8Canvas;
}
set
{
this.CurrentCanvas = value;
value.Redraw += WP8EventHandler_RedrawCanvas;
}
}
Then you juste have to change your code so that the thread assigns the CurrentWP8Canvas property instead of CurrentCanvas
Edit: If you need it to be static, one way could be to store the event handler in a temporary static variable:
public static EventHandler RedrawCanvas { get; set; }
Then set it from MainPage's constructor:
RedrawCanvas = WP8EventHandler_RedrawCanvas;
Finally, declare CurrentWP8Canvas as static, and assign the event handler you stored:
public static WP8Canvas CurrentWP8Canvas
{
get
{
return CurrentCanvas as WP8Canvas;
}
set
{
CurrentCanvas = value;
value.Redraw += RedrawCanvas;
}
}
It should work. However, it's terribly wrong, for many reasons: thread synchronization issues, risks of memory leaks... At that point, you're supposed to conclude that you've reached a dead-end, backtrack, and consider redesigning your architecture.
I want to know what how to avoid or stop FileSystemWatcher raise event twice in C#? I have a solution that will detect everytime if there is newly created xml file from a folder. I test my application using creating xml file using notepad but from the listbox it displays twice.
How can I fix this issue?
Here is my code:
private void fileSystemWatcher1_Created(object sender, System.IO.FileSystemEventArgs e)
{
try
{
fileSystemWatcher1.EnableRaisingEvents = false;
listBox1.Items.Add(e.FullPath);
}
finally
{
fileSystemWatcher1.EnableRaisingEvents = true;
}
}
private void button1_Click(object sender, EventArgs e)
{
DialogResult dialogSelectFolder = folderBrowserDialog1.ShowDialog();
if (dialogSelectFolder.ToString() == "OK")
{
textBox1.Text = folderBrowserDialog1.SelectedPath;
button2.Enabled = true;
}
}
private void button2_Click(object sender, EventArgs e)
{
button1.Enabled = false;
button2.Enabled = false;
button3.Enabled = true;
fileSystemWatcher1.EnableRaisingEvents = true;
fileSystemWatcher1.Path = textBox1.Text;
fileSystemWatcher1.Filter = "*.xml";
}
private void button3_Click(object sender, EventArgs e)
{
button1.Enabled = true;
button3.Enabled = false;
textBox1.Text = "";
fileSystemWatcher1.EnableRaisingEvents = false;
}
}
I have stumbled upon this problem myself twice and i created a class that helps you get only one event at a time. You might also get false events when the file is not in read mode (such as when you copy a file).
You have to create a queue and store all events there and if a time interval passes then raise the appropriate event.
Unfortunately this is not a simple function thus i will include complete code.
using System;
using System.IO;
using System.Timers;
using System.Collections;
using System.ComponentModel;
namespace menelabs.core
{
/// <summary>
/// This class wraps FileSystemEventArgs and RenamedEventArgs objects and detection of duplicate events.
/// </summary>
internal class DelayedEvent
{
private readonly FileSystemEventArgs _args;
/// <summary>
/// Only delayed events that are unique will be fired.
/// </summary>
private bool _delayed;
public DelayedEvent(FileSystemEventArgs args)
{
_delayed = false;
_args = args;
}
public FileSystemEventArgs Args
{
get
{
return _args;
}
}
public bool Delayed
{
get
{
return _delayed;
}
set
{
_delayed = value;
}
}
public virtual bool IsDuplicate(object obj)
{
DelayedEvent delayedEvent = obj as DelayedEvent;
if (delayedEvent == null)
return false; // this is not null so they are different
FileSystemEventArgs eO1 = _args;
RenamedEventArgs reO1 = _args as RenamedEventArgs;
FileSystemEventArgs eO2 = delayedEvent._args;
RenamedEventArgs reO2 = delayedEvent._args as RenamedEventArgs;
// The events are equal only if they are of the same type (reO1 and reO2
// are both null or NOT NULL) and have all properties equal.
// We also eliminate Changed events that follow recent Created events
// because many apps create new files by creating an empty file and then
// they update the file with the file content.
return ((eO1 != null && eO2 != null && eO1.ChangeType == eO2.ChangeType
&& eO1.FullPath == eO2.FullPath && eO1.Name == eO2.Name) &&
((reO1 == null & reO2 == null) || (reO1 != null && reO2 != null &&
reO1.OldFullPath == reO2.OldFullPath && reO1.OldName == reO2.OldName))) ||
(eO1 != null && eO2 != null && eO1.ChangeType == WatcherChangeTypes.Created
&& eO2.ChangeType == WatcherChangeTypes.Changed
&& eO1.FullPath == eO2.FullPath && eO1.Name == eO2.Name);
}
}
/// <summary>
/// This class wraps a FileSystemWatcher object. The class is not derived
/// from FileSystemWatcher because most of the FileSystemWatcher methods
/// are not virtual. The class was designed to resemble FileSystemWatcher class
/// as much as possible so that you can use FileSystemSafeWatcher instead
/// of FileSystemWatcher objects.
/// FileSystemSafeWatcher will capture all events from the FileSystemWatcher object.
/// The captured events will be delayed by at least ConsolidationInterval milliseconds in order
/// to be able to eliminate duplicate events. When duplicate events are found, the last event
/// is droped and the first event is fired (the reverse is not recomended because it could
/// cause some events not be fired at all since the last event will become the first event and
/// it won't fire a if a new similar event arrives imediately afterwards).
/// </summary>
internal class FileSystemSafeWatcher
{
private readonly FileSystemWatcher _fileSystemWatcher;
/// <summary>
/// Lock order is _enterThread, _events.SyncRoot
/// </summary>
private readonly object _enterThread = new object(); // Only one timer event is processed at any given moment
private ArrayList _events;
private Timer _serverTimer;
private int _consolidationInterval = 1000; // milliseconds
#region Delegate to FileSystemWatcher
public FileSystemSafeWatcher()
{
_fileSystemWatcher = new FileSystemWatcher();
Initialize();
}
public FileSystemSafeWatcher(string path)
{
_fileSystemWatcher = new FileSystemWatcher(path);
Initialize();
}
public FileSystemSafeWatcher(string path, string filter)
{
_fileSystemWatcher = new FileSystemWatcher(path, filter);
Initialize();
}
/// <summary>
/// Gets or sets a value indicating whether the component is enabled.
/// </summary>
/// <value>true if the component is enabled; otherwise, false. The default is false. If you are using the component on a designer in Visual Studio 2005, the default is true.</value>
public bool EnableRaisingEvents
{
get
{
return _fileSystemWatcher.EnableRaisingEvents;
}
set
{
_fileSystemWatcher.EnableRaisingEvents = value;
if (value)
{
_serverTimer.Start();
}
else
{
_serverTimer.Stop();
_events.Clear();
}
}
}
/// <summary>
/// Gets or sets the filter string, used to determine what files are monitored in a directory.
/// </summary>
/// <value>The filter string. The default is "*.*" (Watches all files.)</value>
public string Filter
{
get
{
return _fileSystemWatcher.Filter;
}
set
{
_fileSystemWatcher.Filter = value;
}
}
/// <summary>
/// Gets or sets a value indicating whether subdirectories within the specified path should be monitored.
/// </summary>
/// <value>true if you want to monitor subdirectories; otherwise, false. The default is false.</value>
public bool IncludeSubdirectories
{
get
{
return _fileSystemWatcher.IncludeSubdirectories;
}
set
{
_fileSystemWatcher.IncludeSubdirectories = value;
}
}
/// <summary>
/// Gets or sets the size of the internal buffer.
/// </summary>
/// <value>The internal buffer size. The default is 8192 (8K).</value>
public int InternalBufferSize
{
get
{
return _fileSystemWatcher.InternalBufferSize;
}
set
{
_fileSystemWatcher.InternalBufferSize = value;
}
}
/// <summary>
/// Gets or sets the type of changes to watch for.
/// </summary>
/// <value>One of the System.IO.NotifyFilters values. The default is the bitwise OR combination of LastWrite, FileName, and DirectoryName.</value>
/// <exception cref="System.ArgumentException">The value is not a valid bitwise OR combination of the System.IO.NotifyFilters values.</exception>
public NotifyFilters NotifyFilter
{
get
{
return _fileSystemWatcher.NotifyFilter;
}
set
{
_fileSystemWatcher.NotifyFilter = value;
}
}
/// <summary>
/// Gets or sets the path of the directory to watch.
/// </summary>
/// <value>The path to monitor. The default is an empty string ("").</value>
/// <exception cref="System.ArgumentException">The specified path contains wildcard characters.-or- The specified path contains invalid path characters.</exception>
public string Path
{
get
{
return _fileSystemWatcher.Path;
}
set
{
_fileSystemWatcher.Path = value;
}
}
/// <summary>
/// Gets or sets the object used to marshal the event handler calls issued as a result of a directory change.
/// </summary>
/// <value>The System.ComponentModel.ISynchronizeInvoke that represents the object used to marshal the event handler calls issued as a result of a directory change. The default is null.</value>
public ISynchronizeInvoke SynchronizingObject
{
get
{
return _fileSystemWatcher.SynchronizingObject;
}
set
{
_fileSystemWatcher.SynchronizingObject = value;
}
}
/// <summary>
/// Occurs when a file or directory in the specified System.IO.FileSystemWatcher.Path is changed.
/// </summary>
public event FileSystemEventHandler Changed;
/// <summary>
/// Occurs when a file or directory in the specified System.IO.FileSystemWatcher.Path is created.
/// </summary>
public event FileSystemEventHandler Created;
/// <summary>
/// Occurs when a file or directory in the specified System.IO.FileSystemWatcher.Path is deleted.
/// </summary>
public event FileSystemEventHandler Deleted;
/// <summary>
/// Occurs when the internal buffer overflows.
/// </summary>
public event ErrorEventHandler Error;
/// <summary>
/// Occurs when a file or directory in the specified System.IO.FileSystemWatcher.Path is renamed.
/// </summary>
public event RenamedEventHandler Renamed;
/// <summary>
/// Begins the initialization of a System.IO.FileSystemWatcher used on a form or used by another component. The initialization occurs at run time.
/// </summary>
public void BeginInit()
{
_fileSystemWatcher.BeginInit();
}
/// <summary>
/// Releases the unmanaged resources used by the System.IO.FileSystemWatcher and optionally releases the managed resources.
/// </summary>
public void Dispose()
{
Uninitialize();
}
/// <summary>
/// Ends the initialization of a System.IO.FileSystemWatcher used on a form or used by another component. The initialization occurs at run time.
/// </summary>
public void EndInit()
{
_fileSystemWatcher.EndInit();
}
/// <summary>
/// Raises the System.IO.FileSystemWatcher.Changed event.
/// </summary>
/// <param name="e">A System.IO.FileSystemEventArgs that contains the event data.</param>
protected void OnChanged(FileSystemEventArgs e)
{
if (Changed != null)
Changed(this, e);
}
/// <summary>
/// Raises the System.IO.FileSystemWatcher.Created event.
/// </summary>
/// <param name="e">A System.IO.FileSystemEventArgs that contains the event data.</param>
protected void OnCreated(FileSystemEventArgs e)
{
if (Created != null)
Created(this, e);
}
/// <summary>
/// Raises the System.IO.FileSystemWatcher.Deleted event.
/// </summary>
/// <param name="e">A System.IO.FileSystemEventArgs that contains the event data.</param>
protected void OnDeleted(FileSystemEventArgs e)
{
if (Deleted != null)
Deleted(this, e);
}
/// <summary>
/// Raises the System.IO.FileSystemWatcher.Error event.
/// </summary>
/// <param name="e">An System.IO.ErrorEventArgs that contains the event data.</param>
protected void OnError(ErrorEventArgs e)
{
if (Error != null)
Error(this, e);
}
/// <summary>
/// Raises the System.IO.FileSystemWatcher.Renamed event.
/// </summary>
/// <param name="e">A System.IO.RenamedEventArgs that contains the event data.</param>
protected void OnRenamed(RenamedEventArgs e)
{
if (Renamed != null)
Renamed(this, e);
}
/// <summary>
/// A synchronous method that returns a structure that contains specific information on the change that occurred, given the type of change you want to monitor.
/// </summary>
/// <param name="changeType">The System.IO.WatcherChangeTypes to watch for.</param>
/// <returns>A System.IO.WaitForChangedResult that contains specific information on the change that occurred.</returns>
public WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType)
{
//TODO
throw new NotImplementedException();
}
/// <summary>
/// A synchronous method that returns a structure that contains specific information on the change that occurred, given the type of change you want to monitor
/// and the time (in milliseconds) to wait before timing out.
/// </summary>
/// <param name="changeType">The System.IO.WatcherChangeTypes to watch for.</param>
/// <param name="timeout">The time (in milliseconds) to wait before timing out.</param>
/// <returns>A System.IO.WaitForChangedResult that contains specific information on the change that occurred.</returns>
public WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType, int timeout)
{
//TODO
throw new NotImplementedException();
}
#endregion
#region Implementation
private void Initialize()
{
_events = ArrayList.Synchronized(new ArrayList(32));
_fileSystemWatcher.Changed += new FileSystemEventHandler(this.FileSystemEventHandler);
_fileSystemWatcher.Created += new FileSystemEventHandler(this.FileSystemEventHandler);
_fileSystemWatcher.Deleted += new FileSystemEventHandler(this.FileSystemEventHandler);
_fileSystemWatcher.Error += new ErrorEventHandler(this.ErrorEventHandler);
_fileSystemWatcher.Renamed += new RenamedEventHandler(this.RenamedEventHandler);
_serverTimer = new Timer(_consolidationInterval);
_serverTimer.Elapsed += new ElapsedEventHandler(this.ElapsedEventHandler);
_serverTimer.AutoReset = true;
_serverTimer.Enabled = _fileSystemWatcher.EnableRaisingEvents;
}
private void Uninitialize()
{
if (_fileSystemWatcher != null)
_fileSystemWatcher.Dispose();
if (_serverTimer != null)
_serverTimer.Dispose();
}
private void FileSystemEventHandler(object sender, FileSystemEventArgs e)
{
_events.Add(new DelayedEvent(e));
}
private void ErrorEventHandler(object sender, ErrorEventArgs e)
{
OnError(e);
}
private void RenamedEventHandler(object sender, RenamedEventArgs e)
{
_events.Add(new DelayedEvent(e));
}
private void ElapsedEventHandler(Object sender, ElapsedEventArgs e)
{
// We don't fire the events inside the lock. We will queue them here until
// the code exits the locks.
Queue eventsToBeFired = null;
if (System.Threading.Monitor.TryEnter(_enterThread))
{
// Only one thread at a time is processing the events
try
{
eventsToBeFired = new Queue(32);
// Lock the collection while processing the events
lock (_events.SyncRoot)
{
DelayedEvent current;
for (int i = 0; i < _events.Count; i++)
{
current = _events[i] as DelayedEvent;
if (current.Delayed)
{
// This event has been delayed already so we can fire it
// We just need to remove any duplicates
for (int j = i + 1; j < _events.Count; j++)
{
if (current.IsDuplicate(_events[j]))
{
// Removing later duplicates
_events.RemoveAt(j);
j--; // Don't skip next event
}
}
bool raiseEvent = true;
if (current.Args.ChangeType == WatcherChangeTypes.Created || current.Args.ChangeType == WatcherChangeTypes.Changed)
{
//check if the file has been completely copied (can be opened for read)
FileStream stream = null;
try
{
stream = File.Open(current.Args.FullPath, FileMode.Open, FileAccess.Read, FileShare.None);
// If this succeeds, the file is finished
}
catch (IOException)
{
raiseEvent = false;
}
finally
{
if (stream != null) stream.Close();
}
}
if (raiseEvent)
{
// Add the event to the list of events to be fired
eventsToBeFired.Enqueue(current);
// Remove it from the current list
_events.RemoveAt(i);
i--; // Don't skip next event
}
}
else
{
// This event was not delayed yet, so we will delay processing
// this event for at least one timer interval
current.Delayed = true;
}
}
}
}
finally
{
System.Threading.Monitor.Exit(_enterThread);
}
}
// else - this timer event was skipped, processing will happen during the next timer event
// Now fire all the events if any events are in eventsToBeFired
RaiseEvents(eventsToBeFired);
}
public int ConsolidationInterval
{
get
{
return _consolidationInterval;
}
set
{
_consolidationInterval = value;
_serverTimer.Interval = value;
}
}
protected void RaiseEvents(Queue deQueue)
{
if ((deQueue != null) && (deQueue.Count > 0))
{
DelayedEvent de;
while (deQueue.Count > 0)
{
de = deQueue.Dequeue() as DelayedEvent;
switch (de.Args.ChangeType)
{
case WatcherChangeTypes.Changed:
OnChanged(de.Args);
break;
case WatcherChangeTypes.Created:
OnCreated(de.Args);
break;
case WatcherChangeTypes.Deleted:
OnDeleted(de.Args);
break;
case WatcherChangeTypes.Renamed:
OnRenamed(de.Args as RenamedEventArgs);
break;
}
}
}
}
#endregion
}
}
You may find the code at:
https://github.com/melenaos/FileSystemSafeWatcher/blob/master/FileSystemSafeWatcher.cs