sabato 1 dicembre 2007

vb.net 2.0 comprimere e decomprimere file

Il framework 2.0 introduce il namespace System.IO.Compression il quale contiene classi per la compressione o decompressione di uno stream. Vediamo come sfruttarle per comprimere/decomprimere un file usando l'algoritmo GZip.





Imports System.IO
Imports system.IO.Compression

Public Class bcc_Compression

Public Shared Function Decomprimi(ByVal str_Origine As String, _
ByVal str_Destinazione As String) As Boolean

Try

Dim wf As FileStream
Dim rf As FileStream
Dim df As GZipStream

Dim buff(4096) As Byte
Dim int_Count As Int32

' stream per la lettura del file compresso
rf = New FileStream(str_Origine, FileMode.Open)

' stream per la scrittura del file decompresso
wf = New FileStream(str_Destinazione, FileMode.Create)

' stream per la decompressione, prende in input
' lo stream per la lettura del file str_Origine

df = New GZipStream(rf, CompressionMode.Decompress)

' ciclo per la lettura dei dati decompressi da df
int_Count = df.Read(buff, 0, buff.Length)
While int_Count > 0

' scrive il buffer sul file str_Destinazione
wf.Write(buff, 0, int_Count)
int_Count = df.Read(buff, 0, buff.Length)

End While

' chiude gli stream
df.Close()
rf.Close()
wf.Close()

Return True

Catch
ex As Exception

Return False

End Try

End Function


Public Shared Function
Comprimi(ByVal str_Origine As String, _
ByVal str_Destinazione As String) As Boolean
Try

Dim wf As FileStream
Dim cf As GZipStream

Dim buff() As Byte

' legge il file da comprimere in un buffer
buff = My.Computer.FileSystem.ReadAllBytes(str_Origine)

' apre uno stream per scrivere il file compresso
wf = New FileStream(str_Destinazione, FileMode.Create)

' compressione.. cf prende in input uno stream per la
' scrittura del file str_Destinazione

cf = New GZipStream(wf, CompressionMode.Compress)
cf.Write(buff, 0, buff.Length)

' chiude gli stream
cf.Close()
wf.Close()

Return True

Catch
ex As Exception

Return False

End Try

End Function

End Class




mercoledì 28 novembre 2007

xaml - homer simpson su silverlight

Ecco un disegnino per iniziare ad imparare ad usare lo xaml.. quello che si vede qua giù è solo un preview in jpeg, la versione in xaml è invece scaricabile da questo link. Ricordo che per vedere l'esempio in funzione è necessario installare il plug-in silverlight.



Di seguito il codice xaml



<Canvas xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="500" Height="500">

<Line
X1="100"
Y1="350"
X2="300"
Y2="350"
Stroke="Black"
StrokeThickness="2"
Opacity="0.5"
StrokeDashArray="10 20 2 10 5 20"/>

<Ellipse
Canvas.Left="150"
Canvas.Top="350"
Fill="LightGray"
Height="20"
Width="100"
StrokeThickness="2"
Stroke="LightGray" />

<Ellipse
Canvas.Left="80"
Canvas.Top="355"
Width="200"
Height="10"
Fill="LightGray"
Stroke="LightGray"/>

<Canvas Canvas.Left="155" Canvas.Top="70">

<Path Data="M -7 80
L 0 40
L 0 0
A 20 16 0 0 1 60 0
L 60 15
L 62 16
L 64 20
L 60 20
L 45 45
L 28 92Z" Stroke="Black">
<Path.Fill>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="#FFD000" Offset="0.0" />
<GradientStop Color="#FFFF00" Offset="0.25" />
</LinearGradientBrush>
</Path.Fill>
</Path>

<!-- orecchio -->
<Path Data="M 4 32
A 6 4 0 1 0 5 42"
Stroke="black" />

<Path Data="M 4 35
L 0 36
L 2 38"
Stroke="black" />

<!-- capelli -->
<Path Data="M -5 30
L -4 20
L 0 30
L 4 20
L 5 30"
Stroke="black" />

<Path Data="M 22 -16
A 5 5 0 0 1 50 -18"
Stroke="black" />

<Path Data="M 15 -16
A 5 4 0 0 1 42 -22"
Stroke="black" />

<!-- naso -->
<Path Data="M 50 36
L 58 39
A 3 2 0 0 1 56 50
L 43 45"
Stroke="black" Fill="yellow" />

<!-- muso -->
<Path Data="M 58 66
L 58 50
L 45 46
A 12 10 0 1 0 35 75
L 44 74
L 48 76
L 54 72
A 1 1 0 1 0 58 66"
Stroke="black">
<Path.Fill>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="#876516" Offset="0.10" />
<GradientStop Color="#DFAF40" Offset="0.70" />
</LinearGradientBrush>
</Path.Fill>
</Path>

<Path Data="M 52 71
L 46 72
L 48 74 Z"
Stroke="black" Fill="black" />

<Canvas.RenderTransform>
<RotateTransform Angle="-8" CenterX="0" CenterY="40" />
</Canvas.RenderTransform>

</Canvas>

