3

While creating some pictures in another program I wanted to see the updated version of those pictures whenever they change on disk (in a folder).

Should be an easy task, right? System.IO.FileSystemWatcher and then call the target Image.Dispatcher and pass it a lambda which sets the new BitmapImage. Only, it does not work as expected.

Snippet of code first:

private void Watcher_Changed(object sender, System.IO.FileSystemEventArgs e)
{
      revCount++;
      System.Diagnostics.Trace.WriteLine(e.ChangeType.ToString());

      Action updateLabel = () => label.Content = e.FullPath;
      label.Dispatcher.Invoke(updateLabel);

        Action updateImage =
            () =>
            {
                BitmapImage img = new BitmapImage();
                img.BeginInit();
                img.CacheOption = BitmapCacheOption.OnLoad;
                img.UriSource = new Uri(e.FullPath);
                img.EndInit();
                image.Source = img.Clone();
                image.InvalidateVisual();
            };

      imgUpdateFilter.Event(updateImage);
      // image.Dispatcher.Invoke(updateImage);
      Action updateRevCount = () => revision.Content = revCount.ToString();
      revision.Dispatcher.Invoke(updateRevCount);
}

Here is what happens:

  1. I start the application and then update an image in the target folder. The application shows the image (EXPECTED).
  2. I do it a second time (new version of image). And it does not update. (ERROR)
  3. I change another .png file in the folder and it displays it. (EXPECTED)
  4. I do it a third time (next new version of image). And it shows... the first version and not the last one. (ERROR)

This leads me to conclude that there is something fishy going on with clever caching, probably based on the imageUri passed to the Image source.

Only I found no way to turn it off.

In the snippet above, you see the file system watcher event callback on changed. It gets called (3 times!) each time I update my image.

Also possibly of interest is, that if I play with the img.CacheOption = BitmapCacheOption.OnLoad; line and set it for example to Default, then the file is locked, which of course is not desired as I want to render new versions of the file... This is another hint, that something funny is going on.

As you can see in the snippet, I tried to img.Clone(), hoping this would break the sneaky behavior and get me what I want but ... no... does not change a thing.

So, who can tell me what to do to get that Image wpf thing to do what I want?

hcerim
  • 959
  • 1
  • 11
  • 27
BitTickler
  • 10,905
  • 5
  • 32
  • 53
  • You possibly have the FileWatcher dispatcher, and the MainWindow dispatcher that the message is sent on, if sent on the FileWatcher thread, you have to send to the MainWindow thread. – Wayne Jan 18 '17 at 19:20
  • I have the dispatcher which is in the property of the target control. See the label and the revision elements (both ``Labels``). Also, then it would never work and not only in one of the 2,3 use cases I showcased above. ``imgUpdateFilter = new LowPassFilter(image.Dispatcher, 2000, 600);`` This shows that I indeed use the ``image``'s dispatcher. – BitTickler Jan 18 '17 at 19:23
  • This shows the updating GUI and the invoke required, http://stackoverflow.com/questions/4928195/c-sharp-windows-forms-application-updating-gui-from-another-thread-and-class – Wayne Jan 18 '17 at 19:27
  • It is just delayed (via a task and Task.Delay()). To avoid the 3 times update for the same logical event (grace to FileSystemWatcher spam). You can achieve the same without the despamming by using the commented line and by adding a ``System.Threading.Thread.Sleep(1000);`` before it. This is then necessary as it is a race condition between the image saving application and this application. With that version, the behavior is the same. Also step 3 in my little list shows that it works normally if it is not the same image being updated. And your link shows Windows Forms not WPF... – BitTickler Jan 18 '17 at 19:29
  • Yup duplicate probably. But then - great design: Imagine there is a program with 2 methods: ``ClimbTreeWithLadder()`` and ``ClimbTreeWithRope()``. Who would have guessed that the first ones implementation calls ``BreakClimbersNeck()``? :) – BitTickler Jan 18 '17 at 20:07

0 Answers0