VB .NET -  Usando a classe FileSystemWatcher


O componente FileSystemWatcher permite a você monitorar um diretório , ou árvore de diretório disparando eventos e enviando notificações sobre qualquer alteração ocorrida no diretório ou em seus arquivos. Assim, quando um arquivo ou um subdiretório é criado , deletado ou renomeado ou quando o atributo de uma pasta é alterado o FileSystemWatcher entra em ação e funciona como um 'dedo duro' informando estas ocorrências via eventos.

 

Este componente pode ser muito útil em certas circunstâncias. Você pode usá-lo para monitorar se os seus dados estão sendo alterados por outra aplicação , ou se você precisas intervir devido a alguma alteração em um dos arquivos da sua aplicação. Ele funciona com o Windows NT, Windows 2000, Windows XP e Windows Server 2003.(Windows 98 e Windows Millennium ??? você ainda tem a coragem de usar...)

 

O componente pode ser usado para monitorar arquivos em uma máquina local, remota ou ambiente de rede local. Para usá-lo em sua aplicação você deve declarar o namespace System.IO.

 

Você pode criar um componente FileSystemWatcher de duas formas:  Via código ou arrastando o componente da ToolBox para o seu formulário Windows , Formulário Web ou outro designer do Visual Studio e efetuando a configuração. (Não há muita diferença de desempenho ao usar uma ou outra).

 

A seguir uma visão das propriedades do componente quando usado em um formulário windows:

 

 

Conceitos Básicos

 

O código e os exemplos aqui demonstrados foram feitos usando o Visual Basic 2005  no Windows XP. Vamos lá...

 

Etapas para Configurar o componente da Classe FileSystemWatcher:

  1. Criar uma instância do componente

  2. Configurar as propriedades e métodos necessários

  3. Criar um manipulador para os eventos do sistema de arquivos

Nota:

Antes de utilizar o componente você precisa iniciar ao menos as seguintes propriedades :

  • Path - É o nome do diretório que você deseja monitorar (você será notificado de qualquer alteração no interior do diretório mas não dos atributos do próprio diretório)
     

  • IncludeSubdirectories - Define se as notificações devem incluir subdiretórios, neste caso defina True.
     

  • Filter - Permite definir quais arquivos você deseja monitorar. Se você informar *.* receberá notificações sobre todos os arquivos do diretório. O valor padrão é uma string Null, que é a mesma coisa que *.*.
     

  • NotifyFilter - Especifica que tipo de modificação são anunciados por meio do evento Changed do componente, ou seja, quais eventos serão checados. Esta propriedade pode ser uma combinação de um ou mais enumerações de valores como: Attributes, CreationTime, DirectoryName, FileName, LastAccess, LastWrite, Security, e Size. O valor inicial é LastWrite ou Filename ou DirectoryName de forma que você não receberá notificações quando um atributo for alterado.

                    Enumeração NotifyFilter

Nome

Descrição

Attributes

O atributo do arquivo o diretório.

CreationTime

A hora que o arquivo ou pasta foi criada.

DirectoryName

O nome do diretório.

FileName

O nome do arquivo.

LastAccess

A data da última abertura do arquivo ou pasta

LastWrite

A data da última escrita no arquivo ou pasta.

Security

As configurações de segurança do arquivo ou pasta.

Size

O tamanho da pasta ou arquivo.

Outra propriedade importante é InternalBufferSize que obtém ou define o tamanho do buffer interno usado pelo componente. O tamanho padrão é 8k.

Para criar uma instância de um componente FileSystemWatcher você pode usar um dos seguintes métodos sobrecarregados do construtor da classe:


//Inicia uma nova instância da classe FileSystemWatcher
Private dedoDuro As FileSystemWatcher = New FileSystemWatcher


