Introduzione
Sin dai tempi del Visual Basic 1.0, Microsoft ha sempre creduto negli strumenti visuali per la costruzione dell’interfaccia utente. Questo consente a uno sviluppatore di concentrarsi sugli aspetti logici della propria applicazione piuttosto che sulla definizione da codice dell’interfaccia. Lo strumento di sviluppo consente di progettare l’interfaccia utente senza preoccuparsi troppo dell’oggettistica (termine coniato adesso... intendo dire ricordarsi i tipi, le classi, le proprietà, degli oggetti che rappresentano elementi visivi). È altrettanto vero che gli strumenti di sviluppo visuali generano comunque “codice” che a runtime utilizzerà classi, metodi e proprietà per comporre l’interfaccia utente. Famose sono le demo di apertura dei file .frm di Visual Basic 1/2/3/4/5/6 con Notepad per mostrare cosa accade dietro le quinte. Uno sviluppatore VB non si è mai dovuto preoccupare di come venivano memorizzate le informazioni di definizione dell’interfaccia utente: tra l’altro, queste erano completamente nascoste dallo strumento di sviluppo. Dall’arrivo di .NET, gli sviluppatori di applicazioni Windows (o Windows Forms, dovrei dire) possono decidere di usare uno strumento ad alta produttività come Visual Studio.NET oppure utilizzare strumenti “ignoranti” (almeno rispetto a quanto veniva prodotto con essi) per definire delle proprie classi che rappresentano i form da presentare all’utente. In questo secondo caso è necessario definire una classe, derivarla dalla classe base che rappresenta un form (per ereditarne il comportamento), definire da codice tutti gli elementi dell’interfaccia utente (tipi .NET) e associare dei propri metodi agli eventi che questi scatenano durante l’esecuzione.
Rispetto al passato, se mi passate l’affermazione, lo sviluppatore Windows Forms .NET, che utilizza uno strumento visuale come Visual Studio.NET, è comunque molto più vicino alla realtà rispetto a uno sviluppatore Visual Basic 6.0. In Visual Basic 6.0 molti degli aspetti citati sono completamente nascosti al programmatore dallo strumento di sviluppo (occorre ricorrere a NotePad per capire qualcosa del dietro le quinte... ma non si arriva a tutto comunque...). Visual Studio.NET, invece, nasconde molto meno il codice autogenerato di definizione dell’interfaccia utente. Per esempio, se definite il form più banale del mondo, che visualizza un pulsante che riempie una label, si ottiene quanto segue nell’editor di Visual Studio stesso:
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; namespace DevLeap { public class Form1 : System.Windows.Forms.Form { private System.Windows.Forms.Label label1; private System.Windows.Forms.Button button1; public Form1() { // // Required for Windows Form Designer support // InitializeComponent(); // // TODO: Add any constructor code after InitializeComponent // } private void button1_Click(object sender, System.EventArgs e) { } } }
Dal codice autogenerato si possono imparare tante cose:
· Che il nostro form è rappresentato da una serie di righe di codice presenti in un file .cs (non in un file FORM/frm o qualcosa di simile)
· Che il nostro form è una classe pubblica
· Che il nostro form è una classe che deriva da System.Windows.Forms.Form
· Che la label è un controllo definito dal tipo System.Windows.Forms.Label
· Che esiste un metodo button1_Click che gestirà l’evento Click sul bottone
Con una semplice operazione (click sui vari segni “+”) Visual Studio mostra anche il codice (nascosto ma non più di tanto) che serve a definire l’interfaccia utente, a legare l’evento Click con il nostro metodo, a definire l’entry point dell’applicazione e gestire la distruzione (Dispose) del nostro oggetto Form.
Ecco il codice completo:
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
namespace DevLeap
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Button button1;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// label1
//
this.label1.Location = new System.Drawing.Point(16, 16);
this.label1.Name = "label1";
this.label1.TabIndex = 0;
this.label1.Text = "label1";
//
// button1
//
this.button1.Location = new System.Drawing.Point(16, 40);
this.button1.Name = "button1";
this.button1.TabIndex = 1;
this.button1.Text = "button1";
this.button1.Clic += new
System.EventHandler(this.button1_Click);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.Add(this.button1);
this.Controls.Add(this.label1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private void button1_Click(object sender, System.EventArgs e)
{
}
}
}
Il fatto che tutto questo (definizione classe, definizione e posizionamento elementi interfaccia utente, associazione evento/metodo etc) sia definito in un file .cs significa che è possibile compilare questa applicazione con il compilatore C# e produrre un eseguibile perfettamente funzionante. Il ruolo di Visual Studio.NET, quindi, non è quello di consentirci di scrivere un’applicazione (potremmo benissimo fare tutto a mano) ma quello di aiutarci nella scrittura di codice che comunque sarà compilato e eseguito.
In questa riflessione volevo sottolineare il passo avanti che si è compiuto rispetto a Visual Basic 6.0: adesso sappiamo cosa stiamo usando, abbiamo più controllo sul codice, possiamo capire molto di più.
Per le applicazioni web il discorso è molto simile e molto diverso allo stesso tempo. È diverso in quanto in ASP 1/2/3 non esisteva il concetto di controlli sulla pagina, eventi associabili all’interazione dell’utente con l’interfaccia... e direi... non esisteva neanche uno strumento di sviluppo: Visual InterDev era un editor colorato di codice, non uno strumento di sviluppo. Il discorso fatto in precedenza è però anche simile, in quanto tutto il codice di definizione delle pagine aspx e dei relativi code-behind è implementabile a mano (con qualunque editor) oppure può essere generato da Visual Studio.NET. Anche in questo caso inserisco il codice di una pagina aspx che presenta la stessa interfaccia utente dell’applicazione Windows Forms:
<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="DevLeap.WebForm1" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > <html> <head> <title>WebForm1</title> </head> <body MS_POSITIONING="GridLayout"> <form id="Form1" method="post" runat="server"> <asp:Label ID="Label1" Runat="server" /> <asp:Button ID="Button1" Runat="server" /> </form> </body> </html>
E il relativo code-behind, già “esploso” nelle regioni nascoste:
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
namespace DevLeap
{
/// <summary>
/// Summary description for WebForm1.
/// </summary>
public class WebForm1 : System.Web.UI.Page
{
protected System.Web.UI.WebControls.Label Label1;
protected System.Web.UI.WebControls.Button Button1;
private void Page_Load(object sender, System.EventArgs e)
{
// Put user code to initialize the page here
}
#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.Load += new System.EventHandler(this.Page_Load);
}
#endregion
}
}
Dove voglio arrivare: in un’applicazione Windows Forms, la definizione dell’interfaccia utente sta nelle righe di codice (colore, posizionamento, font di ogni elemento), mentre in un’applicazione web ASP.NET risiede in un file descrittivo (.aspx) che utilizza. Una comodità di questo secondo approccio è che non occorre ricompilare l’applicazione a fronte di una modifica dell’interfaccia utente, si possono usare editor diversi (anche NotePad) per eseguire modifiche (o anche progettare) l’interfaccia, il codice “vero” è separato dall’interfaccia utente.
Ci sono quindi una serie di vantaggi nel modo con cui oggi si progettano le interfacce utente web in ASP.NET rispetto al modo utilizzato per le applicazioni Windows Forms.
Longhorn User Interface
In Longhorn si costruiscono le applicazioni definendone il modello a oggetti (esattamente come adesso). Per costruire il modello abbiamo però due possibilità. La prima è scrivere una serie di righe di codice in un linguaggio di programmazione (C# ad esempio), utilizzando classi diverse, ma la stessa metodologia rispetto a quanto visto nel primo esempio dell’articolo. Si definisce una classe, si esegue l’override dei metodi per personalizzarne il contenuto, si legano metodi a eventi e così via. Il secondo metodo (che sembra essere l’unico negli articoli introduttivi a Longhorn) è descrivere l’interfaccia utente utilizzando un nuovo linguaggio di markup chiamato XAML (Extensibile Application Markup Language). Si pronuncia zamel... come camel... in inglese, non in italiano, ovviamente. È l’ennesimo dialetto XML... quindi ne segue tutte le regole.
Un browser a caso (Internet Explorer) sulla piattaforma Longhorn è in grado di interpretare XAML e produrre l’interfaccia utente corrispondente. È possibile compilare il codice XAML per produrre in ultimo un esegubile da installare. Si usa quindi lo stesso paradigma delle applicazioni ASP.NET attuali: l’interfaccia utente si può comporre utilizzando un linguaggio di markup che definisce elementi (posizione, font, testo ecc) che il runtime andrà a visualizzare.
Su questo stesso sito usciranno una serie di articoli su XAML, quindi vi mostro solo un semplice esempio per capire il concetto:
<Window xmlns=http://schemas.microsoft.com/2003.xaml Visible=”true”> <SimpleText Foreground=”Red” FontSize=”12”>DevLeap e Longhorn</SimpleText> </Window>
Se salvate questo codice in un file con estensione .xaml e lo aprite in Internet Explorer (doppio click sul file?... certo... molto più semplice) ottenete l’interfaccia utente corrispondente. Compilando il file otterrete un’applicazione Windows che presenta la stessa interfaccia utente.
Anche in questo caso, come per le pagine .aspx, il runtime utilizzerà delle classi per produrre una finestra (Window) e l’oggetto che rappresenta il testo (SimpleText). Vediamo quindi qualche esempio di applicazione desktop Longhorn che utilizza direttamente le classi (un po’ come abbiamo visto nel primo esempio all’inizio del capitolo) per produrre applicazioni.
Quello che voglio dire non è che il modo migliore per definire l’interfaccia sia ricorrere al codice puro, ma che comunque esiste del codice, esistono delle classi che forse è bene conoscere, o quanto meno sapere che il paradigma .NET è sempre valido in Longhorn. Credo che quasi sempre progetteremo le interfacce utente Longhorn (dovrei dire Avalon... visto che il “sottosistema” che gestisce l’interfaccia utente ha questo nome in codice) utilizzando XAML, magari aiutandoci con uno strumento di sviluppo che faciliti il compito.
La classe Application
Un’applicazione Longhorn può essere rappresentata da una instanza della classe MSAvalon.Windows.Application. Proponendo sempre il paragone con ASP.NET si potrebbe dire che un’applicazione Longhorn sta a MSAvalon.Windows.Application come un’applicazione ASP.NET sta a System.Web.HttpApplication.
La classe Application fornisce il supporto base per una applicazione basata su form. Come in ASP.NET (più precisamente nel global.asax.cs “avviene” tutto questo) definiremo una classe che deriva da MSAvalon.Windows.Application, faremo l’override di alcuni metodi per personalizzare il comportamento base e faremo l’associazione fra gli eventi esposti e i metodi che definiremo.
Iniziamo per gradi: come in ogni applicazione .NET si inizia dalle using. Utilizzeremo tre namespace MSAvalon che contengono la definizione dell’oggetto Application base, dei controlli da posizionare sull’interfaccia utente e dei colori da utilizzare.
using System; using MSAvalon.Windows; using MSAvalon.Windows.Controls; using MSAvalon.Windows.Media;
Proseguiamo come sempre con la definizione del namespace che conterrà la nostra classe e dell’entry point dell’applicazione. Citando l’esempio a inizio capitolo, in Windows Forms facevamo così:
namespace DevLeap
{
public class Form1 : System.Windows.Forms.Form
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
}
}
namespace DevLeap
{
internal sealed class EntryClass
{
[STAThread]
Private static void Main()
{
// Lanciamo l’applicazione
}
}
}
Il metodo statico Main rappresenta l’entry point. Nel metodo main lanciamo l’applicazione, come indicato in grassetto nell’esempio: vediamo di cosa si tratta. Abbiamo affermato che la nostra applicazione sarà rappresentata da una classe che deriva da MSAvalon.Windows.Application, quindi nel nostro esempio Applicazione1.cs dobbiamo aggiungere la definizione di tale classe prima di poter far partire la nostra applicazione (e quindi vedere il codice per eseguire il metodo Run).
namespace DevLeap
{
public class Applicazione : MSAvalong.Windows.Application
{
}
internal sealed class EntryClass
{
[STAThread]
Private static void Main()
{
// Lanciamo l’applicazione
}
}
}
Questa definizione è sufficiente per ottenere un form (completamente vuoto... per ora) che esponga le classiche funzionalità (riduci a icona, ingrandisci, chiudi) di un form Windows. Prima proseguire con la definizione della “classe Applicazione”, terminiamo il metodo Main che esegue la nostra meravigliosa finestra vuota:
namespace DevLeap
{
public class Applicazione : MSAvalong.Windows.Application
{
}
internal sealed class EntryClass
{
[STAThread]
Private static void Main()
{
Applicazione app = new Applicazione();
app.Run();
}
}
}
Semplicemente, nel metodo Main si inizializza l’oggetto app di tipo Applicazione (la nostra applicazione) e si esegue il metodo ereditato Run().
Procediamo con la classe Applicazione, magari aggiungendo qualche controllo. Usiamo sempre un termine di paragone con Windows Forms. Così come Visual Studio .NET inserisce il codice per aggiungere i controlli all’interfaccia utente, dobbiamo trovare un sistema per inserire del codice durante l’inizializzazione o la partenza dell’Applicazione (notare la A maiuscola...la nostra classe applicazione). Prendiamo quindi spunto da VS.NET. Nel caso di un’applicazione Windows Forms, Visual Studio.NET aggiunge il seguente codice al costruttore della nostra classe Form1:
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
}
Il costruttore richiama il metodo InizializeComponent() dove (se vi ricordate il primo esempio citato all’inizio del capitolo) vengono inizializzati, impostati e posizionati tutti gli elementi dell’interfaccia utente.
ASP.NET invece usa un sistema diverso, in quanto esegue l’override del metodo OnInit() per richiamare il codice (sempre InitializeComponent) che definisce l’interfaccia utente.
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}
Viene poi invocato il metodo OnInit della classe base (System.Web.UI.Page in questo caso, visto che la pagina aspx... anzi il code-behind... deriva da questa classe).
Procediamo con questa seconda modalità, mostrandovi prima di tutto il codice del code-behind dell’esempio ASP.NET per farvi notare che non ci sono poi così tante differenze nello stile adottato.
namespace DevLeap
{
/// <summary>
/// Summary description for WebForm1.
/// </summary>
public class WebForm1 : System.Web.UI.Page
{
protected System.Web.UI.WebControls.Label Label1;
protected System.Web.UI.WebControls.Button Button1;
private void Page_Load(object sender, System.EventArgs e)
{
// Put user code to initialize the page here
}
#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.Load += new System.EventHandler(this.Page_Load);
}
#endregion
}
}
Ecco il file Applicazione.cs completo:
namespace DevLeap
{
public class Applicazione : MSAvalong.Windows.Application
{
MSAvalon.Windows.Controls.SimpleText label1;
MSAvalon.Windows.Window mainWindow;
}
internal sealed class EntryClass
{
[STAThread]
Private static void Main()
{
Applicazione app = new Applicazione();
app.Run();
}
}
}
La prima differenza riguarda le definizioni degli oggetti. In modo identico vengono definiti gli oggetti che rapprensentano l’interfaccia utente (nel nostro caso label1). Al contrario di ASP.NET, viene definito anche l’oggetto contenitore dei controlli (mainWindow). Questo è necessario in quanto stiamo definendo l’applicazione che giustamente conterrà un form (cioè una Window), mentre in ASP.NET siamo già all’interno della classe pagina. Lo stesso paragone si applica a una Windows Form, al cui interno verranno definiti gli elementi (label, pulsanti, etc): anche in questo caso, infatti, il codice è “dentro” la classe Form1 che rappresenta già un form Windows.
Procediamo con l’override del metodo di inizializzazione. In ASP.NET viene eseguito l’override del metodo OnInit, in un’applicazione Longhorn si utilizza il metodo OnStartingUp che riceve come argomento StartingUpCancelEventArgs.
Ecco Applicazione1.cs con questa aggiunta segnata in grassetto:
namespace DevLeap
{
public class Applicazione : MSAvalong.Windows.Application
{
MSAvalon.Windows.Controls.SimpleText label1;
MSAvalon.Windows.Window mainWindow;
protected override void OnStartingUp(StartingUpCancelEventArgs e)
{
Base.OnStartingUp();
VisualizzaForm();
}
}
internal sealed class EntryClass
{
[STAThread]
Private static void Main()
{
Applicazione app = new Applicazione();
app.Run();
}
}
}
Dopo aver lanciato il metodo OnStartingUp della classe base, chiamiamo un nostro metodo (è l’analogo di quanto Visual Studio.NET fa con InizializeComponent) che creerà il controllo SimpleText valorizzandone le proprietà. A differenza di quanto accade in Windows Forms, dovremmo anche creare la finestra (mainWindow) e visualizzarla. Ricordatevi che siamo all’interno di un’applicazione Longhorn, non in una finestra di un’applicazione Longhorn.
namespace DevLeap
{
public class Applicazione : MSAvalong.Windows.Application
{
MSAvalon.Windows.Controls.SimpleText label1;
MSAvalon.Windows.Window mainWindow;
protected override void OnStartingUp(StartingUpCancelEventArgs e)
{
Base.OnStartingUp();
VisualizzaForm();
}
private void VisualizzaForm()
{
mainWindow = new MSAvalon.Windows.Window();
label1 = new MSAvalon.Windows.Controls.SimpleText();
label1.Text = “DevLeap e Longhorn”;
label1.FontSize = new FontSize(12, FontSizeType.Point);
mainWindow.Children.Add(label1);
mainWindow.Show();
}
}
internal sealed class EntryClass
{
[STAThread]
Private static void Main()
{
Applicazione app = new Applicazione();
app.Run();
}
}
}
Se non ho dimenticato nessun “;” il codice può essere compilato con il compilatore C#. Ricordatevi di referenziare le librerie di Longhorn. Per default vengono installate nella directory c:\windows\Microsoft.NET\Windows\v.6.0.4030. Potreste aver bisogno di cambiare la versione se lavorate con una build diversa dalla mia. Le librerie da referenziare sono PresentationCore.dll, PresentationFramework.dll e WindowsBase.dll.
Ecco la sintassi del comando CSC:
csc /r:c:\windows\Microsoft.NET\Windows\v.6.0.4030\PresentationCore.dll
c:\windows\Microsoft.NET\Windows\v.6.0.4030\PresentationFramework.dll
c:\windows\Microsoft.NET\Windows\v.6.0.4030\WindowsBase.dll
Applicazione1.cs
Come avete visto, in Longhorn si lavora con le idee presenti in ASP.NET e WindowsForm. L’importante è capire che stiamo sempre definendo classi .NET, quindi override, associazione di metodi a eventi e definizione di oggetti sono identici.
Avremmo potuto lavorare totalmente alla Windows Forms, associando un nostro metodo all’evento StartingUp per intervenire durante al partenza dell’applicazione invece di eseguire l’override del metodo OnStartinUp.
Ecco questa variante:
namespace DevLeap
{
public class Applicazione : MSAvalong.Windows.Application
{
MSAvalon.Windows.Controls.SimpleText label1;
MSAvalon.Windows.Window mainWindow;
private void VisualizzaForm()
{
mainWindow = new MSAvalon.Windows.Window();
label1 = new MSAvalon.Windows.Controls.SimpleText();
label1.Text = “DevLeap e Longhorn”;
label1.FontSize = new FontSize(12, FontSizeType.Point);
mainWindow.Children.Add(label1);
mainWindow.Show();
}
}
internal sealed class EntryClass
{
[STAThread]
Private static void Main()
{
Applicazione app = new Applicazione();
app.StartingUp += new
Application.StartingUpEventHandler(VisualizzaForm);
app.Run();
}
}
}
Nel prossimo articolo vedremo un’applicazione più o meno identica ma basata sulla classe NavigationApplication. Tale classe deriva da MSAvalon.Windows.Application, ma aggiunge funzionalità di navigazione all’applicazione stessa, che sarà formata da più “pagine” anziché da un solo form.