locked
WPF, FileSystemWatcher class - and updating the UI in WPF.

    Question

  •  

    (Im sorry if this thread appears as a duplicate/triplicate in the forum - the other(s) Post thread hung for me - not able to post)

     

    Hello. I'm in dire need of some help - trying to learn to write WPF, through VS2008 - VB9 and using FileSystemWatcher events. My goal is to create a mini framework for FileSystemWatcher which I can extend a bit further as the needs raise, aka the "FileTracker" project.

    - Create an app to serve as a platform for building a file-change detection, and replicate changes to another server.

    - Just an App for now - will extend into a WCF later with host & client when I get a grip on things. :-)

     

    The specifics are more like this:

    - Created a WPF application in VS2008, to serve as a interface and testbed for my results. Window1 class.

    - Created a FileWatcher class library to be extended later as the needs rises.

    - Maintain a DataBase of the changes, just in memory for now - maybe later for SQL - depedning on the need.

     

    My direct problem for now is that when the FileSystemWatcher detects a change, I have found no way of updating the ListView into showing the changes being added to the DataTable.

    And if I try to the direct aproach - I run into problems with different threads.

    And all the sample code / how to's on MSDN or the net seem to be either:

    - C# only

    - Sample using MsgBox or Console.Writeline - and not WPF UI.

     

    So I'm stuck, because Im not able to get the grasp on how to use the Dispatcher for WPF Window1 class, from the FileSystemWatcher.

     

    The app framework

    The WPF app automatically fires up the Window1 class as the "startup window" - thats the default behavior when you create an WPF app in VS2008.

    It contains a listview (just a "debug" tool for now - but great for learning the In's & out's of WPF+VB).

     

    I've decided to use a DataTable to maintain "filechanges". This should be scalable enough if I decide to dump it to MSD or SQL later. And it's flexible enough to manipulating any data coming in & out.

     

    The datatable is defined something like this:

    Code Snippet

    Imports System.Data

     

    Public Class ChangeTables

     

    Private dataSet As DataSet

    Public Function Init() As DataTable

    Return MakeParentTable()

    End Function

     

    Private Function MakeParentTable() As DataTable

    ' Create a new DataTable.

    Dim table As DataTable = New DataTable("ParentTable")

    Dim column As DataColumn

    column = New DataColumn()

    column.DataType = System.Type.GetType("System.Int32")

    column.ColumnName = "id"

    column.ReadOnly = True

    column.Unique = True

    table.Columns.Add(column)

    ' Create second column.

    column = New DataColumn()

    column.DataType = System.Type.GetType("System.String")

    column.ColumnName = "Col2"

    column.AutoIncrement = False

    column.Caption = "Col2"

    column.ReadOnly = False

    column.Unique = False

    table.Columns.Add(column)

    ' Create third column.

    column = New DataColumn()

    column.DataType = System.Type.GetType("System.String")

    column.ColumnName = "Col3"

    column.AutoIncrement = False

    column.Caption = "Col3"

    column.ReadOnly = False

    column.Unique = False

    table.Columns.Add(column)

    ' Make the ID column the primary key column.

    Dim PrimaryKeyColumns(0) As DataColumn

    PrimaryKeyColumns(0) = table.Columns("id")

    table.PrimaryKey = PrimaryKeyColumns

    dataSet = New DataSet()

    dataSet.Tables.Add(table)

    Return table

    End Function

    End Class

     

     

     

    My filewatcher class is built so it can be customized to catch changes, and gather more data (if necesarry later) and populate the DataTable with these changes. Intention is to use the change-list to replicate datafiles from one volume to another volume later. (Getting close to MS Robocopy's functionality I guess).

     

    (more to follow) ....

     

    Friday, February 01, 2008 11:14 AM

Answers

  •  

    For future reference, the WPF forum would probably be a better place for this question.

     

    Below is a simple example of using the FileWatcher and marshalling back to the UI thread. If you are going to bind to the DataTable I would recommend marshalling the raw data to the UI thread and updating the DataTable on the UI thread - that way you avoid attempting to read/write the datatable from different threads at the same time which will cause issues - DataTable is not thread safe.

     

    mark

     

    Code Snippet

    <Window x:Class="Window1"

      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

      Title="Window1" Height="300" Width="300">

    <Grid>

       <Button Margin="0,48,23,0" Name="Button1" Height="23" VerticalAlignment="Top" HorizontalAlignment="Right" Width="76">Start</Button>

       <TextBox Height="26" Margin="49,12,23,0" Name="TextBox1" VerticalAlignment="Top" />

       <Label Height="28" Margin="8,10,0,0" Name="Label1" VerticalAlignment="Top" HorizontalAlignment="Left" Width="Auto">Path:</Label>

       <Label Height="78" Margin="16,0,16,36" Name="Label2" VerticalAlignment="Bottom">Label</Label>

    </Grid>

    </Window>

     

     

    Imports System.IO

    Class Window1

     

    'Delegate used to call back to the UI thread

    Delegate Sub CallBackToUIThread(ByVal e As FileSystemEventArgs)

     

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click

    ' Create a new FileSystemWatcher and set its properties.

    Dim watcher As New FileSystemWatcher()

    watcher.Path = TextBox1.Text

    ' Watch for changes in LastAccess and LastWrite times, and

    ' the renaming of files or directories.

    watcher.NotifyFilter = (NotifyFilters.LastAccess Or NotifyFilters.LastWrite Or NotifyFilters.FileName Or NotifyFilters.DirectoryName)

    ' Only watch text files.

    watcher.Filter = "*.txt"

     

    ' Add event handlers.

    AddHandler watcher.Changed, AddressOf OnChanged

    AddHandler watcher.Created, AddressOf OnChanged

    AddHandler watcher.Deleted, AddressOf OnChanged

    AddHandler watcher.Renamed, AddressOf OnRenamed

     

    ' Begin watching.

    watcher.EnableRaisingEvents = True

    End Sub

     

    ' Runs on the UI thread

    Public Sub NotifyUIThreadOfChange(ByVal e As FileSystemEventArgs)

    Label2.Content = "File: " & e.FullPath & " " & e.ChangeType

    End Sub

     

    ' FilesystemWatcher event handlers - run on an arbitrary thread

    Private Sub OnChanged(ByVal source As Object, ByVal e As FileSystemEventArgs)

    'Call back to the UI thread

    Me.Dispatcher.BeginInvoke( _

    System.Windows.Threading.DispatcherPriority.Normal, _

    New CallBackToUIThread(AddressOf NotifyUIThreadOfChange), _

    e)

    End Sub

     

    Private Sub OnRenamed(ByVal source As Object, ByVal e As RenamedEventArgs)

    'Call back to the UI thread

    Me.Dispatcher.BeginInvoke( _

    System.Windows.Threading.DispatcherPriority.Normal, _

    New CallBackToUIThread(AddressOf NotifyUIThreadOfChange), _

    e)

    End Sub

    End Class

     

     

    Wednesday, February 06, 2008 6:18 PM

All replies

  •  

    The filewatcher class has a skeleton like this (the interface and some other code is just started, and some is shamelessly taken from the MSDN library) - and some of the code is hardcoded because Im still trying to learn this :-)

    Code Snippet

    Imports System.IO

    Imports System.IO.FileSystemWatcher

    Imports FileTracker.Window1

    Imports System.Windows.Threading

     

    Public Class FileWatcher

    Inherits System.IO.FileSystemWatcher

     

    Interface IFileWatcher

    Property Path() As String

    Property Filter() As String

    End Interface

     

    Private Shared _fsw As New System.IO.FileSystemWatcher()

     

    Public Sub New()

    _fsw.Path = "D:\Temp"

    _fsw.Filter = "*.txt"

    _fsw.IncludeSubdirectories = False

    _fsw.IncludeSubdirectories = False

    _fsw.NotifyFilter = _

    (NotifyFilters.LastAccess Or _

    NotifyFilters.LastWrite Or _

    NotifyFilters.FileName Or _

    NotifyFilters.DirectoryName)

    ' Add event handlers.

    AddHandler _fsw.Changed, AddressOf fwOnChanged

    AddHandler _fsw.Created, AddressOf fwOnChanged

    AddHandler _fsw.Deleted, AddressOf fwOnChanged

    AddHandler _fsw.Renamed, AddressOf fwOnRenamed

    AddHandler _fsw.Error, AddressOf fwOnError

    ' Begin watching.

    _fsw.EnableRaisingEvents = True

    Debug.Print("Public Sub New(): FileWatcher._fsw")

     

    End Sub

     

    Private Shared Sub fwOnChanged(ByVal source As Object, ByVal e As FileSystemEventArgs)

    Dim s As String

    s = String.Format("Name: {0} Change: {1}", e.FullPath, e.ChangeType)

    Debug.Print("Public Shared Sub fwOnChanged(): " & s)

    End Sub

    Private Shared Sub fwOnRenamed(ByVal source As Object, ByVal e As RenamedEventArgs)

    Dim s As String

    s = String.Format("From: {0} To: {1}", e.OldFullPath, e.FullPath)

    Debug.Print("Public Shared Sub fwOnRenamed(): " & s)

    End Sub

     

    Private Sub fwOnError(ByVal source As Object, ByVal e As ErrorEventArgs)

    MsgBox("The FileSystemWatcher has detected an error")

    ' Give more information if the error is due to an internal buffer overflow.

    If TypeOf e.GetException Is InternalBufferOverflowException Then

    ' This can happen if Windows is reporting many file system events quickly

    ' and internal buffer of the FileSystemWatcher is not large enough to handle this

    ' rate of events. The InternalBufferOverflowException error informs the application

    ' that some of the file system events are being lost.

    MsgBox( _

    "The file system watcher experienced an internal buffer overflow: " _

    + e.GetException.Message, MsgBoxStyle.OkOnly, "Error")

    End If

    End Sub

    Private Sub FileWatcher_Disposed(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Disposed

    _fsw.EnableRaisingEvents = False

    RemoveHandler _fsw.Changed, AddressOf fwOnChanged

    RemoveHandler _fsw.Created, AddressOf fwOnChanged

    RemoveHandler _fsw.Deleted, AddressOf fwOnChanged

    RemoveHandler _fsw.Renamed, AddressOf fwOnRenamed

    RemoveHandler _fsw.Error, AddressOf fwOnError

    Debug.Print("FileWatcher_Disposed()")

    End Sub

    End Class

     

     

    Friday, February 01, 2008 11:22 AM
  • Lastly the full code behind Window1.xaml.vb

    Code Snippet

    Imports System.IO

    Imports System.Data

    Imports System.Threading

    Imports FileTracker.ChangeTables

    Imports FileTracker.FileWatcher

    Partial Public Class Window1

    Inherits System.Windows.Window

    Private Shared ftFW As New FileTracker.FileWatcher

    Private Shared tblFileChanges As DataTable = New DataTable()

     

    Public Sub New()

    InitializeComponent()

    tblFileChanges = GetData()

    BindData()

    End Sub

     

    Function GetData() As DataTable

    Dim dt As DataTable = New DataTable()

    Dim ct As ChangeTables = New ChangeTables

    Try

    dt = ct.Init()

    Catch ex As Exception

    MessageBox.Show(ex.ToString())

    End Try

    Return dt

    End Function

     

    Sub BindData()

    Dim bindbase As BindingBase

    Dim lvSP As DependencyProperty = ListView.ItemsSourceProperty

    lvFileTrack.DataContext = tblFileChanges

    'lvFileTrack.SetBinding(ListView.ItemsSourceProperty, New Binding())

    lvFileTrack.SetBinding(lvSP, New Binding())

    bindbase = New Binding("id") ' I need to create the first binding separatly, otherwise it will fail. c2 and c3 binds nicely by itself.

    ' Also an interesting binding FAQ: http://blogs.msdn.com/wpfsdk/archive/2006/10/19/wpf-basic-data-binding-faq.aspx

    c1.DisplayMemberBinding = bindbase

    c1.Width = Double.NaN

    c2.DisplayMemberBinding = New Binding("Col2")

    c2.Width = Double.NaN

    c3.DisplayMemberBinding = New Binding("Col3")

    c3.Width = Double.NaN

    End Sub

     

    Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click

    ' Test-button1

    End Sub

     

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button2.Click

    ' Test-button2

    End Sub

     

    Public Shared Sub UpdateChanged(ByVal str As String)

    Dim cldt As ChangeTables = New ChangeTables

    Dim row As DataRow

    row = tblFileChanges.NewRow()

    row("id") = tblFileChanges.Rows.Count + 1

    row("Col2") = Now()

    row("Col3") = str

    tblFileChanges.Rows.Add(row)

    ' Okai, the list should update - but I havent found any methods to call

    ' this Sub without getting problems with threading.

    End Sub

     

    Private Sub Window1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles Me.Closing

    If Not (ftFW Is Nothing) Then ftFW.Dispose()

    End Sub

     

    Private Sub Window1_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

    UpdateChanged("1st sample - code working")

    End Sub

    End Class

     

     

     

    XAML looks like this:

    Code Snippet

    <Window x:Class="Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="Window1" Height="300" Width="600" ResizeMode="CanResizeWithGrip"

    >

    <Grid Name="Grid1">

    <Grid.RowDefinitions>

    <RowDefinition Height="40" />

    <RowDefinition Height="198*" />

    <RowDefinition Height="22" />

    </Grid.RowDefinitions>

    <StatusBar Name="StatusBar1" Grid.Row="2" Margin="0">

    </StatusBar>

    <Button HorizontalAlignment="Right" Margin="0,9,10,8" Name="Button1" Width="75">Dummy1</Button>

    <Button HorizontalAlignment="Right" Margin="0,9,100,8" Name="Button2" Width="75">Dummy2</Button>

    <ListView Name="lvFileTrack" Grid.Row="1" Margin="5">

    <ListView.View>

    <GridView AllowsColumnReorder="True">

    <GridViewColumn x:Name="c1" Header="col1" />

    <GridViewColumn x:Name="c2" Header="col2" />

    <GridViewColumn x:Name="c3" Header="col3" />

    </GridView>

    </ListView.View>

    </ListView>

    </Grid>

    </Window>

     

     

     

     

    Please - can you give me some hints on how to be able to catch a filechange, dump it to the table (should be working here) - and update the ListView / Gridview on the UI thread?

     

    Sincerly, Jon Carlsen

    - Norway

    Friday, February 01, 2008 11:23 AM
  •  

    For future reference, the WPF forum would probably be a better place for this question.

     

    Below is a simple example of using the FileWatcher and marshalling back to the UI thread. If you are going to bind to the DataTable I would recommend marshalling the raw data to the UI thread and updating the DataTable on the UI thread - that way you avoid attempting to read/write the datatable from different threads at the same time which will cause issues - DataTable is not thread safe.

     

    mark

     

    Code Snippet

    <Window x:Class="Window1"

      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

      Title="Window1" Height="300" Width="300">

    <Grid>

       <Button Margin="0,48,23,0" Name="Button1" Height="23" VerticalAlignment="Top" HorizontalAlignment="Right" Width="76">Start</Button>

       <TextBox Height="26" Margin="49,12,23,0" Name="TextBox1" VerticalAlignment="Top" />

       <Label Height="28" Margin="8,10,0,0" Name="Label1" VerticalAlignment="Top" HorizontalAlignment="Left" Width="Auto">Path:</Label>

       <Label Height="78" Margin="16,0,16,36" Name="Label2" VerticalAlignment="Bottom">Label</Label>

    </Grid>

    </Window>

     

     

    Imports System.IO

    Class Window1

     

    'Delegate used to call back to the UI thread

    Delegate Sub CallBackToUIThread(ByVal e As FileSystemEventArgs)

     

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click

    ' Create a new FileSystemWatcher and set its properties.

    Dim watcher As New FileSystemWatcher()

    watcher.Path = TextBox1.Text

    ' Watch for changes in LastAccess and LastWrite times, and

    ' the renaming of files or directories.

    watcher.NotifyFilter = (NotifyFilters.LastAccess Or NotifyFilters.LastWrite Or NotifyFilters.FileName Or NotifyFilters.DirectoryName)

    ' Only watch text files.

    watcher.Filter = "*.txt"

     

    ' Add event handlers.

    AddHandler watcher.Changed, AddressOf OnChanged

    AddHandler watcher.Created, AddressOf OnChanged

    AddHandler watcher.Deleted, AddressOf OnChanged

    AddHandler watcher.Renamed, AddressOf OnRenamed

     

    ' Begin watching.

    watcher.EnableRaisingEvents = True

    End Sub

     

    ' Runs on the UI thread

    Public Sub NotifyUIThreadOfChange(ByVal e As FileSystemEventArgs)

    Label2.Content = "File: " & e.FullPath & " " & e.ChangeType

    End Sub

     

    ' FilesystemWatcher event handlers - run on an arbitrary thread

    Private Sub OnChanged(ByVal source As Object, ByVal e As FileSystemEventArgs)

    'Call back to the UI thread

    Me.Dispatcher.BeginInvoke( _

    System.Windows.Threading.DispatcherPriority.Normal, _

    New CallBackToUIThread(AddressOf NotifyUIThreadOfChange), _

    e)

    End Sub

     

    Private Sub OnRenamed(ByVal source As Object, ByVal e As RenamedEventArgs)

    'Call back to the UI thread

    Me.Dispatcher.BeginInvoke( _

    System.Windows.Threading.DispatcherPriority.Normal, _

    New CallBackToUIThread(AddressOf NotifyUIThreadOfChange), _

    e)

    End Sub

    End Class

     

     

    Wednesday, February 06, 2008 6:18 PM
  • Thank you very much, Mark. I appreciate the input, it's so much easier to understand from a working sample code.

    Again, thank you Smile

     

     

    Wednesday, February 06, 2008 11:58 PM
  • No problem. I hope this is enough to get you going - mark

    Thursday, February 07, 2008 5:37 AM