<Canvas Canvas.Left="212" Canvas.Top="90">
<Ellipse Canvas.Left="-10" Canvas.Top="-10" Width="20" Height="20" Stroke="black" Fill="white" />
<Ellipse Canvas.Left="5" Canvas.Top="5" Width="2" Height="2" Stroke="black" Fill="black" />
</Canvas>

<Canvas Canvas.Left="200" Canvas.Top="92">
<Ellipse Canvas.Left="-10" Canvas.Top="-10" Width="20" Height="20" Stroke="black" Fill="white" />
<Ellipse Canvas.Left="5" Canvas.Top="5" Width="2" Height="2" Stroke="black" Fill="black" />
</Canvas>


<!-- Braccio -->

<Canvas Canvas.Left="185" Canvas.Top="197" >

<Path Data="M 0 0
C -18 -10 -18 -50 20 -30
L 0 0"
Stroke="black">
<Path.Fill>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="#DDDDDD" Offset="0.10" />
<GradientStop Color="#FFFFFF" Offset="0.30" />
</LinearGradientBrush>
</Path.Fill>
</Path>

<Path Data="M 1 -2
L 19 -28
L 30 -20
L 55 -30
C 58 -37 54 -40 52 -42
L 54 -45
L 57 -46
L 61 -38
L 61 -42
L 64 -44
L 68 -44
L 68 -47
L 71 -49
L 74 -49
L 76 -47
C 70 -30 84 -35 87 -35
L 89 -32
L 89 -31
L 80 -28
C 82 -28 69 -15 66 -16
C 32 22 20 5 1 -2"
Stroke="black">
<Path.Fill>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="#FFD000" Offset="0.0" />
<GradientStop Color="#FFFF00" Offset="0.22" />
</LinearGradientBrush>
</Path.Fill>
</Path>

<Path Data="M 68 -30
C 72 -30 70 -20 68 -22"
Stroke="black"/>

<Path Data="M 65 -39
L 70 -39
L 71 -40
L 71 -44
L 66 -44"
Stroke="black"/>


<Canvas.RenderTransform>
<RotateTransform Angle="-15" CenterX="0" CenterY="0" />
</Canvas.RenderTransform>

</Canvas>


<!-- pancia -->
<Canvas Canvas.Left="154" Canvas.Top="110">

<!-- busto -->
<Path Data="M 0 40
C -10 40 -20 70 -20 70
C -30 80 -45 100 -40 120
C -30 140 50 140 70 130
C 85 120 50 60 35 49Z"
Stroke="black">
<Path.Fill>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="#DDDDDD" Offset="0.10" />
<GradientStop Color="#FFFFFF" Offset="0.30" />
</LinearGradientBrush>
</Path.Fill>
</Path>

<!-- colletto -->
<Path Data="M -2 21
L -10 41
L 25 51
L 30 31Z"
Stroke="black">
<Path.Fill>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="#DDDDDD" Offset="0.10" />
<GradientStop Color="#FFFFFF" Offset="0.30" />
</LinearGradientBrush>
</Path.Fill>
</Path>

<Path Data="M 26 51
L 31 31
L 35 48
L 40 31
L 45 33
L 35 51Z"
Stroke="black">
<Path.Fill>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="#DDDDDD" Offset="0.10" />
<GradientStop Color="#FFFFFF" Offset="0.30" />
</LinearGradientBrush>
</Path.Fill>
</Path>

<!-- cravatta -->
<Path Data="M 28 42
L 42 42
L 44 52
L 34 52
L 34 47
L 27 47Z"
Fill="red" Stroke="black"/>

<Path Data="M 34 52
L 44 52
L 75 100
L 70 120
L 40 110Z"
Fill="red"
Stroke="black" />

</Canvas>


<!-- Braccio -->

<Canvas Canvas.Left="155" Canvas.Top="197" >

<Path Data="M 0 0
C -18 -10 -18 -50 20 -30
L 0 0"
Stroke="black">
<Path.Fill>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="#DDDDDD" Offset="0.10" />
<GradientStop Color="#FFFFFF" Offset="0.30" />
</LinearGradientBrush>
</Path.Fill>
</Path>

<Path Data="M 1 -2
L 19 -28
L 30 -20
L 55 -30
C 58 -37 54 -40 52 -42
L 54 -45
L 57 -46
L 61 -38
L 61 -42
L 64 -44
L 68 -44
L 68 -47
L 71 -49
L 74 -49
L 76 -47
C 70 -30 84 -35 87 -35
L 89 -32
L 89 -31
L 80 -28
C 82 -28 69 -15 66 -16
C 32 22 20 5 1 -2"
Stroke="black">
<Path.Fill>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="#FFD000" Offset="0.0" />
<GradientStop Color="#FFFF00" Offset="0.22" />
</LinearGradientBrush>
</Path.Fill>
</Path>

<Path Data="M 68 -30
C 72 -30 70 -20 68 -22"
Stroke="black"/>

<Path Data="M 65 -39
L 70 -39
L 71 -40
L 71 -44
L 66 -44"
Stroke="black"/>

</Canvas>


<!-- Gambe -->

<Canvas Canvas.Left="115" Canvas.Top="230">

<!-- gamba destra -->