//Inicia uma nova instância com a propriedade Path
Private dedoDuro As FileSystemWatcher = New FileSystemWatcher("d:\")


//Inicia uma nova instância com as propriedades Path e Filters
Private dedoDuro As FileSystemWatcher = New FileSystemWatcher("d:\", "*.txt")

Volto a repetir, o componente não irá monitorar o diretório/arquivo definido até que a propriedade Path esteja definida, e que a propriedade EnableRaisingEvents seja definida como True.( O padrão é true).

Já deu para notar que o componente tem o potencial de receber uma enorme quantidade de eventos especialmente se você o tiver configurado para monitorar o tráfico de uma rede. Isto pode causar problemas pois o componente usa um buffer para gerenciar os eventos notificados que é passado para a API Win32 Application Programming Interface. Se ocorrerem muitas alterações em um curto período pode ocorrer um estouro de buffer fazendo com que o componente perca os dados das alterações realizadas e desta forma irá enviar notificações em branco fazendo que ocorra um exceção (O evento Error será disparado). Você pode alterar o tamanho do buffer via propriedade InternalBufferSize mas isto pode ter um custo alto demais dependendo dos valores usados. (Para evitar o estouro do buffer use as propriedades NotifyFilter e includeSubdirectories de forma filtrar as notificações.)

Podemos usar também o método WairtForChanged para esperar até que um evento especifico ocorra e então continuar com a execução da thread. Você pode definir usar o método WaitForChanged para monitorar um diretório até que a data do último acesso mude e então iniciar o processo que deseja realizar. Você define o tipo de mudança que deseja monitorar definindo o valor da enumeração WatcherChangeType. Os valores possíveis são:

Nome

Descrição

All   

A criação, exclusão, alteração ou mudança de nome de arquivo ou diretório.
Changed   A mudança de arquivo ou diretório. Os tipos de mudança incluem: mudança de tamanho, atributos, definição de segurança, última escrita e hora do último acesso.
Created A criação de arquivo ou diretório.
Deleted   A exclusão de arquivo ou diretório.
Renamed   A mudança de nome de arquivo ou diretório

O método WaitForChanged é síncrono e retorna um objeto do tipo WaitForChangedResult.  Se a aplicação não realiza qualquer operação a não ser esperar por mudanças no caminho definido ou se você monitora operações de uma thread secundária você pode escrever um código mais simples e eficiente usando o método WaitForChanged. Ele não retorna nada até que uma alteração em um arquivo seja detectada ou o tempo definido expire.

Esta classe contém informação específica sobre o tipo de mudança ocorrida no diretório. Você pode acessar a informação tal como: Name, OldName e TimedOut neste objeto para encontrar mais informações sobre a mudança.

O código abaixo ilustra um exemplo :

' Cria um novo componente FileSystemWatcher com valores 
Dim tmpFsw As New FileSystemWatcher(txtPath.Text, txtFilter.Text)

' Espera no máximo 10 s para qualquer evento de arquivo.
Dim resultado As WaitForChangedResult = tmpFsw.WaitForChanged(WatcherChangeTypes.All, 10000)

' Verifica se a operação excedeu o tempo definido
If resultado.TimedOut Then
   Console.WriteLine("Já se passaram 10 segundos sem evento")
Else
    Console.WriteLine("Evento : , resultado .Name, resultado .ChangeType.ToString())
End If

Manipulando os eventos do sistema de arquivos

O componente FileSystemWatcher dispara quatro eventos dependendo do tipo de alteração que ocorre no diretório/arquivo na monitoração. São eles:

Para acessar estes eventos você deve definir o tratamento que chama os métodos no seu código quando a mudança ocorre. Vejamos exemplos para cada um dos eventos:


dedoDuro.Changed += New FileSystemHandler(dedoDuro_Changed)
dedoDuro.Created += New FileSystemHandler(dedoDuro_Created)
dedoDuro.Deleted += New FileSystemHandler(dedoDuro_Deleted)
dedoDuro.Renamed += New RenamedEventHandler(dedoDuro_Renamed)
 

Algumas ocorrências como copiar ou mover um arquivo não correspondem diretamente a um evento que possa ser disparado. Entretanto estas ocorrências disparam outros eventos. Assim quando você copia um arquivo o sistema dispara um evento Create no diretório no qual o arquivo foi copiado mas não dispara qualquer evento no diretório original. Ao mover um arquivo o servidor dispara dois eventos: um evento Deleted no diretório origem seguido pelo evento Created no diretório destino.

Lembre-se no entanto que os eventos não serão disparados a menos que você defina a propriedade EnableRaisingEvents como sendo igual a True. Os eventos Created,Deleted e Changed recebem um objeto FileSystemEventArgs o qual expõe duas importantes propriedades: Name (o nome do arquivo que foi criado, deletado ou alterado) e FullPath (o caminho completo).

Nota: Para para desabilitar a monitoração basta definir EnableRaisingEvents como igual a False.

Um exemplo ideal para ser mostrado usando o componente FileSystemWatcher seria a criação de uma aplicação que possa estar sempre rodando e que possa ser iniciada assim que o sistema iniciar. No Visual Studio temos este recurso a disposição através da opção de criar um novo projeto do tipo Windows Service. Infelizmente o VB2005 não possui este tipo de projeto e eu ainda não tenho o VS.NET 2.0. Fico devendo, mas em breve estarei mostrando como usar este recurso.

Vamos remediar criando uma aplicação Windows forms que irá usar o FileSystemWatcher para monitorar um diretório. Na mesma aplicação iremos criar e excluir diretórios e arquivos para receber as notificações de monitoração. Para evitar o erro de chamada ilegal de Thread que acessa o controle em outra thread iremos definir a propriedade CheckForIllegalCrossThreadCalls como sendo igual a False. Isto só é valido para a .NET Framework 2.0.

 

Nota:  Ao tentar acessar um controle de um formulário a partir de uma nova Thread iniciada irá disparar a exceção

InvalidOperationException. Definindo CheckForIllegalCrossThreadCalls como sendo False estamos evitando a exceção.

 

Crie um novo projeto no VB2005 do tipo Windows Forms e inclua os controles no formulário padrão conforme figura abaixo:

 

Declare o namespace System.IO:

Imports System.io

 

 

No evento Click de cada um dos botões de comando teremos código que efetuará a ação de criar e deletar arquivo e diretório. O código é dado a seguir: (Não vou comentar o código pois além de óbvio não é o objetivo do artigo.)

 

  Private Sub btnCriaArquivo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCriaArquivo.Click
        Dim caminho As String = txtPath.Text
        Dim arquivo As String = caminho + "\" + txtArquivo.Text
        If Not (txtArquivo.Text = "") Then
            If Not File.Exists(arquivo) Then
                Try
                    File.Create(arquivo)
                Catch CrFile As Exception
                    MessageBox.Show(CrFile.Message, "Erro ao criar arquivo", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
                End Try
            Else
                MessageBox.Show(arquivo + " Arquivo já existe", "Erro na Criação do Arquivo", MessageBoxButtons.OK, MessageBoxIcon.Stop)
                txtArquivo.Focus()
                SendKeys.Send("{HOME}+{END}")
            End If
        Else
            MessageBox.Show("Informe o nome do arquivo", "Erro na Criação do Arquivo", MessageBoxButtons.OK, MessageBoxIcon.Information)
            txtArquivo.Focus()
        End If
    End Sub

    Private Sub btnCriarDiretorio_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCriarDiretorio.Click
        Dim caminho As String = txtPath.Text
        Dim dir As String = caminho + "\" + txtArquivo.Text
        If Not (txtArquivo.Text = "") Then
            If Not Directory.Exists(dir) Then
                Try
                    Directory.CreateDirectory(dir)
                Catch CrDir As Exception
                    MessageBox.Show(CrDir.Message, "Erro a criar diretório", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
                End Try
            Else
                MessageBox.Show(dir + " Diretório já existe", "Erro na Criação do Diretório", MessageBoxButtons.OK, MessageBoxIcon.Stop)
                txtArquivo.Focus()
                SendKeys.Send("{HOME}+{END}")
            End If
        Else
            MessageBox.Show("Informe o nome do diretório", "Erro na Criação do Diretório", MessageBoxButtons.OK, MessageBoxIcon.Information)
            txtArquivo.Focus()
        End If
    End Sub
 
    Private Sub btnDeletaArquivo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDeletaArquivo.Click
        Dim caminho As String = txtPath.Text
        Dim arquivo As String = caminho + "\" + txtArquivo.Text
        If Not (txtArquivo.Text = "") Then
            If File.Exists(arquivo) Then
                Try
                    If File.Exists(arquivo) Then
                        File.Delete(arquivo)
                    Else
                        MessageBox.Show("Arquivo não existe", "Erro na exclusão do Arquivo", MessageBoxButtons.OK, MessageBoxIcon.Stop)
                    End If
                Catch DelErr As Exception
                    MessageBox.Show(DelErr.Message, "Erro na exclusão do Arquivo", MessageBoxButtons.OK, MessageBoxIcon.Stop)
                End Try
            Else
                MessageBox.Show(arquivo + " Arquivo não existe", "Erro na exclusão do Arquivo", MessageBoxButtons.OK, MessageBoxIcon.Stop)
                txtArquivo.Focus()
                SendKeys.Send("{HOME}+{END}")
            End If
        Else
            MessageBox.Show("Informe o nome do arquivo", "Erro na exclusão do Arquivo", MessageBoxButtons.OK, MessageBoxIcon.Information)
            txtArquivo.Focus()
        End If
    End Sub

 

No início das declarações do formulário temos que definir que o FileSystemWatcher para monitorar eventos no arquivo de sistemas para isto declaramos:

Dim WithEvents dedoDuro As New FileSystemWatcher()

A seguir no evento Load do formulário vamos definir os valores para monitoração do componente:

 

Private Sub frmfsw_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

dedoDuro.Path = txtPath.Text
dedoDuro.IncludeSubdirectories = chkIncluiSubdiretorios.Checked
' Monitora subdiretorios

dedoDuro.Filter = txtFilter.Text ' Monitora somente arquivos DLL

' Inclui mudança em atributos a lista de mudanças que pode disparar eventos de notificação

dedoDuro.NotifyFilter = NotifyFilters.FileName Or NotifyFilters.DirectoryName Or NotifyFilters.CreationTime

' Permite a notificação de eventos
dedoDuro.EnableRaisingEvents =
True

CheckForIllegalCrossThreadCalls = False   ' <<<--- atenção sem esta declaração vai ocorrer uma exceção
End
Sub


Agora é só criar os tratadores para cada evento que desejamos monitorar , para facilitar a leitura eu reuni os eventos em um código onde estou tratando os 3 eventos:

 

Private Sub dedoDuro_All(ByVal sender As Object, ByVal e As FileSystemEventArgs) Handles dedoDuro.Changed, dedoDuro.Created, dedoDuro.Deleted

txtInfo.Text += "Arquivo alterado: " & e.FullPath & " - " & "Evento :  " &  e.ChangeType & "" & Microsoft.VisualBasic.Chr(10) & ""

End Sub

 

Mas se você desejar pode criar um para cada evento declarando cada um da seguinte forma:

 

Protected Sub dedoDuro_Created(ByVal sender As Object, ByVal e As FileSystemEventArgs)

txtInfo.Text += "ChangeType :: " + e.ChangeType.ToString + "" & Microsoft.VisualBasic.Chr(10) & "FullPath ::" + e.FullPath.ToString + "" & Microsoft.VisualBasic.Chr(10) & "" & Microsoft.VisualBasic.Chr(10) & ""

End Sub

Protected Sub dedoDuro_Changed(ByVal sender As Object, ByVal e As FileSystemEventArgs)

txtInfo.Text += "ChangeType :: " + e.ChangeType.ToString + "" & Microsoft.VisualBasic.Chr(10) & "FullPath ::" + e.FullPath.ToString + "" & Microsoft.VisualBasic.Chr(10) & "" & Microsoft.VisualBasic.Chr(10) & ""

End Sub

Protected Sub dedoDuro_Deleted(ByVal sender As Object, ByVal e As FileSystemEventArgs)

txtInfo.Text += "ChangeType :: " + e.ChangeType.ToString + "" & Microsoft.VisualBasic.Chr(10) & "FullPath ::" + e.FullPath.ToString + "" & Microsoft.VisualBasic.Chr(10) & "" & Microsoft.VisualBasic.Chr(10) & ""

End Sub

Protected Sub dedoDuro_Renamed(ByVal sender As Object, ByVal e As RenamedEventArgs)

txtInfo.Text += "ChangeType :: " + e.ChangeType.ToString + "" & Microsoft.VisualBasic.Chr(10) & "FullPath ::" + e.FullPath.ToString + "" & Microsoft.VisualBasic.Chr(10) & "Old FileName :: " + e.OldName.ToString + "" & Microsoft.VisualBasic.Chr(10) & "" & Microsoft.VisualBasic.Chr(10) & ""

End Sub

Executando o projeto e efetuando algumas operações temos:

Onde o número ao lado de Evento é recebido da propriedade ChangeType que indica qual o tipo de evento ocorreu:

1- Criar
2- Deletar
3- Alterar

Por isto gerenciamos os 3 eventos usando um tratador de evento único.

 

 

Enfim, o componente FileSystemWatcher , quando bem usado oferece recursos que antes exigiram um esforço considerável para obter o mesmo resultado. Use mas não abuse...

 

Pegue o projeto completo aqui:  dedoDuro.zip

 

Aguarde em breve mais artigos sobre os novos recursos VB.NET 2005. Até breve...
 

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??


Referências:


José Carlos Macoratti