Devo confessarlo, anche io sono rimasto un po' perplesso dopo l'esempio di Andrea nell'ultimo Workshop di UGIdotNET dell'altro ieri. Se ricordo bene, lui aveva creato Attribute [MinLength], [MaxLength] e così via, per decorare la sua classe e aggiungendo attributi di validazione riutilizzabili. E' due giorni che penso a come sfruttare meglio gli Attribute di .NET e ieri sera, poco prima di uscire con gli amici, ecco la rivelazione che mi è venuta dall'alto.
Nel mio ultimo post [MCAD], ho parlato di come utilizzare la classe PrintDocument per implementare all'interno della nostra applicazione funzioni di stampa. Usando le funzioni GDI+, e gestendo l'evento PrintPage, bisogna tutte le volte disegnare testo (DrawString) e grafica (DrawLine, DrawCurve e quant'altro). La trovo una cosa piuttosto noiosa: pensiamoci bene. Se sto creando la classica applicazione di fatturazione, devo creare il classico report per stampare fatture, documenti di trasporto, ordini clienti e fornitori e così via. Se continuiamo a pensarci su, vediamo come su ogni report di stampa bisogna comunque mettere il logo dell'azienda da qualche parte. Perchè fare tutte le volte l'impaginazione quando posso velocizzare il lavoro? Ecco la soluzione che ho messo in piedi questa mattina.
Ho creato una nuova classe AdvancedPrintDocument, che eredita dalla classe base PrintDocument. Il codice completo è il seguente:
using System;
using System.Drawing;
using Libba;
namespace Libba
{
[Logo("K:\\Documenti\\Immagini\\Personali Igor\\blog_igor.jpg", 70, 200)]
public class AdvancedPrintDocument : System.Drawing.Printing.PrintDocument
{
public AdvancedPrintDocument()
{
//
// TODO: aggiungere qui la logica del costruttore
//
}
protected override void OnPrintPage(System.Drawing.Printing.PrintPageEventArgs e)
{
base.OnPrintPage (e);
float posy = e.MarginBounds.Top, posx = e.MarginBounds.Left;
Attribute[] attr = Attribute.GetCustomAttributes(typeof(AdvancedPrintDocument));
foreach (Attribute a in attr)
{
if (a is Libba.Logo)
{
Logo lg = (Logo) a;
Image logo = Image.FromFile(lg.BitmapFileName, true);
#region "Calcolo delle coordinate"
if (lg.Coordinata.X == 0)
{
switch (lg.ImageAlign)
{
case ContentAlignment.BottomCenter:
{
posx = (e.PageBounds.Width - logo.PhysicalDimension.Width) / 2;
posy = e.MarginBounds.Bottom - logo.PhysicalDimension.Height;
break;
}
case ContentAlignment.BottomLeft:
{
posx = e.MarginBounds.Left;
posy = e.MarginBounds.Bottom - logo.PhysicalDimension.Height;
break;
}
case ContentAlignment.BottomRight:
{
posx = e.MarginBounds.Right - logo.PhysicalDimension.Width;
posy = e.MarginBounds.Bottom - logo.PhysicalDimension.Height;
break;
}
case ContentAlignment.MiddleCenter:
{
posx = (e.PageBounds.Width - logo.PhysicalDimension.Width) / 2;
posy = (e.PageBounds.Height - logo.PhysicalDimension.Height) / 2;
break;
}
case ContentAlignment.MiddleLeft:
{
posx = e.PageBounds.Left;
posy = (e.PageBounds.Height - logo.PhysicalDimension.Height) / 2;
break;
}
case ContentAlignment.MiddleRight:
{
posx = e.MarginBounds.Right - logo.PhysicalDimension.Width;
posy = (e.PageBounds.Height - logo.PhysicalDimension.Height) / 2;
break;
}
case ContentAlignment.TopLeft:
{
posx = e.MarginBounds.Left;
posy = e.MarginBounds.Top;
break;
}
case ContentAlignment.TopCenter:
{
posx = (e.PageBounds.Width - logo.PhysicalDimension.Width) / 2;
posy = e.MarginBounds.Top;
break;
}
case ContentAlignment.TopRight:
{
posx = e.MarginBounds.Right - logo.PhysicalDimension.Width;
posy = e.MarginBounds.Top;
break;
}
}
}
else
{
posx = lg.Coordinata.X;
posy = lg.Coordinata.Y;
}
#endregion
// disegno la bitmap sull'oggetto Graphics posizionandolo come voluto
e.Graphics.DrawImage(logo, posx, posy, logo.PhysicalDimension.Width, logo.PhysicalDimension.Height);
}
}
}
}
}
Non perdetevi troppo nell'implementazione. La cosa interessante che ho creato è l'attributo Logo, che in pratica è una classe il cui costruttore è stato overloadato 3 volte. Ecco il codice.
using System.Drawing;
namespace Libba
{
///
/// Descrizione di riepilogo per Logo.
///
public class Logo : System.Attribute
{
string bitmapFileName = string.Empty;
ContentAlignment imageAlign;
Point coordinata;
public string BitmapFileName
{
get
{
return(this.bitmapFileName);
}
set
{
this.bitmapFileName = value;
}
}
public ContentAlignment ImageAlign
{
get
{
return(this.imageAlign);
}
set
{
this.imageAlign = value;
}
}
public Point Coordinata
{
get
{
return(this.coordinata);
}
set
{
this.coordinata = value;
}
}
public Logo(string BitmapFileName)
{
this.bitmapFileName = BitmapFileName;
this.imageAlign = ContentAlignment.TopLeft;
}
public Logo(string BitmapFileName, ContentAlignment ImageAlign)
{
this.bitmapFileName = BitmapFileName;
this.imageAlign = ImageAlign;
}
public Logo(string BitmapFileName, int X, int Y)
{
this.bitmapFileName = BitmapFileName;
this.coordinata = new Point(X, Y);
}
}
}
Notate che la classe Logo eredita da System.Attribute. In breve le proprietà sono: bitmapFileName è una stringa contenente il percorso di un file bitmap sul nostro hard-disk. imageAlign è un valore dall'enum ContentAlignment che contiene l'allineamento che vogliamo dare alla nostra bitmap (TopLeft, TopRight e così via). Se non ci interessa dare un particolare allineamento, possiamo usare un oggetto coordinata per indicare la coordinata in alto a sinistra da cui vogliamo iniziare a disegnare la nostra bitmap. Il costruttore è stato ridefinito 3 volte:
- public Logo(string BitmapFileName)
- public Logo(string BitmapFileName, ContentAlignment ImageAlign)
- public Logo(string BitmapFileName, int X, int Y)
Nell'esempio sopra quindi, la nostra classe AdvancedPrintDocument è stata decorata con l'attributo Logo, passando il path dell'immagine e le coordinate (70, 200). Nell'overloading dell'evento OnPrintPage uso il metodo GetCustomAttributes per vedere se e come è stato utilizzato l'attributo custom Logo in questo caso, ovvero:
Attribute[] attr = Attribute.GetCustomAttributes(typeof(AdvancedPrintDocument));
foreach (Attribute a in attr)
{
if (a is Libba.Logo)
{
Logo lg = (Logo) a;
Image logo = Image.FromFile(lg.BitmapFileName, true);
//calcolo delle coordinate
// disegno la bitmap sull'oggetto Graphics posizionandolo come voluto
e.Graphics.DrawImage(logo, posx, posy, logo.PhysicalDimension.Width, logo.PhysicalDimension.Height);
}
}
Se il metodo GetCustomAttributes mi ritorna un Attribute di tipo Libba.Logo (che è la mia classe che eredita da System.Attribute), allora prelevo l'attribute facendo il casting al tipo Logo. Calcolo le coordinate (nel codice sopra ho riportato solo il commento) facendo uno switch sui possibili valori di imageAlign, oppure Alla fine disegno semplicemente sull'oggetto Graphics la bitmap che ho caricato da file, allineandola o posizionandola come indicato nei valori dell'attributo.
Questo che ho descritto credo sia un buon utilizzo di Custom Attribute: è sufficiente usare la nuova classe AdvancedPrintDocument per avere tutte le features offerte dalla classe base: in più, usando l'Attribute Logo possiamo impaginare un'immagine molto velocemente, posizionandola o centrandola dove vogliamo e senza troppe complicazioni. Che ne dite?