Desenvolvimento - C#

Monitorando Arquivos e Diretórios com FileSystemWatcher

Neste artigo veremos os conceitos, propriedades, eventos e o uso prático de um componente do .NET chamado FileSystemWatcher. Com ele, conseguimos monitorar arquivos e diretórios de um ou mais computadores.

por Wellington Balbo de Camargo



Olá pessoal, neste artigo irei criar uma aplicação em Windows Forms para mostrar como monitorar arquivos e diretórios usando o componente do .NET Framework FileSystemWatcher.

Faço este artigo com base nas videoaulas de Luiz Maia, do Portal Linha de Código, mediante autorização do mesmo. Seguindo as videoaulas, de início irei falar dos conceitos, propriedades e atributos do FileSystemWatcher e logo depois farei um exemplo prático em Windows Forms demonstrando o uso do mesmo. Acompanhem:

O FileSystemWatcher é um componente nativo do .NET Framework que tem como função monitorar diretórios e árvores de diretórios disparando eventos e enviando notificações sobre quaisquer alterações que venham a ocorrer nestes diretórios. Desta forma, quando algum diretório ou subdiretório é criado, alterado, renomeado ou excluído, o FileSystemWatcher acaba funcionando como um “dedo duro” informando ao usuário estas ocorrências. Esse componente pode ser usado para monitorar um ou mais computadores locais, remotos ou em rede.

- As principais propriedades do FileSystemWatcher são:

- EnableRaisingEvents – o diretório só será monitorado se esta propriedade estiver “setada” como true.

- Path – será a pasta que será monitorada pelo nosso componente.

- IncludeSubDirectories – valor booleano, se deixarmos true, ele irá monitorar os subdiretórios.

- Filter – é a extensão do arquivo que nosso componente irá filtrar (.txt por exemplo). Se deixarmos o valor padrão (*.*), ele irá filtrar todos os arquivos.

- NotifyFilter – serão os atributos (ou gatilhos) que, ao serem alterados, farão com que nosso componente dispare um determinado evento que irá notificar o usuário que determinado arquivo ou diretório foi alterado.

- Atributos para os Filtros de Notificação (NotifyFilter):

- Attributes – o atributo do arquivo ou diretório.

- CreationTime – a hora que o arquivo ou diretório foi criado.

- DirectoryName – o nome do diretório.

- FileName – o nome do arquivo.

- LastAccess – a data da última abertura do arquivo ou diretório.

- LastWrite – a data da última escrita no arquivo ou diretório.

- Security – as configurações de segurança do arquivo ou diretório.

- Size – O tamanho do arquivo ou diretório.

- Sobrecarga de Métodos:

Temos três sobrecargas da classe do FileSystemWatcher, que são:

1ª - Inicia uma nova instância da classe FileSystemWatcher:

FileSystemWatcher fsw = new FileSystemWatcher();

2ª - Inicia uma nova instância da classe FileSystemWatcher passando como parâmetro a propriedade Path:

FileSystemWatcher fsw = new FileSystemWatcher(@"C:\pasta");

3ª - Inicia uma nova instância da classe FileSystemWatcher passando como parâmetro as propriedades Path e Filter:

FileSystemWatcher fsw = new FileSystemWatcher(@"C:\pasta", "*.*");

- Eventos do FileSystemWatcher:

fsw.Changed += new FileSystemEventHandler(fsw_Changed);

fsw.Created += new FileSystemEventHandler(fsw_Created);

fsw.Deleted += new FileSystemEventHandler(fsw_Deleted);

fsw.Renamed += new RenamedEventHandler(fsw_Renamed);

Estes serão os quatro eventos que usaremos em nosso exemplo.

- Eventos Disparados:

- Changed – disparado sempre que mudanças são feitas no tamanho, atributos, última escrita, último acesso ou permissões de segurança NTFS de um arquivo ou diretório.

- Created – disparado sempre que um diretório é criado.

- Deleted – disparado sempre que um diretório é excluído.

- Renamed – disparado sempre que o nome do diretório ou arquivo é alterado.

- Erro: Estouro de Buffer!

Nosso componente pode receber um grande número de eventos simultâneos, principalmente se o mesmo estiver configurado para rodar em uma rede. Isso pode nos trazer alguns problemas: como o componente usa o Buffer para gerenciar esses eventos, se em um curto período de tempo o Buffer receber muitos eventos, pode ocorrer o erro de Estouro de Buffer, fazendo com que nosso componente perca os dados das alterações realizadas nas pastas e nos envie notificações em branco.

Para que evitemos este erro, podemos usar o método WaitForChangedResult, que espera que um evento específico ocorra para que possa continuar a execução. Podemos definir este evento de acordo com os atributos especificados abaixo:

- 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.

Então, se algum destes atributos acima for alterado, é disparado nosso evento.