<!-- scarpa -->
<Path Data="M 60 110
L 100 110
C 100 112 110 110 120 125
C 110 130 90 130 80 127
L 78 130
L 60 130Z"
Stroke="black">
<Path.Fill>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="#000000" Offset="0.0" />
<GradientStop Color="#5E2F00" Offset="0.20" />
</LinearGradientBrush>
</Path.Fill>
</Path>

<Path Data="M 60 125
L 115 125"
Stroke="black"/>

<Path Data="M 100 20
C 120 70 105 90 100 110
C 105 112 65 112 60 110
C 65 90 70 70 55 40"
Stroke="black">
<Path.Fill>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="#004A95" Offset="0.10" />
<GradientStop Color="#0080FF" Offset="0.50" />
</LinearGradientBrush>
</Path.Fill>
</Path>

<Path Data="M 102 100
C 105 102 35 102 60 100"
Stroke="black"/>

<!-- gamba sinistra -->

<!-- scarpa -->
<Path Data="M 30 110
L 70 110
C 70 112 80 110 90 125
C 80 130 60 130 50 127
L 48 130
L 30 130Z"
Stroke="black">
<Path.Fill>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="#000000" Offset="0.0" />
<GradientStop Color="#5E2F00" Offset="0.20" />
</LinearGradientBrush>
</Path.Fill>
</Path>

<Path Data="M 30 125
L 87 125"
Stroke="black"/>

<Path Data="M 0 0
C 10 20 90 20 110 10
C 120 15 80 40 70 40
C 90 70 75 90 70 110
C 75 112 35 112 30 110
C 35 90 50 70 25 40
C 8 38 -5 2 0 0"
Stroke="black">
<Path.Fill>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="#004A95" Offset="0.10" />
<GradientStop Color="#0080FF" Offset="0.50" />
</LinearGradientBrush>
</Path.Fill>
</Path>

<Path Data="M 72 100
C 75 102 35 102 30 100"
Stroke="black" />
</Canvas>

<Rectangle Width="499" Height="499" Stroke="Blue" StrokeThickness="3" />

<Canvas.Background>
<SolidColorBrush x:Name="mySolidColorBrush" Color="White" />
</Canvas.Background>

<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<BeginStoryboard>
<Storyboard x:Name="ColorStoryboard">

<!-- Animate the background color of the canvas from red to green over 4 seconds. -->
<ColorAnimation BeginTime="00:00:00" Storyboard.TargetName="mySolidColorBrush" Storyboard.TargetProperty="Color" From="White" To="Green" Duration="0:0:4" />

</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
</Canvas>

domenica 28 ottobre 2007

geo localizzazione dei visitatori in 5 minuti

Ecco come dotare un sito della geo-localizzazione dei visitatori sfruttando due servizi gratuiti e un piccolo sorso di javascript. Per la geo-localizzazione occorre per prima cosa risalire alla latitudine e longitudine del visitatore sfruttando il suo indirizzo IP, ottenute queste informazioni bisogna poi mostrarle su una mappa.. Ci sono diversi servizi in rete che permettono di associare latitudine e longitudine ad un indirizzo IP, io qui mi sono servito di freeIpServices.com il quale, oltre ad essere gratuito, è anche molto semplice da usare, basta importare uno script javascript e sfruttare le due funzioni getLat() e getLng() che restituiscono rispettivamente latitudine e longitudine. Quello che ci rimane da fare è prendere una bella mappa e incollarci sopra la latitudine e la longitudine usando "..abbondante ccccolla vinilica" di muciacciana memoria. Qui le alternative sono principalmente due: Google Maps e Virtual Earth di casa Microsoft. Nell'esempio che segue ho usato Virtual Earth per il quale c'è in rete un ottimo SDK a questo indirizzo The Virtual Earth Interactive SDK, per caricare la mappa basta richiamare il metodo LoadMap al quale vengono passati in input latitudine e longitudine. Ed ecco il risultato del nostro "collage"



Qui di seguito invece l'esempio completo




<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Coffee Break - GeoLocalization By IP </title>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<script type="text/javascript" src="http://freeipservices.com/ip2geo?v=1"></script>

<script type="text/javascript" src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6"></script>

<script type="text/javascript">
var map = null;

function GetMap()
{
map = new VEMap('myMap');
map.LoadMap(new VELatLong(getLat(), getLng()), 10 ,'h' ,false);
document.getElementById("_fis").style.width="475px";
}

</script>

<style type="text/css">

#_fis {
color: white;
background-color: black;
font-family: verdana;
font-weight: bold;
}

</style type="text/css">

</head>

<body onload="GetMap();">

<div id='myMap' style="position:relative; width:500px; height:400px;"></div>

</body>

</html>


giovedì 18 ottobre 2007

Esprimere un numero in lettere

