Nel capitolo precedente abbiamo impiegato penne predefinite. Ora vogliamo cercare qualcosa di più accattivante. Dopo aver inizializzato un nuovo oggetto Pen, possiamo modificarne le proprietà:
- Brush : associando un pennello a questa proprietà è possibile riempire il tratto della penna mediante tale pennello con sfumature, disegni, immagini, eccetera...
- CompoundArray : ammettiamo che l'oggetto Pen abbia una larghezza abbondante, ad esempio 10. Tutto il tratto viene riempito unfirmemente
con un colore (a meno che non abbiate modificato la proprietà Brush). Mediante questa proprietà possiamo decidere di rimpiazzare
il blocco cromatico con più strisce colorate di largezza definita. Ecco un esempio:
Dim b As New Bitmap(300, 300) Dim g As Graphics = Graphics.FromImage(b) Dim p As New Pen(Color.Black, 14) p.CompoundArray = New Single() {0, 0.2, 0.4, 0.8, 0.9, 1} g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias g.Clear(Color.White) g.DrawLine(p, 10, 10, 80, 100)
La penna lascia un tratto di larghezza 14, ma esso non è uniforma. La proprietà CompoundArray è di tipo array di Single. In questo array vanno specificate delle posizioni percentuali, che indicano l'intervallarsi di strisce e spazi. Nel codice sopra, ho specificato che da 0% a 20% della larghezza ci deve essere una linea, dal 20% al 40% uno spazio, dal 40% all'80% una linea, dall'80% al 90% uno spazio e dal 90% al 100% una linea. Il tutto ha prodotto un risultato come quello in figura. - DashStyle : permette di scegliere un differente tipo di tratteggio tra alcuni predefiniti. Eccone un esempio:
L'ultimo è stato creato modificando la proprietà DashPattern: è anch'essa un'array di Single, ma specifica la lunghezza in pixel di un tratto e di uno spazio (in figura era 5, 5, 10, 5, ossia 5px di linea, 5 di spazio, 10 di linea e 10 di spazio). Si tratta di pixel poiché il tratto si estende in lunghezza (quindi non si possono specificare valori relativi) - DashCap : determina la forma degli estremi dei punti o delle linee che costituiscono una linea tratteggiata. Ecco un esempio, combinato
con la proprietà CompoundArray:
- StartCap / EndCap : specifica la forma geometrica posta all'inizio o alla fine della linea (di tutta la linea). Simile a DashCap, ma con molte più varianti.
Oltre a SolidBrush, esistono alcuni altri pennelli con caratteristiche peculiari. Ad esempio:
- HatchBrush : riempie una superficie con una trama specificata. Qui ci sono tutte le varianti;
- LinearGradientBrush : esegue una sfumature sull'area da riempire. Potete consultare alcuni esempi nella sezione Appunti;
- TextureBrush : riempie un'area specificata con un'immagine, eventualmente ripetendola e/o allungandola. Esempio
Per mostrarvi qualche utilizzo pratico e non proprio basilare alla grafica in .NET voglio mostrare come sia possibile disegnare una userbar
simile a quelle create con Photoshop.
Per creare una userbar si seguono più o meno sempre questi passaggi:
- si prende uno sfondo di dimensioni 350x19 (formato standard), oppure si riempie quest'area con un gradiente colorato e vi si applica sopra un'altra porzione di immagine;
- si applica sullo sfondo una trama omogenea formata da righe diagonali molto sottili e ravvicinate di colore scuro;
- si crea un effetto lucido sovrapponendo all'immagine così creata una semiellisse di colore bianco, con una trasparenza del 20-30%;
- per ultimare il lavoro, si pone una scritta sulla parte destra della barra, di solito usando il font Visitor TT2 BRK.
Noi automatizzeremo tutto questo creando una classe apposita:
Namespace Userbars Class Userbar Private _BackgroundImage As Image Private _BackgroundImagePosition As Int32 Private _Text As String Private _Size As Size = New Size(350, 25) Public Property Size() As Size Get Return _Size End Get Set(ByVal value As Size) If value.Width > 0 And value.Height > 0 Then _Size = value Else _Size = New Size(350, 25) End If End Set End Property Public Property BackgroundImage() As Image Get Return _BackgroundImage End Get Set(ByVal value As Image) If value.Width > Me.Size.Width Or value.Height > Me.Size.Height Then Throw New ArgumentOutOfRangeException() Else _BackgroundImage = value End If End Set End Property Public Property BackgroundImagePosition() As Int32 Get Return _BackgroundImagePosition End Get Set(ByVal value As Int32) If value > Me.Size.Width Then Throw New ArgumentOutOfRangeException() Else _BackgroundImagePosition = value End If End Set End Property Public Property Text() As String Get Return _Text End Get Set(ByVal value As String) _Text = value End Set End Property Public Function Create() As Image Dim Result As New Bitmap(Me.Size.Width + 1, Me.Size.Height + 1) Dim G As Graphics = Graphics.FromImage(Result) 'Questi valori sono statici poiché costanti. Una 'volta inizializzati saranno sempre uguali e non 'verranno creati ulteriori oggetti ad ogni invocazione 'di Create() 'HatchBrush permette di riempire un'area con una trama 'prefissata. Noi vogliamo disegnare delle sottili righe 'scure, un motivo a cui corrisponde l'enumeratore 'indicato. 'Il colore usato è un Nero con opacità pari 'a 48: dato che il valore massimo è 255, si tratta di 'un nero al 19% di opacità. Il colore di sfondo è 'invece trasparente (come se non ci fosse). Static LinesBrush As New HatchBrush(HatchStyle.DarkUpwardDiagonal, Color.FromArgb(48, Color.Black), Color.Transparent) 'Il font usato è Visitor TT2 BRK, grandezza 11pt. '11 va molto bene per le userbar di dimensione 'standard. Se volete qualcosa di più generale, 'il font deve dipendere dall'altezza Static FontUsed As New Font("Visitor TT2 BRK", 11, FontStyle.Regular) 'Questo pennello servirà per l'effetto lucido. Dato 'che si tratta di un SolidBrush, riempirà l'area con 'un colore omogeneo, in questo caso un bianco 'trasparente Static TranspBrush As New SolidBrush(Color.FromArgb(70, Color.White)) 'Imposta la modalità di smussamento delle linee. Dato 'che questa funzione viene usata solo quando l'utente 'la richiede (quindi non molte volte al secondo), e che 'il risultato deve essere il migliore possibile, 'utilizziamo un algoritmo ad alto rendimento e 'bassa velocità di rendering. G.SmoothingMode = SmoothingMode.HighQuality 'Imposta la modalità di sovrapposizione. Poiché 'dobbiamo disegnare molte cose le una sopra alle altre, ci 'serve che i nuovi disegni non sovrascrivano quelli 'precedenti, ma vi si applichino sopra rispettandone 'le trasparenze G.CompositingMode = CompositingMode.SourceOver 'Disegna l'immagine di sfondo G.DrawImage(Me.BackgroundImage, Me.BackgroundImagePosition, 0) 'Disegna le righe su tutta l'immagine G.FillRectangle(LinesBrush, 0, 0, Me.Size.Width, Me.Size.Height) 'Applica il velo di bianco trasparente. Notate che 'utilizzo delle coordinate negative per disegnare 'l'ellisse fuori dall'immagine. In questo modo, 'noi vedremo solo la parte di ellisse che rientra 'nell'area effettivamente esistente, ossia solo metà. 'Inoltre ho messo qualche coefficiente per aggiustare 'la larghezza e rendere migliore l'aspetto G.FillEllipse(TranspBrush, -5, -Me.Size.Height + 3, Me.Size.Width + 10, CInt(Me.Size.Height * 1.5)) 'Disegna il contorno della barra G.DrawRectangle(Pens.Black, 0, 0, Me.Size.Width, Me.Size.Height) 'Calcola la dimensione del testo (in pixel) Dim TextSize As SizeF = G.MeasureString(Me.Text, FontUsed) 'Quindi disegna la stringa Text in bianco, spostata 'rispetto al margine destro in modo che il testo non 'vada fuori dall'immagine. G.DrawString(Me.Text, FontUsed, Brushes.White, Me.Size.Width - CInt(TextSize.Width) - 30, Me.Size.Height 3) 'Restituisce il risultato Return Result End Function End Class End Namespace
Ed ecco un esempio:
Come noterete, la proprietà BackgroundImagePosition ha senso solo se l'immagine è di larghezza inferiore alla userbar, ossia
nel caso in cui lo sfondo debba essere riempito con un gradiente uniforme (LinearGradientBrush). Non ho implementato questa funzionalità
nel codice, ma la lascio come esercizio. Potete usare come riferimento il mio articolo al riguardo nella sezione Appunti.
Ecco uno stupidissimo codice di esempio per disegnare un orogoloio:
Class Form1 Private ClockImage As New Bitmap(230, 230) Private G As Graphics = Graphics.FromImage(ClockImage) Private Sub tmrClock_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrClock.Tick G.Clear(Me.BackColor) Dim Time As Date = Date.Now Static BorderPen As New Pen(Color.Black, 5) Static HourPen As New Pen(Color.Red, 4) With {.EndCap = LineCap.Triangle} Static MinutePen As New Pen(Color.Blue, 2) With {.EndCap = LineCap.Triangle} Static SecondPen As New Pen(Color.Green, 1) Dim C As Point = New Point(ClockImage.Width / 2, ClockImage.Height / 2) Dim h, m, s As Single h = Time.Hour + (Time.Minute / 60) m = Time.Minute + (Time.Second / 60) s = Time.Second G.SmoothingMode = SmoothingMode.AntiAlias G.FillEllipse(Brushes.White, 5, 5, ClockImage.Width - 10, ClockImage.Height - 10) G.DrawEllipse(BorderPen, 5, 5, ClockImage.Width - 10, ClockImage.Height - 10) For I As Int32 = 0 To 11 G.FillEllipse(Brushes.Black, _ C.X + CInt(ClockImage.Width / 2.3 * Math.Cos(Math.PI / 2 - I / 6 * Math.PI)) - 1, _ C.Y - CInt(ClockImage.Width / 2.3 * Math.Sin(Math.PI / 2 - I / 6 * Math.PI)) - 1, _ 2, 2) Next G.DrawLine(HourPen, C.X, C.Y, _ C.X + CInt(ClockImage.Width / 4 * Math.Cos(Math.PI / 2 - h / 6 * Math.PI)), _ C.Y - CInt(ClockImage.Width / 4 * Math.Sin(Math.PI / 2 - h / 6 * Math.PI))) G.DrawLine(MinutePen, C.X, C.Y, _ C.X + CInt(ClockImage.Width / 3 * Math.Cos(Math.PI / 2 - m / 30 * Math.PI)), _ C.Y - CInt(ClockImage.Width / 3 * Math.Sin(Math.PI / 2 - m / 30 * Math.PI))) G.DrawLine(SecondPen, C.X, C.Y, _ C.X + CInt(ClockImage.Width / 2.2 * Math.Cos(Math.PI / 2 - s / 30 * Math.PI)), _ C.Y - CInt(ClockImage.Width / 2.2 * Math.Sin(Math.PI / 2 - s / 30 * Math.PI))) imgClock.Image = ClockImage End Sub End Class