1

I'm creating a windows forms app for educational purposes, it's base idea is to have a download manager which will move files from default Downloads folder to the folder with a name of files extension. I created class MyFileWatcher which basically watches Downloads folder for new files, code looks like this:

public class MyFileWatcher
    {
        private readonly FileSystemWatcher _fileSystemWatcher;

        public MyFileWatcher()
        {

            _fileSystemWatcher = new FileSystemWatcher(resources.sourcePath);
            _fileSystemWatcher.Created += (sender, e) => _fileWatcher_Created(sender, e);

            _fileSystemWatcher.EnableRaisingEvents = true;
        }

        private void _fileWatcher_Created(object sender, FileSystemEventArgs e)
        {
            //calls class FileManager's function MoveFiles (here is base logic of how and where files should move)
            CustomServiceContainer.GetService<IFileManager>().MoveFiles(resources.sourcePath, resources.targetPath);

            //this is the problem
            Notification_form ntf = new Notification_form();
            ntf.showAlert(eventType);
        }
    }

The problem is, when _fileWatcher_Created event calls notification form nothing gets displayed(even thought I can see on taskbar the window is created) and I can't get why, cause if I assign showAlert function to button's on click event it displays notification correctly.

If needed this is what Notification form looks like:

public partial class Notification_form : Form
    {
        private int x { get; set; }
        private int y { get; set; }

        public Notification_form()
        {
            InitializeComponent();
        }

        public enum enmAction { wait, start, close }

        private Notification_form.enmAction action;

        public void showAlert(string msg)
        {
            this.Opacity = 0.0;
            this.StartPosition = FormStartPosition.Manual;
            string fName;

            for (int i = 1; i < 10; i++)
            {
                fName = "alert" + i.ToString();
                Notification_form frm = (Notification_form)Application.OpenForms[fName];

                if (frm == null)
                {
                    this.Name = fName;
                    this.x = Screen.PrimaryScreen.WorkingArea.Width - this.Width + 15;
                    this.y = Screen.PrimaryScreen.WorkingArea.Height - this.Height * i;
                    this.Location = new Point(this.x, this.y);
                    break;
                }
            }

            this.x = Screen.PrimaryScreen.WorkingArea.Width - base.Width - 5;

            this.lblTitle.Text = msg;

            this.Show();
            this.action = enmAction.start;
            this.timer1.Interval = 1;
            this.timer1.Start();
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            switch (this.action)
            {
                case enmAction.wait:
                    timer1.Interval = 5000;
                    action = enmAction.close;
                    break;
                case Notification_form.enmAction.start:
                    this.timer1.Interval = 1;
                    this.Opacity += 0.1;
                    if (this.x < this.Location.X)
                    {
                        this.Left--;
                    }
                    else
                    {
                        if (this.Opacity == 1.0)
                        {
                            action = Notification_form.enmAction.wait;
                        }
                    }
                    break;
                case enmAction.close:
                    timer1.Interval = 1;
                    this.Opacity -= 0.1;

                    this.Left -= 3;
                    if (base.Opacity == 0.0)
                    {
                        base.Close();
                    }
                    break;
            }
        }

        private void btnCloseNtf_Click(object sender, EventArgs e)
        {
            timer1.Interval = 1;
            action = enmAction.close;
        }
    }

Working Pickle
  • 122
  • 3
  • 10
  • 3
    FileSystemWatcher raises its events in Threadpool threads (not your main UI Thread), unless you set its `SynchronizingObject` property to an object that provides the SynchonizationContext. That's just one thing. Note that the Created event is raised as soon as the file is created, it doesn't mean that the file can be moved (or accessed) at that time. That's another thing. You should avoid any sync action in the event handler. -- The Opacity is another thing... – Jimi Oct 30 '20 at 16:10
  • thanks for useful answer, do you have any idea how can I check for file's accessibility, if it can be moved or not. – Working Pickle Oct 30 '20 at 16:17
  • 1
    You could have picked something less complicated for *educational purposes* :) There's nothing in the Framework for that explicitly. Even digging deeper into the System's functions doesn't make it *easier* to handle. It's often suggested to *try it* and if you can't access the file, catch the exception. Then try again *later*. So you need a worker thread that performs these checks. The Changed event can be used in combination, to actually start the timed operation only when the event is raised. Note that an Anti-Virus may change this pattern, causing events to raise out-of-sync, etc. – Jimi Oct 30 '20 at 16:32
  • Thanks a lot mate. You actually helped me a lot with this project, will try to use the recommendations, and yes SynchronizingObject was actually what I looked for. – Working Pickle Oct 30 '20 at 16:47

1 Answers1

0

For those who will come across the same problem, as Jimi said the main problem in this case was that UI and FileSystemWatcher work on different threads, so when I tried to call form from MyFileWatcher it didn't display form as the call was made from different thread, how did I fix it? well I set SynchronizingObject of FileSystemWatcher in Form(all credits go to Jimi) like this: fw._fileSystemWatcher.SynchronizingObject = this; where fw is FileSystemWatcher object which is passed to form durring its creation from Program class (Note: it's important to pass to form the same object that you'll use for getting notifications, in this case).

Moreover, creation of file doesn't mean that file can be moved(as Jimi said, again :)). Generally files are in temporary extension during its download(tmp, crdownload or maybe even other) so you need to check for file change as well to make sure that you are not moving temporary file with no value, and besides that, as file gets created and changed the notification form was called multiple times even thought I needed it to fire once as one file was created in reality. So how would I fix that? well I don't know if its the best solution but I created a new text document which has all the extension types that i want to move, so now I'm checking downloads folder for the extensions that are in my text document and if we have a match the file gets moved and as I'm not looking for temporary file types anymore the FileSystemWatcher actually gives me one Notification now. (and you can always add new file types in text document manually if needed, but I'm planning to integrate that function in the app)

Hope this helps somebody, some day...

Working Pickle
  • 122
  • 3
  • 10