Oggi ho dovuto scrivere un modulo per la compilazione e la stampa automatica di assegni, nulla di che, uno di quei compiti poco stimolanti che si svolgono col cervello in standby e lo sguardo perso nel nulla. E poi odio avere a che fare con le stampanti, forse perchè tutte le stampanti che ho avuto mi hanno puntualmente abbandonato il giorno prima della consegna di qualche tesina. Ad essere sincero però, devo ammettere che cinque minuti di piacere me li sono ritagliati, ed è stato quando ho dovuto scrivere una funzione che mi permettesse di esprimere l'importo dell'assegno in lettere. Un momento di piacere che definirei dal sapore nostalgico! Non so perchè, ma mentre scrivevo la funzione mi sono venuti in mente i primi anni dell'università, quando studiavo Pascal e implementavo il BubbleSort piuttosto che la Torre di Hanoi o robe del genere. A volte il cervello fa collegamenti strani, forse dovrei smettere di bere caffè..


Public Class bcc_NumeriInLettere

Private mTabella1() As String = {"uno", _
"due", _
"tre", _
"quattro", _
"cinque", _
"sei", _
"sette", _
"otto", _
"nove", _
"dieci", _
"undici", _
"dodici", _
"tredici", _
"quattordici", _
"quindici", _
"sedici", _
"diciassette", _
"diciotto", _
"diciannove"}

Private mTabella2() As String = {"venti", _
"trenta", _
"quaranta", _
"cinquanta", _
"sessanta", _
"settanta", _
"ottanta", _
"novanta"}


Private Function m_FiniscePerVocale(ByVal tmp As String) As Boolean

Return
tmp.EndsWith("a") Or _
tmp.EndsWith("e") Or _
tmp.EndsWith("i") Or _
tmp.EndsWith("o") Or _
tmp.EndsWith("u")

End Function

Private Function
m_IniziaPerVocale(ByVal tmp As String) As Boolean

Return
tmp.StartsWith("a") Or _
tmp.StartsWith("e") Or _
tmp.StartsWith("i") Or _
tmp.StartsWith("o") Or _
tmp.StartsWith("u")

End Function

Private Function
m_da1a999(ByVal strval As String) As String

Dim
l As Int16 = strval.Length
Dim i, j, k As Int16

Dim tmp, dec, uni As String
Dim
c As String

tmp = ""
j = 0

For i = l To 1 Step -1

c = strval.Chars(j)
j = j + 1

If c <> "0" Then

Select Case i

Case 3

If c = "1" Then

tmp &= "cento"

Else

k = CInt(c) - 1
tmp &= mTabella1(k) & "cento"

End If

Case 2

' controlla se è un numero conosciuto
k = CInt(c & strval.Chars(j))

If k < color="blue">Then


tmp &= mTabella1(k - 1)

ElseIf (k Mod 10) = 0 Then

' venti, trenta, quaranta...
tmp &= mTabella2((k / 10) - 2)

Else

' ventuno, ventidue, ventitre..
k -= k Mod 10
k = (k / 10) - 2
dec = mTabella2(k)

k = CInt(strval.Chars(j).ToString) - 1
uni = mTabella1(k)

If m_FiniscePerVocale(dec) And m_IniziaPerVocale(uni) Then

' per esempio ventuno, ventotto
' tronchiamo venti a vent e concateniamo uno, otto..

dec = dec.SubString(0, dec.Length - 1)

End If

tmp &= dec & uni

End If

Return tmp

Case 1

' basta tradurre l'unita
k = CInt(c) - 1
tmp &= mTabella1(k)

End Select

End If

Next

Return
tmp

End Function

Public Function
NumeriInLettere(ByVal strval As String) As String

Dim
part1 As String
Dim
part2 As String
Dim
tmp As String
Dim
rv As String
Dim
len As Int16
Dim i As Int16

part1 = strval
part2 = ""
tmp = ""
rv = ""

i = strval.IndexOf(",")
If i <> -1 Then

part1 = strval.SubString(0, i)
part2 = strval.SubString(i + 1)

End If

While True


len = part1.Length
If len > 12 Then

' ci fermiamo solo ai miliardi..
Return
"numero troppo grande"

ElseIf (12 >= len) And (len > 9) Then

' miliardi
i = len - 9
tmp = part1.SubString(0, i)
part1 = part1.SubString(i)

tmp = m_da1a999(tmp)
rv = IIf(tmp = "uno", "unmiliardo", tmp & "miliardi")

ElseIf (9 >= len) And (len > 6) Then

' milioni
i = len - 6
tmp = part1.SubString(0, i)
part1 = part1.SubString(i)

tmp = m_da1a999(tmp)
rv &= IIf(tmp = "uno", "unmilione", tmp & "milioni")

ElseIf (6 >= len) And (len > 3) Then

i = len - 3
tmp = part1.SubString(0, i)
part1 = part1.SubString(i)

tmp = m_da1a999(tmp)
rv &= IIf(tmp = "uno", "mille", tmp & "mila")

Else

tmp = m_da1a999(part1)
rv &= tmp & "/" & IIf(part2 <> "", part2, "00")
Return rv

End If

End While

End Function

End Class


domenica 14 ottobre 2007

FoxBurner: le statistiche di FeedBurner sulla statusbar del tuo browser