Dito tudo isto, vamos à prática.

Abra o Visual Studio, crie uma nova aplicação Windows Form e dê o nome de ExemploFileSystemWatcher.

No form que é criado, arraste os controles e adicione o componente FileSystemWatcher para que seu form fique da seguinte forma:

Os ID’s dos controles, na ordem que está no form, são esses:

lblPasta, txtPasta, lblFiltro, txtFiltro, lblNotificacoes, txtNotificacoes, cboHabilitaEventos e cboIncluirSubDiretorios (CheckBoxes), gboFSW (GroupBox), lblArquivoDiretorio, txtArquivoDiretorio, btnCriaArquivo, btnCriaDiretorio e btnDeletaArquivo.

Lembrando que esses são os ID’s que eu usei, nada os impede de usar a nomenclatura que mais for favorável e intuitivo a vocês.

Como vocês podem ver na imagem acima, no txtPasta deixei um caminho padrão para usarmos neste exemplo e no txtFiltro, deixei como padrão o *.*, que como já dito anteriormente, indica que todos os tipos de arquivos serão monitorados.

Agora vamos ao Page_Load da página para fazer com que tudo isto funcione. Para isso, dê dois cliques em cima do form e insira o seguinte código (está comentado para melhor entendimento):

Antes, não se esqueça do using:

using System.IO;

Agora sim:

private void Form1_Load(object sender, EventArgs e)

        {

            try

            {

                //caminho da pasta que o FileSystemWatcher irá monitorar (atribuo o valor do TextBox)

                fsw.Path = txtPasta.Text;

                //tipos de filtro que o FileSystemWatcher irá considerar

                fsw.Filter = txtFiltro.Text;

                //lista de atributos que irão disparar eventos

                fsw.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.CreationTime;

                //permitir a monitoração (sem o valor estar como true, é impossível do FSW monitorar os arquivos)

                fsw.EnableRaisingEvents = cboHabilitaEventos.Checked;

                //monitorar subdiretórios (atribuo o valor do checkbox. Como está checado, assume o valor true)

                fsw.IncludeSubdirectories = cboIncluirSubDiretorios.Checked;

                //uso a propriedade abaixo como false para evitar o erro de chamada ilegal de thread, que pode

                //acessar um controle em outra thread aconteça. Se isso acontecer, será disparado uma exceção.

                CheckForIllegalCrossThreadCalls = false;

                //uso do WaitForChangedResults (mostrado no artigo) para Windows Services e Console Application"s

                //instancio a classe WaitForChangedResults, passando o FSW com o método WaitForChanged e dois

                //parâmetros: o tipo de modificações que ele irá aguardar, que no caso são todas, e o tempo de

                //espera para que sejam disparados estes eventos, que será de 10 segundos.

                WaitForChangedResult wcr = fsw.WaitForChanged(WatcherChangeTypes.All, 10000);

                //faço uma verificação, se der Timeout (passar o tempo esperado de 10 segundos),

                //disparo um aviso. Se não der Timeout, exibo o Nome do Evento e o Tipo dele.

                if (wcr.TimedOut)

                {

                    Console.WriteLine("Já se passaram 10 minutos do evento");

                }

                else

                {

                    Console.WriteLine("Evento: " + wcr.Name, wcr.ChangeType.ToString());

                }

            }

            catch (Exception)

            {

                throw;

            }

        }

PS: Não se esqueça de sempre usar Try/Catch em seu código.

Agora voltemos ao form. Abra a ToolBox (CTRL + W + X), arraste o componente FileSystemWatcher para seu form e deixe o nome padrão dele, fileSystemWatcher1. Ainda nas Propriedades, vá aos eventos do FSW e dê dois cliques em cima de cada evento para chamar o respectivo método.

Vamos codificar os métodos chamados por estes eventos.

No Changed, faça o seguinte:

private void fileSystemWatcher1_Changed(object sender, FileSystemEventArgs e)

        {

            txtNotificacoes.Text += String.Format("Alteração: {0} {1}", e.FullPath, Environment.NewLine);

            txtNotificacoes.Text += String.Format("Nome: {0} {1}", e.Name, Environment.NewLine);

            txtNotificacoes.Text += String.Format("Evento: {0} {1}", e.ChangeType, Environment.NewLine);

            txtNotificacoes.Text += String.Format("----------------------- {0}", Environment.NewLine);

        }

No Created, faça o seguinte:

private void fileSystemWatcher1_Created(object sender, FileSystemEventArgs e)

        {

            txtNotificacoes.Text += String.Format("Criação: {0} {1}", e.FullPath, Environment.NewLine);

            txtNotificacoes.Text += String.Format("Nome: {0} {1}", e.Name, Environment.NewLine);

            txtNotificacoes.Text += String.Format("Evento: {0} {1}", e.ChangeType, Environment.NewLine);

            txtNotificacoes.Text += String.Format("----------------------- {0}", Environment.NewLine);

        }

