Detectar alterações no Sistema Operativo
As aplicações estão muitas vezes dependentes de informações do sistema operativo para trabalharem correctamente. Cores utilizadas, fontes utilizadas, utilizador actual do sistema, data/hora, etc, são algumas das coisas que podem influenciar o funcionamento da aplicação. Por exemplo, se uma aplicação faz algo a uma determinada hora e essa hora é modificada, essa tarefa pode não ser efectuada.
Caso se necessite, ou seja, caso exista uma dependência da aplicação de informações do sistema operativo, é possível detectar alterações e tomar decisões.
O seguinte exemplo mostra como detectar algumas alterações no sistema operativo, mostrando uma mensagem informativa. Existem mais condições e eventos que se podem monitorizar, mas para este exemplo apenas serão mostrados alguns.
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' Cria eventos para algumas alterações no sistema
' (data/hora, sessão e preferências de visualização do utilizador)
AddHandler Microsoft.Win32.SystemEvents.TimeChanged, _
AddressOf TimeChangedEvent
AddHandler Microsoft.Win32.SystemEvents.SessionSwitch, _
AddressOf SessionSwitchEvent
AddHandler Microsoft.Win32.SystemEvents.UserPreferenceChanged, _
AddressOf UserPreferenceChangedEvent
End Sub
' Verifica alterações na data/hora do sistema
Sub TimeChangedEvent(ByVal sender As Object, ByVal e As System.EventArgs)
MessageBox.Show("Foi alterada a data/hora do sistema")
End Sub
' Verifica alterações na sessão (logins)
Sub SessionSwitchEvent(ByVal sender As Object, ByVal e As Microsoft.Win32.SessionSwitchEventArgs)
' Verifica qual a alteração efectuada
Select Case e.Reason
Case Microsoft.Win32.SessionSwitchReason.SessionLock
MessageBox.Show("O sistema foi bloqueado")
Case Microsoft.Win32.SessionSwitchReason.SessionUnlock
MessageBox.Show("O sistema foi desbloqueado")
Case Microsoft.Win32.SessionSwitchReason.SessionLogon
MessageBox.Show("Efectuado um novo login no sistema")
Case Microsoft.Win32.SessionSwitchReason.SessionLogoff
MessageBox.Show("Efectuado um novo logout no sistema")
End Select
End Sub
' Verifica alterações às preferências do utilizador
Sub UserPreferenceChangedEvent(ByVal sender As Object, ByVal e As Microsoft.Win32.UserPreferenceChangedEventArgs)
' Verifica qual a categoria alterada
Select Case e.Category
Case Microsoft.Win32.UserPreferenceCategory.Color
MessageBox.Show("Foram alteradas as cores do sistema")
Case Microsoft.Win32.UserPreferenceCategory.Screensaver
MessageBox.Show("Foi alterado o screensaver do sistema")
Case Microsoft.Win32.UserPreferenceCategory.Window
MessageBox.Show("Foram alteradas dimensões ou características do sistema")
End Select
End Sub
Implementando um FIFO
O namespace System.Collections.Generic possui uma classe que permite implementar, de uma forma muito simples, um FIFO (first-in, first-out). Este método é muito útil em diversas aplicações (ex: produtos numa linha de produção, buffers, etc), mas também para manter os últimos registos de algo.
Para mostrar a utilização desta colecção, Queue, será mostrada criada uma pequena lista de itens e depois será removido um e inserido outro. Um exemplo simples mas certamente muito útil.
''' <summary>
''' Mostra os itens que estão na lista
''' </summary>
Sub showList(ByVal fifoList As Queue(Of String))
For x As Byte = 0 To fifoList.Count – 1
Debug.WriteLine(fifoList(x).ToString)
Next
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim fifo As New Queue(Of String)
' Adiciona itens ao fifo através do método Enqueue()
For x As Byte = 0 To 9
fifo.Enqueue("Item " & x)
Next
' Mostra os dados actuais
showList(fifo)
Stop
' ------------------------------------------
' OUTPUT (Immediate Window):
' ------------------------------------------
' Item 0
' Item 1
' Item 2
' Item 3
' Item 4
' Item 5
' Item 6
' Item 7
' Item 8
' Item 9
' Informação do item que vai sair da lista
Dim fifoNextToLeave As String = fifo.Peek
Debug.WriteLine("Próximo item a sair: " & fifoNextToLeave)
' ------------------------------------------
' OUTPUT (Immediate Window):
' ------------------------------------------
' Próximo item a sair: Item 0
' Remove um item da lista (por ordem) e guarda
' a informação numa variável que será mostrada
Dim fifoOut As String = fifo.Dequeue()
Debug.WriteLine("Item removido: " & fifoOut)
' Adiciona um novo item para manter o mesmo número de itens
fifo.Enqueue("Item 10")
' Mostra os dados finais
showList(fifo)
Stop
' ------------------------------------------
' OUTPUT (Immediate Window):
' ------------------------------------------
' Item 1
' Item 2
' Item 3
' Item 4
' Item 5
' Item 6
' Item 7
' Item 8
' Item 9
' Item 10
' Limpa todos os resultados da lista
fifo.Clear()
End Sub
PS: Como sempre, qualquer dúvida, comentário ou correcção ao artigo é sempre bem vinda!
Um stored procedure (procedimento armazenado) é um conjunto de instruções em linguagem Transact-SQL, guardadas no servidor de forma pré-compilada. A execução pode ser feita dentro do próprio SQL ou através de uma aplicação externa, como é p caso do Visual Studio.
Os stored procedures têm algumas vantagens em relação aos comandos directos ao servidor, como é o caso de modularidade (simples de modificar sem alterar o código da aplicação), rapidez (ficam pré-compilados e ficarem em cache), entre outras, mas gostava de destacar a segurança e na protecção a ataques denominados SQL Injection.
Os SQL injection são instruções que manipulam a query, alterando a entrada de dados no servidor, através de uma aplicação externa. São uma das maiores causas de falhas de segurança das aplicações e podem destruir toda a estrutura de dados.
Para uma melhor percepção da importância dos stored procedures e da protecção aos SQL Injection, recomendo a visualização de um webcast gratuito, disponível na MSDN Brasil de Luciano Moreira: https://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?culture=pt-BR&EventID=1032383827&CountryCode=BR
A utilização de stored procedures em Visual Basic.Net é bastante simples. Cria-se a tabela, depois um novo stored procedure e para finalmente o código.
O seguinte exemplo mostra como criar um stored procedure e como é simples, através do código, mostrar, inserir, modificar ou apagar dados. Não serão mostrados todos os exemplos, uma vez que as principais diferenças estão nos comandos T-SQL e não propriamente no código.
Para o seguinte exemplo foi criada uma tabela simples, com o nome produtos, com três campos apenas:
ID INT (primary key)
codigo INT
produto VARCHAR(50)
Um exemplo simples de um stored procedure para listar todos os dados da tabela produtos seria:
CREATE PROCEDURE dbo.sp_produtos
AS
BEGIN
SELECT * FROM produtos;
END
Após a gravação do stored procedure a instrução CREATE passa a ALTER, porque o que será feito posteriormente é uma alteração ao procedimento e não uma criação. Para o exemplo será feito um stored procedure um pouco mais complexo, que permita a pesquisa por código ou por produto (ou parte do nome).
/*
Stored Procedure que permite a pesquisa por
código ou nome (ou parte do nome) do produto
*/
CREATE PROCEDURE dbo.sp_produtos
/** Indicação dos parâmetros de entrada com um valor por defeito
o que permite que não sejam indicados através do código */
@codigo INT = NULL,
@produto VARCHAR(50) = NULL
AS
BEGIN
SELECT *
FROM produtos
-- Definição do filto da pesquisa
WHERE (@codigo IS NULL OR codigo = @codigo)
AND (@produto IS NULL OR produto LIKE '%'+ @produto + '%');
END
Depois no código de Visual Basic.Net:
' Informação da Connection String
Dim myConnectionString As String = _
"Data Source=.\SQLEXPRESS;AttachDbFilename='c:\myDatabase.mdf';Integrated Security=True;User Instance=True"
' Ligação à base de dados
Using connection As New SqlConnection(myConnectionString)
connection.Open()
' Indica o nome do Stored Procedure e que o tipo do comando
Dim command As New SqlCommand("sp_produtos", connection)
command.CommandType = CommandType.StoredProcedure
' Caso o código tenha sido indicado
If txtCodigo.Text <> String.Empty Then
command.Parameters.Add("@codigo", SqlDbType.Int).Value = txtCodigo.Text
End If
' Caso o produto tenha sido indicado
If txtProduto.Text <> String.Empty Then
command.Parameters.Add("@produto", SqlDbType.VarChar).Value = txtProduto.Text
End If
' Executa o comando e guarda o resultado no SqlDataReader
Dim reader As SqlDataReader = command.ExecuteReader()
' Caso tenha resultados
If reader.HasRows Then
' Ciclo que irá mostrar os produtos encontrados
While reader.Read
Dim result As String = _
String.Format("{0}-{1}-{2}", reader.Item(0), reader.Item(1), reader.Item(2))
Debug.WriteLine(result)
End While
Debug.WriteLine("Fim da pesquisa!") ' Informação no fim da pesquisa
Else
' Caso não tenham sido encontrados produtos ...
Debug.WriteLine("Não foram encontrados produtos!")
End If
reader.Close()
reader = Nothing
End Using 'connection
Este exemplo mostra como listar (na Immediate Window) os produtos de uma tabela, permitindo ou não, indicar dados de modo a estabelecer um filtro de pesquisa. Para inserir dados, modificar ou mesmo apagar é necessário adequar o stored procedure e o código é bastante semelhante, como já mostrado em artigos anteriores.
Este artigo pretende apenas mostrar, de uma forma geral, alguns dos benefícios dos stored procedures e a sua importância na melhoria de performance e segurança dos dados.
PS: Como sempre, qualquer dúvida, comentário ou correcção ao artigo é sempre bem vinda!
Tratamento de Erros
Teoricamente é possível criar uma aplicação em que todos os erros são tratados de modo a não acontecerem, ou seja, nunca existirem. Esta teoria na realidade não acontece por vários motivos, sendo como principal o facto do programador não contemplar ou não conseguir contemplar todos os possíveis problemas durante o desenvolvimento.
Uma correcta gestão dos erros na aplicação evita mensagens difíceis de analisar, aplicações a fecharem inesperadamente e torna, obviamente, as aplicações mais profissionais e robustas.
Com a introdução do .NET apareceu um novo método em substituição ao On Error Go To <label>. O método Try … Catch … End Try permite criar um bloco no código onde o erro é verificado e tratado. É simples de implementar, muito eficiente e com diversas variações que dão uma maior flexibilidade. Num exemplo simples funciona desta forma:
Try
' código do programa
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
No entanto existem mais variações que permitem uma melhor gestão dos erros. São o caso de instruções com o Finally, que permite que o código execute sempre, ou seja, caso exista ou não erro, e o When, que permite decidir se o erro é ou não “agarrado”.
Além disso pode-se a qualquer momento dizer que ocorreu um erro e forçar a mensagem sem duplicar código, através da instrução Throw.
Eis um pequeno exemplo, com a alteração de um valor ou outro, permite alterar a forma como a mensagem/erro é tratado, e mostra alguns métodos que é possível implementar:
Sub CalculaResultado(ByVal valor1 As Integer, ByVal valor2 As Integer, ByVal ignoraErro As Boolean)
Try
' Verifica se os valores são zero
If valor1 = 0 AndAlso valor2 = 0 Then
' Define uma nova ArgumentNullException
Dim msgErro As String = "Os valores a calcular não podem ser os dois 0"
Throw New ArgumentNullException(msgErro, New Exception(String.Empty))
End If
' Caso o valor 1 seja zero, vai ser originada uma
' ArithmeticException devido à divisão por zero
Dim valorfinal As Integer = valor2 / valor1
' Neste exemplo caso o resultado seja menor que 10
' origina uma Exception simples
If valorfinal < 10 Then
Throw New Exception("O valor final não é valido")
End If
Catch ex As ArithmeticException When ignoraErro = False
Debug.WriteLine("Não é permitida uma divisão de um valor por zero")
Catch ex As ArgumentNullException When ignoraErro = False
Debug.WriteLine(ex.Message)
Catch ex As Exception
Debug.WriteLine(ex.Message)
Finally
Debug.WriteLine("Fim do exemplo!")
End Try
End Sub
E alguns resultados:
' Calcula o resultado com números a zero
CalculaResultado(0, 0, False)
Os valores a calcular não podem ser os dois 0
Fim do exemplo!
' Provoca uma divisão por zero
CalculaResultado(0, 50, False)
Não é permitida uma divisão de um valor por zero
Fim do exemplo!
' Provoca uma divisão por zero, mas não trata o erro
CalculaResultado(0, 50, True)
Arithmetic operation resulted in an overflow.
Fim do exemplo!
' Calcula onde o resultado é inferior a 10
CalculaResultado(45, 50, False)
O valor final não é valido
Fim do exemplo!
Inserir Imagens no SQL Server
Em dois artigos anteriores, foram abordadas algumas formas de trabalhar com dados num servidor SQL. Gestão de Dados em SQL Server - Parte I e Gestão de Dados em SQL Server - Parte II mostraram como inserir, modificar, apagar e ler dados, mas no entanto podem-se também inserir imagens e não basta simplesmente indicar qual é a imagem.
Existem duas alternativas para a gestão de imagens: guardar apenas a localização e colocar a imagem no servidor ou gravar a imagem directamente num campo.
Para inserir a imagem é necessário ler, gravar num array de bytes e finalmente utilizar esse array para gravar na base de dados.
Eis um exemplo de como o fazer:
' Define a Connection String
Dim myConnectionString As String = _
"Data Source=.\SQLEXPRESS;AttachDbFilename='c:\myDatabase.mdf';Integrated Security=True;User Instance=True"
Try
' Cria um novo FileStream para leitura da imagem
Dim fs As New IO.FileStream("c:\image.jpg", IO.FileMode.Open, IO.FileAccess.Read)
' Cria um array de Bytes do tamanho do FileStream
Dim ImageData(fs.Length() - 1) As Byte
' Lê os bytes do FileStream para o array criado
fs.Read(ImageData, 0, ImageData.Length)
' Fecha o FileStream ficando a imagem guardada no array
fs.Close()
Using connection As New SqlClient.SqlConnection(myConnectionString)
' Define o comando Transact-SQL para inserir dados
Dim SQL As String = "INSERT INTO contacts ([name],[img]) VALUES (@name,@img);"
Dim command As New SqlClient.SqlCommand(SQL, connection)
' Define os parâmetros para a inserção de dados, onde está o array
' de bytes(imagem) a ser inserida. O tipo do campo é Image
command.Parameters.Add("@name", SqlDbType.VarChar).Value = "jpaulino"
command.Parameters.Add("@img", SqlDbType.Image).Value = ImageData
connection.Open()
' Insere os campos no SQL
command.ExecuteNonQuery()
End Using
Catch ex As Exception
MessageBox.Show(ex.Message, My.Application.Info.Title, MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
PS: Como sempre, qualquer dúvida, comentário ou correcção ao artigo é sempre bem vinda!
No VB.NET não está disponível uma label que permita criar um sombreado no texto de modo a tornar-lo com mais destaque. Esta propriedade é muito comum em ambiente Office, em programas como o Excel, Word, etc, mas está disponível também em muitos outros programas.
Para criar uma label que permita criar um sombreado é preciso desenhar o texto através do namespace System.Drawing. O conceito é bastante simples: a label escreve um texto, que será a sombra, e através do evento onPaint() desenha-se o texto que fica por cima.
Depois é escondida a propriedade ForeColor (que define a cor do texto) porque esta não será a cor do texto mas sim da sombra e são criadas algumas novas propriedades:
- OffsetX (define o offset, ou desvio, na vertical face ao texto inicial)
- OffsetY (define o offset, ou desvio, na horizontal face ao texto inicial)
- ForeColorFront (define a cor que fica por cima)
- ForeColorBack (define a cor que fica por baixo – sombra)
É necessário então criar uma nova classe e utilizar o seguinte código:
Imports System.Drawing
Imports System.ComponentModel
Public Class ShadowLabel
Inherits Label
#Region "Properties"
Private m_offsetX As Integer
Private m_offsetY As Integer
Private m_ForeColorFront As Color
''' <summary>
''' Definição do offset em X
''' </summary>
<DefaultValue(0)> _
<Category("Shadow")> _
<Description("Definição do offset em X")> _
Public Property OffsetX() As Integer
Get
Return m_offsetX
End Get
Set(ByVal value As Integer)
m_offsetX = value
Me.Invalidate()
End Set
End Property
''' <summary>
''' Definição do offset em Y
''' </summary>
<DefaultValue(-1)> _
<Category("Shadow")> _
<Description("Definição do offset em Y")> _
Public Property OffsetY() As Integer
Get
Return m_offsetY
End Get
Set(ByVal value As Integer)
m_offsetY = value
Me.Invalidate()
End Set
End Property
''' <summary>
''' Definição da cor da frente
''' </summary>
<DefaultValue(GetType(Color), "RoyalBlue")> _
<Category("Shadow")> _
<Description("Definição da cor da frente")> _
Public Property ForeColorFront() As Color
Get
Return m_ForeColorFront
End Get
Set(ByVal value As Color)
m_ForeColorFront = value
Me.Invalidate()
End Set
End Property
''' <summary>
''' Definição da cor de sombreado
''' </summary>
<DefaultValue(GetType(Color), "White")> _
<Category("Shadow")> _
<Description("Definição da cor de sombreado")> _
Public Property ForeColorBack() As Color
Get
Return Me.ForeColor
End Get
Set(ByVal value As Color)
Me.ForeColor = value
Me.Invalidate()
End Set
End Property
' Serve apenas para definir que a propriedade ForeColor
' não está disponível para alterar no editor e no design
<Browsable(False), EditorBrowsable(EditorBrowsableState.Never)> _
Public Overrides Property ForeColor() As System.Drawing.Color
Get
Return MyBase.ForeColor
End Get
Set(ByVal value As System.Drawing.Color)
MyBase.ForeColor = value
End Set
End Property
#End Region
' Define algumas propriedades por defeito
Public Sub New()
' Definição dos valores iniciais de offset
Me.OffsetX = 0
Me.OffsetY = -1
' Definição das cores iniciais do texto
Me.ForeColorFront = Color.RoyalBlue
Me.ForeColorBack = Color.White
End Sub
' No evento Paint desenha o texto
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
MyBase.OnPaint(e)
' Desenha o texto que fica na frente
Dim BackBrush As New SolidBrush(Me.ForeColorFront)
e.Graphics.DrawString(Me.Text, Me.Font, BackBrush, OffsetX, OffsetY, StringFormat.GenericDefault)
BackBrush.Dispose()
End Sub
End Class
No final é preciso compilar a aplicação e o controlo estará disponível na toolbar.
Este é um exemplo que pretende mostrar como criar uma label com uma sombra, mas também como utilizar algumas funcionalidades do namespace Drawing.
PS: Como sempre, qualquer dúvida, comentário ou correcção ao artigo é sempre bem vinda!