Questa settimana ho dedicato parte del mio tempo libero alla scrittura di una estensione per FireFox, era già da un pò che mi ero ripromesso di implementarne una, mi mancava solo l'ispirazione.. che finalmente è arrivata.. I feed generati da questo blog sono gestiti tramite FeedBurner, strumento che personalmente trovo utilissimo e che interrogo milioni di volte al giorno (perdendo ovviamente un sacco di tempo..) per sapere quanto popolari siano i miei feed (poco, pochissimo devo dire.. :D). E da qui nasce l'ispirazione, le statistiche di FeedBurner nella statusbar del mio FireFox! Scrivere una estensione per FireFox che si limiti a svolgere questo compito non è per nulla complicato, ci sono molti tutorial in rete sull'argomento ( a tal proposito vi consiglio Creating a dynamic status bar extension ), la parte più fastidiosa poteva essere recuperare le statistiche da FeedBurner ma, per fortuna, nemmeno questo punto si è poi rivelato complicato da risolvere, FeedBurner infatti mette a disposizioni delle API per la gestione del proprio account e dei propri feed. Per mettere insieme il tutto mi sono bastate un paio di ore di lavoro grazie anche all'aiuto fondamentale della Console degli errori e della Extension Developer ( scaricabile da questo link ) che hanno sveltito di molto il processo di sviluppo. Da questo indirizzo è possibile scaricare la mia FoxBurner, per installarla basta aprire la finestra delle estensioni ( Strumenti->Componenti Aggiuntivi ) e trascinare li il file foxburner.xpi. Per tutti coloro che invece volessero dare una occhiata al codice ricordo che basta cambiare l'estensione del file foxburner da xpi a zip e poi decomprimere..

sabato 6 ottobre 2007

Interfaccia IEditableObject, una implementazione generica

Ecco una classe che uso spesso come base dei miei Business Object, implementa l'interfaccia IEditableObject così da fornire la modifica dei dati transazionale a tutte le classi che la ereditano. L'interfaccia IEditableObject richiede di implementare i metodi BeginEdit, EndEdit e CancelEdit. Il metodo BeginEdit viene chiamato prima di iniziare la modifica dell'oggetto e si occupa di salvarne da qualche parte lo stato, il metodo CancelEdit serve per annullare l'operazione di modifica e ripristinare lo stato iniziale dell'oggetto, mentre il metodo EndEdit serve a confermare le modifiche eseguite sull'oggetto. E' da sottolineare il fatto che tra la chiamata a BeginEdit e la chiamata a uno tra EndEdit e CancelEdit possono intercorrere altre chiamate al metodo BeginEdit che devono essere ignorate. Come già detto il compito di BeginEdit è quello di salvare lo stato dell'oggetto così da poterlo poi ripristinare nel caso in cui l'utente volesse annullare le modifiche. A tale scopo sfrutteremo una Collection e la potenza della classe Reflection. Vediamo come: facendo uso della Reflection scoveremo tutte le proprietà pubbliche dell'oggetto e, scorrendole una dopo l'altra, salveremo il valore di ognuna di esse in una Collection usando come chiave il nome della proprietà. All'inizio del metodo BeginEdit metteremo un controllo sul valore della Collection, se questa non è nothing vuol dire che non si tratta della prima chiamata a BeginEdit e quindi usciamo dalla procedura, viceversa se la Collection è nothing allora possiamo istanziarla e procedere con il salvataggio dello stato. Il metodo EndEdit si occupa della conferma dei dati, ci limiteremo quindi a settare la Collection a nothing per far si che una successiva chiamata a BeginEdit non venga ignorata. Nel metodo CancelEdit vengono compiute le operazione inverse di quelle che si eseguono nel metodo BeginEdit, si leggono cioè i valori salvati nella Collection e si settano le proprietà dell'oggetto.



Public Class bcc_EditableObject
Implements System.ComponentModel.IEditableObject

Private mTmpData As Collection

Public Sub BeginEdit() _
Implements System.ComponentModel.IEditableObject.BeginEdit

' solo la prima BeginEdit ha effetto
If Not IsNothing(mTmpData) Then Exit Sub

Dim pi_src As Reflection.PropertyInfo

mTmpData = New Collection

For Each pi_src In _
Me.GetType.GetProperties( _
Reflection.BindingFlags.Public Or _
Reflection.BindingFlags.Instance)

' la proprietà non deve essere ReadOnly
If pi_src.CanWrite Then

mTmpData.Add(pi_src.GetValue(Me, Nothing), pi_src.Name)

End If

Next

End Sub


Public Sub CancelEdit() _
Implements System.ComponentModel.IEditableObject.CancelEdit

' nulla da ripristinare
If IsNothing(mTmpData) Then Exit Sub

Dim pi_src As Reflection.PropertyInfo

For Each pi_src In _
Me.GetType.GetProperties( _
Reflection.BindingFlags.Public Or _
Reflection.BindingFlags.Instance)

' la proprietà non deve essere ReadOnly
If pi_src.CanWrite Then

pi_src.SetValue(Me, mTmpData.Item(pi_src.Name), Nothing)

End If

Next

End Sub


Public Sub EndEdit() _
Implements System.ComponentModel.IEditableObject.EndEdit

' via libera alla prossima BeginEdit
mTmpData = Nothing

End Sub

End Class


sabato 29 settembre 2007

ToolStrip, rendering in stile Xp!