No Deleted, faça o seguinte:

private void fileSystemWatcher1_Deleted(object sender, FileSystemEventArgs e)

        {

            txtNotificacoes.Text += String.Format("Exclusão: {0}, {1}", e.FullPath, Environment.NewLine);

            txtNotificacoes.Text += String.Format("Nome: {0} {1}", e.Name, Environment.NewLine);

            txtNotificacoes.Text += String.Format("Evento: {0} {1}", e.ChangeType, Environment.NewLine);

            txtNotificacoes.Text += String.Format("----------------------- {0}", Environment.NewLine);

        }

No Renamed, faça o seguinte:

private void fileSystemWatcher1_Renamed(object sender, RenamedEventArgs e)

        {

            txtNotificacoes.Text += String.Format("Alteração de Nome: {0} {1}", e.FullPath, Environment.NewLine);

            txtNotificacoes.Text += String.Format("Nome: {0} {1}", e.Name, Environment.NewLine);

            txtNotificacoes.Text += String.Format("Evento: {0} {1}", e.ChangeType, Environment.NewLine);

            txtNotificacoes.Text += String.Format("----------------------- {0}", Environment.NewLine);

        }

Pronto, eventos criados.

Agora vamos criar os métodos responsáveis pelos botões CriaArquivo, CriaDiretório e DeletaArquivo. Crie o método CriaArquivo:

private void CriaArquivo()

        {

            try

            {

                //crio a string caminho, que recebe o caminho do txtPasta

                //crio a string arquivo, a concateno com \\ e com o que for digitado no txtArquivoDiretorio

                string caminho = txtPasta.Text;

                string arquivo = caminho + "\\" + txtArquivoDiretorio.Text;

                //faço uma verificação, se foi digitado algo no txtArquivoDiretorio

                if (txtArquivoDiretorio.Text != string.Empty)

                {

                    //faço outra verificação, se o arquivo da variável arquivo não existe

                    if (!File.Exists(arquivo))

                    {

                        try

                        {

                            //caso não exista, uso o File.Create

                            File.Create(arquivo);

                        }

                        catch (IOException er)

                        {

                            //se der erro entro neste catch customizado

                            Console.WriteLine("Erro ao criar o arquivo: " + er.Message);

                        }

                    }

                    //se o arquivo da variável arquivo existir, entro no else e exibo uma mensagem ao usuário

                    else

                    {

                        Console.WriteLine("Arquivo já existe!");

                    }

                }

            }

            catch (Exception)

            {

               

                throw;

            }

        }

O CriaDiretório e DeletaArquivo são parecidos, o que muda são as mensagens de aviso apenas e o tipo da classe, que são File e Directory, dependendo do método (no final do artigo, disponibilizarei o código-fonte deste artigo). Agora abra a unidade C: da sua máquina e crie a pasta que o FSW usará para monitorar os arquivos. No meu caso, aqui é a exemploFSW.

Agora vamos voltar ao form e dar dois cliques em cada um dos botões do form, para chamar os métodos criados anteriormente. Faça desta forma:

private void btnCriaArquivo_Click(object sender, EventArgs e)

        {

            CriaArquivo();

        }

        private void btnCriaDiretorio_Click(object sender, EventArgs e)

        {

            CriaDiretorio();

        }

        private void btnDeletaArquivo_Click(object sender, EventArgs e)

        {

            DeletaArquivo();

        }

Pronto, agora compile seu projeto e experimente criar, alterar e excluir arquivos e pastas e veja as notificações que aparecem.

Assim finalizo nosso artigo

Para quem se interessar, disponibilizo o código fonte desse projeto aqui.

Créditos à Luiz Maia, que fez as videoaulas e ao Linha de Código, por onde pude baixá-las (mediante assinatura), estudá-las e posteriormente fazer este artigo.

Quaisquer dúvidas, mandem emails para wellingtonbalbo@gmail.com ou deixem nos comentários deste artigo que responderei o mais breve possível.

Até o próximo artigo!

Wellington Balbo de Camargo

Wellington Balbo de Camargo - Desenvolvedor Web/Desktop em .NET, com pouco mais de 1 ano de experiência na área. Cursando o 4º Semestre de Análise de Sistemas, atualmente estuda para melhorar os conhecimentos em SQL Server e ASP.NET, buscando em um futuro próximo as certificações Microsoft nessas áreas. Mantém um blog com dicas e artigos para iniciantes em .NET no endereço http://programandodotnet.wordpress.com e nas horas vagas procura ajudar e aprender com o pessoal do Fórum daMSDN. Atualmente é editor de artigos da Linha de Código.