A utilização e manipulação de ficheiros de texto (*. txt ou outra extensão) é muito comum embora existam bastantes alternativas disponíveis para guardar informação. No entanto, muitas vezes não é uma questão de alternativa (usar ou não usar) mas sim uma necessidade.
O .NET Framework 2.0 introduziu novos métodos que possibilitam um fácil e rápido acesso. Os métodos mais utilizados e mais eficazes para ler e escrever em ficheiros de texto são os namespaces IO (através da classe File, principalmente ReadAllLines, WriteAllText e AppendAllText e o StreamReader, StreamWriter) e o My (através da classe My.Computer.FileSystem, principalmente ReadAllText e WriteAllText).
Obviamente que existem diferenças entre eles, mas as principais diferenças são que o StreamReader e StreamWriter lê/escreve linha a linha e os outros métodos lêem/escrevem tudo de uma só vez, ou seja, utilizam mais recursos da aplicação. Por exemplo o StreamReader lê linha a linha enquanto o método ReadAllLines lê toda a informação para uma variável (array).
É claro que isto apenas se consegue notar em grande número de informação, mas para verem as principais diferenças e desempenhos vejam a seguinte tabela com os tempos de acesso em milisegundos.
Tempo Médio de Leitura
| Método/Nº Linhas | 10.000 | 100.000 | 1.000.000 |
| IO.File.ReadAllLines | 4,0 | 71,2 | 957,1 |
| IO.StreamReader | 2,6 | 33,9 | 313,1 |
| My.Computer.FileSystem.ReadAllText | 6,7 | 60,5 | 991,2 |
Tempo Médio de Leitura
| Método/Nº Linhas | 10.000 | 100.000 | 1.000.000 |
| IO.File.WriteAllText | 15,1 | 123,8 | 1.426,4 |
| IO.StreamWriter | 13,8 | 115,5 | 1.922,8 |
| My.Computer.FileSystem.WriteAllText | 15,3 | 116,8 | 1.389,1 |
Nota: Tempo em milisegundos com a média de 10 leitura e texto por linha de 13 caracteres
Para mostrar um pouco a utilização dos diferentes métodos serão mostrados dois exemplos da leitura e escrita. Como o IO.File e o My.Computer.FileSystem são bastante semelhantes, onde a principal diferença é que a classe IO.File tem um método que lê todas as linhas sem necessitar de utilizar uma função de Split() para as colocar num array, irá ser mostrado apenas este. O My.Computer.FileSystem tem ainda um parametro adicionar que permite adicionar, ou não, texto a um arquivo já existente (append).
Exemplo de leitura de um ficheiro através do classe IO.File (ReadAllLines)
Dim total As Integer = 0
Dim fileName As String = "c:\teste.txt"
' Guarda num Array linha a linha
Dim lines() As String = IO.File.ReadAllLines(fileName)
' Ciclo que irá mostrar linha a linha
For Each line As String In lines
Debug.WriteLine(line.ToString)
total += 1
Next
' Mostra o total de linha lidas
Debug.WriteLine("Total de linhas:" & total.ToString)
Exemplo de escrita de um ficheiro através do classe IO.File (WriteAllText)
Dim total As Integer = 0
Dim fileName As String = "c:\teste.txt"
' Cria um novo StringBuilder
Dim sb As New System.Text.StringBuilder
' Ciclo que irá inserir construir o ficheiro a gravar
For x As Integer = 0 To 9999
' Cria uma linha com o formato "Linha nº 0001"
sb.AppendLine("Linha nº " & x.ToString.PadLeft(4, "0"))
total += 1
Next
' Grava o ficheiro no disco
IO.File.WriteAllText(fileName, sb.ToString)
' Mostra o resultado
Debug.WriteLine(String.Format("Ficheiro {0} com {1} registos", fileName, total))
Exemplo de leitura de um ficheiro através do classe IO (StreamReader)
Dim total As Integer = 0
Dim fileName As String = "c:\teste.txt"
' Ciclo que irá mostrar linha a linha
Using reader As New IO.StreamReader(fileName)
' Ciclo até ao fim do ficheiro
While Not reader.EndOfStream()
' Mostra o resultado linha a linha
Dim input As String = reader.ReadLine()
Debug.WriteLine(input)
total += 1
End While
End Using
' Mostra o total de linha lidas
Debug.WriteLine("Total de linhas:" & total.ToString)
Exemplo de escrita de um ficheiro através do classe IO (StreamWriter)
Dim total As Integer = 0
Dim fileName As String = "c:\teste.txt"
' Cria um nova instância de um StreamWriter
Using writer As New IO.StreamWriter(fileName)
' Inicia o ciclo que irá gravar no ficheiro
For x As Integer = 0 To 9999
writer.WriteLine("Linha nº " & x.ToString.PadLeft(4, "0"))
total += 1
Next
End Using
' Mostra o resultado
Debug.WriteLine(String.Format("Ficheiro {0} com {1} registos", fileName, total))
Estes são pequenos exemplos da estrutura tipo, embora existam ainda em cada classe, uma serie de métodos que podem ser utilizados. Pelos exemplos mostrados e pelo desempenho de cada um, deverá ser utilizado aquele que mais se ajustar a cada situação. Apenas em ficheiros grandes deverá haver um mais cuidado na escolha do método a utilizar.
Todos utilizam por defeito uma codificação de UTF-8 mas por vezes é necessário ajustar para certos ficheiros. A opção System.Text.Encoding.Default é muitas vezes a melhor solução para problemas de codificação.
PS: Como sempre, qualquer dúvida, comentário ou correcção ao artigo é sempre bem vinda!
DateTimePicker – Sem Selecção (nulo)
O DateTimePicker é um excelente controlo
para seleccionar e mostrar datas ou horas. Não necessita de validação, é fácil de
seleccionar ou incrementar, permite uma introdução directa e é visualmente bastante
agradável. Mas, e como não há regra sem excepção, tem um grande defeito: não tem
uma possibilidade directa simples de indicar que não existe escolha, ou seja, colocar
uma selecção a nulo.
Quando estamos a inserir ou a mostrar
dados, o controlo DateTimePicker indica sempre uma data/hora. Isto obriga a uma
adaptação por parte do programador, criando um conjunto de soluções alternativas,
para indicar que não existe nenhuma selecção. Há quem coloque uma data (tipo 01-01-1900),
há quem coloque uma checkbox para habilitar/desabilitar
o controlo e há quem simplesmente não o utilize.
A melhor solução, e é obviamente a
minha opinião, é mostrar sem nada (em branco), indicando que não tem selecção. Para
fazer isto é necessário o seguinte:
DATA
Para mostrar a data em branco é necessário
definir o controlo DateTimePicker com o formato
Custom (personalizado) e indicar um espaço em branco (“ “)
Me.DateTimePicker1.Format = DateTimePickerFormat.Custom
Me.DateTimePicker1.CustomFormat = " "
Depois, e quando o utilizador seleccionar
uma data, repomos o formato normal
Private Const EmptySpace
As String = " "
'
Detecta que foi alterado um valor(data) no DateTimePicker
Private Sub DateTimePicker1_ValueChanged(ByVal sender As
Object, ByVal
e As System.EventArgs) _
Handles DateTimePicker1.ValueChanged
' Verifica se o texto
é um espaço em branco
If DateTimePicker1.Text
= EmptySpace Then
' Define
o formato como Shot (formato DD-MM-YYYY) e dá um
' enter
para fechar (CloseUp) da janela que está aberta
Me.DateTimePicker1.Format = DateTimePickerFormat.Short
SendKeys.Send("{ENTER}")
End If
End
Sub
HORA
Como no caso anterior é necessário
definir o controlo DateTimePicker com o formato
Custom (personalizado) e indicar um espaço em branco (“ “). O problema é
que neste caso o utilizador inicia a alteração da hora/ através dos botões UpDown
uma vez que não consegue introduzir manualmente (não permite o focus uma vez que
não tem valores). Para detectarmos que o utilizador clicou com o rato num botão
de incremento/decremento é necessário fazer o override ao WndProc, uma vez que os
eventos MouseDown ou MouseUp não detectam os botões.
O melhor é criar um controlo personalizado
em que se define este processo. No exemplo o controlo tem o nome de myDateTimePicker.
Me.MyDateTimePicker1.ShowUpDown
= True
Me.MyDateTimePicker1.Format
= DateTimePickerFormat.Custom
Me.MyDateTimePicker1.CustomFormat = " ""
O código para o controlo personalizado,
fazendo o override ao WndProc é o seguinte:
Public Class
myDateTimePicker
Inherits DateTimePicker
Const EmptySpace As
String = " "
'
Constante que verifica se foi utilizado o botão esquerdo do rato
Private Const WM_LBUTTONDOWN = &H201
' Overrides ao WndProc
Protected Overrides
Sub WndProc(ByRef m
As System.Windows.Forms.Message)
' Caso seja o botão esquerdo e o texto esteja com um espaço em branco
If m.WParam = WM_LBUTTONDOWN Then
If Me.Text
= EmptySpace Then
Me.Format = DateTimePickerFormat.Time
End If
End If
MyBase.WndProc(m)
End
Sub
End
Class
Para terminar,
pode-se ainda apagar a formatação quando o utilizador pressionar a tecla DEL, nos
eventos KeyUp, KeyDown ou KeyPress.
Private Sub MyDateTimePicker1_KeyDown(ByVal sender As
Object, ByVal
e As System.Windows.Forms.KeyEventArgs) _
Handles
MyDateTimePicker1.KeyDown
' Caso a tecla seja a
DELETE
If e.KeyCode = Keys.Delete Then
Me.MyDateTimePicker1.Format = DateTimePickerFormat.Custom
Me.
MyDateTimePicker1.CustomFormat = " "
End If
End
Sub
Este é um exemplo
de como melhorar a utilização deste controlo tornando-o quase “perfeito”.
StopWatch – Medindo
o Tempo de Execução
Existem formas diferentes de construir
código de modo a executar algo. Umas mais rápidas, umas mais simples ou mesmo mais
versáteis. As operações mais demoradas estão normalmente relacionadas com a gestão
de dados (bases de dados, ficheiros, etc.) mas as pequenas optimizações em conjunto,
podem melhorar a performance da aplicação. Nos casos de duvida, em que qual é o
método mais rápido para fazer algo, o melhor mesmo é testar.
O StopWatch permite medir de uma forma
bastante simples o tempo de execução, utilizando muito pouco código. O método para
testar o tempo de execução de diferentes métodos é sempre o mesmo, ou seja:
.
' Cria-se uma nova instância
Dim stopWatch
As New Stopwatch
' Inicia-se a contagem
stopWatch.Start()
' Faz-se um ciclo com várias repetições
para verificar o tempo que demorou
' a executar. Caso o código já seja
demorado, não é necessário ciclo
For x As Integer
= 0 To 10000
' código a testar
Next
' Para-se a contagem
stopWatch.Stop()
' Mostra-se o tempo que demorou a executar
em milisegundos
Debug.WriteLine(stopWatch.ElapsedMilliseconds.ToString)
Depois repetem-se
algumas vezes, coloca-se o código de comparação a testar e comparam-se os resultados
obtidos. Pode ser necessário ajustar o número de repetições no ciclo de maneira
a diferenciar melhor os resultados.
Podem-se ainda
melhorar a visualização, especialmente em tarefas mais demoradas, utilizando o TimeSpan.
Depois de parar o Stopwatch, através do método Stop(), pode-se fazer o seguinte:
' Coloca o tempo total na
variável do tipo TimeSpan.
Dim ts As
TimeSpan = stopWatch.Elapsed
' Mostra a informação dividida
em Horas, Minutos, Segundo e Milisegundos.
Dim totalTime As
String = _
String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10)
Debug.WriteLine(totalTime)
O exemplo mostrar como aplicar e executar
testes de velocidade de código, com o objectivo de melhorar a performance da aplicação.
PS: Como sempre, qualquer dúvida,
comentário ou correcção ao artigo é sempre bem
vinda!
A maioria das aplicações tem algumas
opções que são importantes guardar de modo a serem utilizadas durante e em próximas
execuções. Estas configurações ou settings
são bastante utilizadas e importantes, sendo os métodos mais usados no Visual Basic
6 a criação de ficheiros INI’s e a utilização do
Registry do Windows.
Tamanho e/ou localização de uma janela,
data do último acesso, impressora utilizada, nome do último
login, são apenas alguns exemplos do que é “normal” e necessário guardar.
Pode-se obviamente também guardar esta informação numa base de dados, mas se necessitarmos
de guardar o endereço da base de dados ou o nome do servidor? É necessário recorrer
a outros métodos.
Actualmente, e embora também se utilize
também no Visual Basic 6, os ficheiros XML estão a liderar os ficheiros de
settings fazendo parte de várias classes do Visual Basic.NET o que simplifica
bastante este processo. Existe ainda um processo de criação de
settings embebido na aplicação.
Existem grandes defensores dos ficheiros
INI’s e grandes defensores de guardar a informação no
Registry do Windows, no entanto, e é a minha opinião pessoal, é que ambos
os métodos são válidos e devem ser utilizados para diferentes fins.
Ambos têm vantagens e desvantagens,
por exemplo: os ficheiros INI’s são fáceis analisar, não necessitam de acesso de
administrador para os modificar/criar, mas também são fáceis de modificar não oferecendo
por isso nenhuma segurança. Por outro lado a utilização do
Registry do Windows necessita de acesso de administrador (o que em muitos
sistemas é impensável), é mais delicada a sua utilização pois podem-se apagar/modificar
inadvertidamente outras informações importantes no
Registry e é mais difícil de analisar a informação sendo necessário recorrer
a um editor. No entanto é bastante mais seguro.
Podem ser utilizados os dois sistemas,
de acordo com a interpretação de cada programador, por exemplo, guardar informações
mais importantes (como passwords) no
Registry e informações menos importantes/relevantes
em ficheiros INI.
Ficheiros INI’s
Os ficheiros INI’s não são mais do
que ficheiros de texto em que se guardam informações devidamente agrupadas o que
permitem uma boa organização e fácil acesso através de API’s. A estrutura de um
ficheiro é:
[Secção1]
Chave1=Texto1
Chave2=Texto2
[Secção2]
Chave1=Texto1
Chave2=Texto2
Este é um exemplo simples de utilização:
Option Explicit
' Declaração de API's
Private Declare Function WritePrivateProfileString Lib
"kernel32" Alias "WritePrivateProfileStringA" _
(ByVal
lpApplicationName As String,
ByVal lpKeyName As Any, _
ByVal lpString As Any,
ByVal lpFileName As String)
As Long
Private Declare Function GetPrivateProfileString Lib
"kernel32" Alias "GetPrivateProfileStringA" _
(ByVal
lpApplicationName As String,
ByVal lpKeyName As Any, _
ByVal lpDefault As String,
ByVal lpReturnedString As String, _
ByVal nSize As Long,
ByVal lpFileName As String)
As Long
' Escreve no ficheiro
INI através de API ignorando o erro
Public Function INIWrite(sSection As String,
sKeyName As String, sNewString
As String, sINIFileName As String) As Boolean
Call
WritePrivateProfileString(sSection, sKeyName, sNewString, sINIFileName)
INIWrite = (Err.Number = 0)
End Function
' Lê no ficheiro INI
através de API ignorando o erro
Public Function INIRead(sSection As String,
sKeyName As String, sINIFileName
As String) As String
Dim
sRet As String
sRet = String(255, Chr(0))
INIRead = Left(sRet, GetPrivateProfileString(sSection,
ByVal sKeyName, "", sRet, Len(sRet), sINIFileName))
End Function
' No evento Form Load
Private Sub Form_Load()
' Escreve uma informação no ficheiro INI. Caso exista modifica
a existente,
‘ caso não exista cria uma nova chave e o respectivo texto
Call INIWrite("GERAL", "utilizador", "jpaulino", App.Path
& "\mySettings.ini")
End Sub
' Botão para mostrar
o resultado
Private Sub btnRead_Click()
Dim
result As String
' Lê a chave no ficheiro INI para uma variável
e mostra a informação
result = INIRead("GERAL", "utilizador", App.Path &
"\mySettings.ini")
MsgBox result, vbInformation
End Sub
O resultado no evento Form Load será
a criação de um ficheiro ini (mySettings.ini) com a seguinte estrutura:
[GERAL]
utilizador=jpaulino
Windows Registry
A utilização do Windows Registry é
ainda mais fácil, não sendo necessário a utilização de API’s. A localização onde
é guardada a informação é fixa e é a seguinte:
HKEY_CURRENT_USER
\Software
\VB and VBA Program Settings
\Applicação
\ Secção
Chave = Texto
Este é um exemplo simples de utilização:
Option Explicit
' Escreve a informação
no Registry
Private Sub bntWrite_Click()
SaveSetting "Teste", "GERAL",
"Página", "http://vbtuga.blogspot.com/"
End Sub
' Botão para mostrar
o resultado
Private Sub btnRead_Click()
Dim
result As String
result = GetSetting("Teste",
"GERAL", "Página", "")
MsgBox result, vbInformation
End Sub
Como já referido anteriormente, existem
actualmente outros métodos para guardar informação relevante à aplicação, mas estas
duas formas são ainda utilizadas. Muitos programadores de Visual Basic 6 que migraram
para Visual Basic.Net ainda utilizam estes métodos (embora não aconselhável).
Este artigo, embora bastante simples,
pretende apenas mostrar algumas alternativas fáceis e eficazes de guardar informações
de configuração da aplicação e como as utilizar de uma forma geral.
PS: Como sempre, qualquer dúvida, comentário ou correcção ao artigo é sempre bem vinda!