Ecco una semplice classe per dare alle ToolStrip uno style Xp-Like (di colore rosso!).
E' soltanto un punto di partenza, Può essere arricchita riscrivendo i metodi che si
occupano del disegno delle tendine piuttosto che dei bottoni..



Class bcc_XpRenderer
Inherits ToolStripProfessionalRenderer

Protected Overrides Sub OnRenderToolStripBackground( _
ByVal e As System.Windows.Forms.ToolStripRenderEventArgs)

Dim r1, r2, r3 As Rectangle
Dim b1, b3 As Drawing2D.LinearGradientBrush

r1 = New Rectangle(0, 0, e.ToolStrip.Width, 10)
r2 = New Rectangle(0, 10, e.ToolStrip.Width, e.ToolStrip.Height - 10)
r3 = New Rectangle(0, e.ToolStrip.Height - 10, e.ToolStrip.Width, 10)

b1 = New Drawing2D.LinearGradientBrush(r1, _
Color.White, _
Color.Red, _
Drawing2D.LinearGradientMode.Vertical)

b3 = New Drawing2D.LinearGradientBrush(r3, _
Color.Red, _
Color.DarkRed, _
Drawing2D.LinearGradientMode.Vertical)

e.Graphics.FillRectangle(b1, r1)
e.Graphics.FillRectangle(Brushes.Red, r2)
e.Graphics.FillRectangle(b3, r3)

b1.Dispose()
b3.Dispose()

End Sub

End Class



Per vedere l'effetto che fa basterà impostare la proprietà Renderer del Toolstrip con new bcc_XpRenderer() ed il gioco è fatto.

sabato 22 settembre 2007

Testare la connessione alla rete..

Vogliamo sapere se disponiamo di una connessione alla rete?
Ecco cosa ci consiglia il prezioso MSDN..
http://msdn2.microsoft.com/it-it/library/4y6b9c17(VS.80).aspx

mi verrebbe da dire: ci sei, ce la fai? sei connesso???
.. oops l'ho detto.. :D

Windows Form e InputValidation

Ecco un piccolo esempio su come, generalmente, gestisco la validazione dei dati. Immaginiamo di avere un form che implementi una ipotetica scheda clienti con i seguenti campi: nome, cognome, indirizzo e numero telefonico. L'utente deve inserire necessariamente i primi tre campi e se inserisce anche il numero di telefono deve inserirlo ovviamente in un formato corretto. Se qualche campo viola le condizioni precedenti, il bottone per salvare i dati sul database viene disabilitato e accanto a quei campi che contengono dati non validi compare un messaggio di errore. Ingredienti: un form, 4 controlli TextBox accompagnati da altrettanti controlli Label, un controllo Button per il salvataggio dei dati, un ErrorProvider per i messaggi di errori e, infine, la piccolissima classe bcc_InputValidation che incontreremo tra qualche riga. Validare ogni controllo singolarmente è semplice, in questo esempio avendo solamente dei TextBox si sfrutta per questo scopo l'evento TextChanged. Sarebbe stato più corretto utilizzare l'evento Validating ma avremmo visto il risultato della validazione solo dopo essere usciti dal controllo, usando invece l'evento TextChanged avremo una validazione in tempo reale. La cosa più antipatica è invece il dover tenere traccia dello stato di tutti i controlli per decidere quando abilitare il bottone per il salvataggio. La classe bcc_InputValidation si occupa proprio di questo:


Private Class bcc_InputValidation

Private mht As New Hashtable

' serve ad indicare che la proprietà InputValido è cambiata
Public Event InputValidoChanged As EventHandler

' salva lo stato di ogni controllo che vogliamo monitorare
Public Sub SettaStato(ByVal str_NomeControllo As String,ByVal bool_Valido As Boolean)

If mht.ContainsKey(str_NomeControllo) Then

mht(str_NomeControllo) = bool_Valido

Else

mht.Add(str_NomeControllo, bool_Valido)

End If

' è cambiato il valore di InputValido
RaiseEvent InputValidoChanged(Me,New EventArgs)

End Sub

' Restituisce True se tutti i controlli sono validi
Public ReadOnly Property InputValido()
Get
Dim
val As Boolean

For Each val In mht.Values

' se trova un solo controllo non valido, restituisce False
If Not val Then Return False

Next


' Tutti i controlli sono validi
Return True

End Get

End Property

End Class


La proprietà Enabled del bottone ( btn_Salva ) che serve al salvataggio dei dati, viene messa in DataBinding con la proprietà InputValido dell'oggetto globale mInputValidation, istanza della classe bcc_InputValidation. Ovviamente, quando InputValido vale True il bottone verrà automaticamente abilitato, di contro, quando InputValido vale False il bottone verrà disabilitato. Vediamo un pò più nel dettaglio il funzionamento della classe bcc_InputValidation. Ogni volta che si esegue la validazione di un singolo controllo, si memorizza l'esito della validazione ( true o false ) nella HashTable della classe bcc_InputValidation attraverso il metodo SettaStato il quale poi si occuperà di lanciare un evento InputValidoChanged. E' proprio questo evento
che notifica al Databinding il cambiamento della proprietà pubblica InputValido determinando così lo stato ( abilitato o meno ) del bottone btn_Salva. Ecco il resto del codice:

Private Sub frmMain_Load(ByVal sender As Object,_
ByVal e As System.EventArgs) Handles Me.Load

' questa inizializzazione fa scattare l'input validation
txt_Cognome.Text = "..."
txt_Nome.Text = "..."
txt_Indirizzo.Text = "..."
txt_Telefono.Text = ""

' se tutti i controlli sono validi,
' il bottone salva viene abilitato

btn_Salva.DataBindings.Add("Enabled", mInputValidation, "InputValido")

End Sub

Private Sub
btn_Salva_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles btn_Salva.Click

' ....
' qui vanno le chiamate al database
' ...


MsgBox("dati salvati correttamente")

End Sub

Private Sub
txt_TextChanged(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles txt_Nome.TextChanged

Dim txt As TextBox = sender

' per default setta lo stato del controllo a valido
mInputValidation.SettaStato(txt.Name, True)
ep_ErrorProvider.SetError(txt, "")

If txt.Text.Trim = "" Then

mInputValidation.SettaStato(txt.Name, False)
ep_ErrorProvider.SetError(txt, "campo richiesto!")

End If

End Sub

Private Sub
txt_Telefono_TextChanged(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles txt_Telefono.TextChanged

mInputValidation.SettaStato("txt_Telefono", True)
ep_ErrorProvider.SetError(txt_Telefono, "")

' non è un campo richiesto
If txt_Telefono.Text.Trim = "" Then Exit Sub

' se l'utente ha inserito il telefono, validiamolo
If Not Regex.IsMatch(txt_Telefono.Text.Trim, "^[+]?[0-9 ]+$") Then

mInputValidation.SettaStato("txt_Telefono", False)
ep_ErrorProvider.SetError(txt_Telefono, "formato non valido!")

End If

End Sub




A questo link il progetto completo.

sabato 15 settembre 2007

Un Editor HTML per vb.net

Qualche giorno fa mi sono imbattuto nel seguente problema: dovevo scrivere una applicazione Windows Form che permettesse all'utente di inviare delle email in formato html. Per l'invio delle email nessun problema, qualche problema poteva nascere dal fatto che l'utente dovesse formattarle in html. Mi sarebbe servito quindi un editor html WYSIWYG in quanto gli utenti, per definizione, non conoscono l'html.. Li per li ho pensato che il compito fosse di immediata soluzione, "scarico qualche controllo per vb.net ed il gioco è fatto", mi sono detto. Mi sono messo allora al lavoro cercando in rete qualche cosa che potesse tornare utile al mio scopo, ma la maggior parte dei controlli che ho trovato erano o a pagamento o troppo complicati da riutilizzare. Non mi sono perso d'animo e, rimboccandomi le maniche, ho deciso di fare tutto da me. Una base dalla quale partire l'avevo già, qualche anno fa mi ero infatti cimentato nella scrittura di un editor html per asp.net, dovevo quindi importare questo controllo in vb.net. Ecco come ho risolto il problema: ho utilizzato un controllo WebBrowser da fare puntare alla pagina html contenente l'editor. A questo punto il grosso del lavoro era stato fatto, e anche in pochissimo tempo, per completare l'opera dovevo solo dare un aspetto più gradevole all'editor, questa pagina html conteneva infatti, nella sua parte alta, una toolbar per la formattazione del testo che non si integrava bene con il resto dell'applicazione. Ho deciso allora di lasciare nella pagina html solo il cuore javascript dell'editor e di implementare la bottoniera "all'esterno" del WebBrowser usando due controlli ToolStrip. La comunicazione tra l'interfaccia grafica e il javascript della pagina è stata realizzata attraverso la proprietà ObjectForScripting del controllo WebBrowser. Utilizzando il metodo WebBrowser.Document.InvokeScript è possibile richiamare una procedura javascript definita all'interno del documento html, di contro, si può invocare una procedura vb.net dal javascript utilizzando la sintassi window.external.nomeprocedura.
Ecco uno screenshot dell'editor



Al solito, un esempio vale più di mille parole, a questo link potrete trovare una soluzione contenente il codice sorgente dell'editor html. Per chi volesse saperne di più sulla proprietà ObjectForScripting del controllo WebBrowser ecco il link alla pagina di msdn

sabato 8 settembre 2007

un filtro veloce..

Quando mi capita di realizzare qualche gestionale, la richiesta che mi sento fare di più dai clienti è quella di poter lavorare senza staccare le mani dalla tastiera.. odiano il mouse. Per far fronte a tale richiesta, qualche tempo fa, ho realizzato uno UserControl facilmente riadattabile che permette la ricerca e la selezione di elementi ( ad esempio clienti, articoli etc etc ) all'interno di una DataGridView in modo molto rapido.

Nell'immagine qui in alto possiamo vedere una lista di clienti filtrata per "Codice Cliente" e "Nome Cliente". Mentre si digitano le parole chiave con le quali filtrare l'elenco di clienti, è possibile muoversi all'interno dell'elenco filtrato usando le freccie su e giù senza dover spostare il focus sulla DataGridView. A questo link potrete scaricare una soluzione vb.net 2005 contenente il codice delle UserControl. Qui brevemente mi limiterò ad illustrare le idee sulle quali si basa il filtro. Le due caselle di testo "Codice Cliente" e "Nome Cliente" sono il cuore del filtro, attraverso di esse si deve poter filtrare i clienti e contestualmente muoversi all'interno dell'elenco filtrato. Per ora puntiamo la nostra attenzione sulla parte di scorrimento e selezione del filtro. Entrambe le sopracitate caselle di testo sono istanze della classe bcc_TextBox, una classe che eredita da TextBox La classe bcc_TextBox contiene l'override della funzione ProcessCmdKey attraverso la quale è possibile intercettare la pressione di ogni tasto all'interno del controllo TextBox. I tasti che ci interessa intercettare e gestire sono ovviamente i tasti freccia SU e GIU e il tasto INVIO, per tutti gli altri vogliamo che la classe bcc_TextBox si comporti come un normale TextBox. Ecco il frammento di codice qui di seguito:

Protected Overrides Function ProcessCmdKey(ByRef msg As System.Windows.Forms.Message, ByVal keyData As System.Windows.Forms.Keys) As Boolean

Select Case keyData

Case Keys.Down, Keys.Up, Keys.Return, Keys.Enter

RaiseEvent OneKeyPressed(keyData)
Return True

Case Else

Return MyBase.ProcessCmdKey(msg, keyData)

End Select

End Function

Come si può vedere dal codice, la classe bcc_TextBox lancia un evento OneKeyPressed per notificare all'esterno l'avvenuta pressione di uno dei tasti che si vuole gestire passando come parametro proprio il codice del tasto premuto. Sarà poi lo UserControl che si occuperà di gestire l'evento in questione attraverso il seguente handler:

Private Sub m_OneKeyPressed(ByVal key As System.Windows.Forms.Keys)

If IsNothing(dgv_Clienti.DataSource) Then Exit Sub

Dim cm As CurrencyManager
cm = Me.BindingContext(dgv_Clienti.DataSource)


If cm.Position = -1 Then Exit Sub

Select Case key

Case Keys.Up

If cm.Position = 0 Then
cm.Position = cm.List.Count - 1
Else
cm.Position -= 1
End If


Case Keys.Down

If cm.Position = cm.List.Count - 1 Then
cm.Position = 0
Else
cm.Position += 1
End If

Case Keys.Return, Keys.Enter

m_SelezionaCliente()

End Select

End Sub

Per spostarsi tra le righe del DataGridView si è usato un CurrencyManager, sfruttandone la proprietà Position è semplicissimo muoversi tra l'elenco filtrato dei clienti . Il CurrencyManager viene anche impiegato per restituire il cliente selezionato.

Private Sub m_SelezionaCliente()

Dim drv As DataRowView
Dim cm As CurrencyManager

cm = Me.BindingContext(dgv_Clienti.DataSource)
If cm.Position = -1 Then Exit Sub

drv = CType(cm.Current, DataRowView)

RaiseEvent ClienteSelezionato(drv("Id"), drv("Nome"))

End Sub

Resta da spiegare come avviene il filtraggio dei dati. Nessuna particolare novità, si è settato un evento TextChanged sulle due caselle di testo e si è composta una stringa con la quale filtrare il DataView usato come DataSource del DataGridView

Private Sub txt_DoFilter(ByVal sender As System.Object, ByVal e As System.EventArgs)

Try
RemoveHandler txt_CodiceCliente.TextChanged, AddressOf txt_DoFilter
RemoveHandler txt_NomeCliente.TextChanged, AddressOf txt_DoFilter

Dim dv As DataView
Dim strFilter As String = ""

dv = dgv_Clienti.DataSource

strFilter = ""

If txt_CodiceCliente.Text.Trim <> "" Then

Dim val As Int64

Try

val = CInt(txt_CodiceCliente.Text.Trim)

Catch ex As Exception

txt_CodiceCliente.Text = ""

Finally

strFilter = "( Id = " & txt_CodiceCliente.Text.Trim & " )"

End Try

End If

If txt_NomeCliente.Text.Trim <> "" Then

Dim tmp As String

tmp = txt_NomeCliente.Text.Trim
tmp = tmp.Replace("'", "")
tmp = tmp.Replace("""", "")
tmp = tmp.Replace("%", "")

If tmp <> txt_NomeCliente.Text.Trim Then txt_NomeCliente.Text = tmp

strFilter &= IIf(strFilter <> "", " AND ", "") & "(Nome Like '%" & txt_NomeCliente.Text.Trim & "%')"

End If

dv.RowFilter = strFilter
lb_ClientiTrovati.Text = dgv_Clienti.RowCount

Catch ex As Exception

Finally

AddHandler txt_CodiceCliente.TextChanged, AddressOf txt_DoFilter
AddHandler txt_NomeCliente.TextChanged, AddressOf txt_DoFilter

End Try

End Sub

printf("Hello World!..\n");

di tanto in tanto posterò qui qualche semplice soluzione
( trovata rigorosamente sotto l'effetto di litri e litri di caffè )
ai più comuni problemi di programmazione, con la speranza di
poter essere di aiuto a qualche altro caffeinomane..