Transcript Visual C# 2

MASSIMO UBERTINI
VISUAL C#
WWW.UBERTINI.IT
MODULO 7
WINDOWS
Applicazioni Windows
Equazione di 1°
Applicazioni Windows Forms
Equazione di 1°
Grafica
Visual C#
um 1 di 202
APPLICAZIONI WINDOWS
INTRODUZIONE
Alla base di un’interfaccia grafica vi sono quattro elementi fondamentali.
1. La finestra grafica, form: associata all’applicazione.
2. Gli oggetti visuali, controlli: contenuti nella finestra grafica.
3. Gli eventi generati dai controlli.
4. I gestori di eventi: sono metodi che gestiscono gli eventi.
In questo modello, il dialogo tra l’utente l’applicazione non avviene mediante metodi
d’ingresso e di uscita dei dati ma attraverso i controlli che costituiscono l’interfaccia.
Essi sono di vari tipi, in base alla natura della comunicazione che svolgono con l’utente:
acquisire una sequenza di caratteri, rispondere al clic del mouse, visualizzare
un’immagine, un’animazione, una serie di valori in forma di lista o di tabella.
CONTROLLI
Sono oggetti visuali in grado di “visualizzare se stessi” sullo schermo.
Il programmatore deve compiere le seguenti azioni.
1. Creare il controllo.
2. Definirne l’aspetto, impostando le sue proprietà.
3. Definire e associare dei metodi agli eventi del controllo che si desidera gestire.
4. Aggiungere il controllo all’interfaccia.
Dopodiché esso sarà sempre visibile e in grado di ricevere le azioni dell’utente, almeno fin
quando non sarà eliminato dall’interfaccia oppure temporaneamente reso invisibile o
disabilitato.
Visual C#
um 2 di 202
I tipi di controlli di un’interfaccia grafica sono organizzati in una gerarchia, alla base sta il
tipo Control.
La posizione e le dimensioni di qualsiasi controllo sono definite in relazione all’area
cliente del form che li contiene.
L’area cliente è rappresentata da un piano cartesiano la cui origine si trova nell’angolo in
alto a sinistra, al suo interno ogni controllo occupa una zona rettangolare, l’area controllo,
di dimensioni misurate in pixel.
La parte di controllo che eccede le dimensioni dell’area cliente non è visualizzata.
Visual C#
um 3 di 202
La posizione di un controllo nell’area cliente può essere espressa in due modi.
1. Attraverso la coppia di proprietà Left (ascissa) e Top (ordinata).
2. Attraverso la proprietà Location che appartiene ad un particolare tipo di dato: il Point.
In entrambi casi, la posizione si riferisce all’angolo in altro a sinistra dell’area controllo.
Quando un controllo è creato, sia la posizione sia le dimensioni sono impostate a dei valori
predefiniti: Left e Top sempre a 0; Width e Height in base al tipo di controllo.
Il programmatore le reimposta in fase di costruzione dell’interfaccia e all’interno del
costruttore del form ma nulla impedisce di modificarle in qualsiasi punto del codice.
A quale controllo, tra tutti quelli inseriti nel form, sono inviati i codici dei caratteri digitati
dall’utente?
La risposta è: al controllo attualmente selezionato, quello che al momento detiene il focus.
Perché l’interfaccia sia effettivamente funzionale, dev’essere possibile spostare il focus da
un controllo all’altro.
 Premendo il tasto TAB (SHIFT TAB): il focus si sposta al controllo successivo
(precedente), nell’ordine di tabulazione, il quale dipende, per ogni controllo, dal valore
della proprietà TabIndex.
 Cliccando sul controllo con il mouse.
Dichiarare e creare il controllo
I controlli sono oggetti e dunque è possibile accedere a essi attraverso delle variabili ma
soltanto dopo che sono stati creati.
Ogni controllo, come qualsiasi altro oggetto, appartiene ad un determinato tipo o classe.
Per dichiarare e creare un pulsante di nome btnPulsante si deve scrivere il codice
seguente.
Button bntPulsante = new Button();
Definire l’aspetto del controllo
Per aspetto di un controllo s’intende le sue caratteristiche visuali, come ad esempio:
posizione, altezza, larghezza e colore.
Le caratteristiche o attributi di un controllo sono definite assegnando gli opportuni valori
alle sue proprietà; ad esempio, per definire il testo di un pulsante s’imposta il valore della
proprietà Text che è di tipo stringa.
bntPulsante.Text = "Conferma";
I controlli sono disegnati in fase di run-time.
È buona regola di programmazione anteporre al nome del controllo il suo tipo.
 frm per gli oggetti della classe Form; ad esempio, frmInserimentoDati.
 txt per gli oggetti della classe TextBox, caselle di testo; ad esempio, txtCoefficienteA.
 lbl per gli oggetti della classe Label, etichette; ad esempio, lblPromptA.
 btn per gli oggetti della classe Button, pulsanti; ad esempio, btnCalcola.
Controllo
DataGrid
GridView
ImageButton
Hyperlink
DropDownList
ListBox
Visual C#
Prefisso
dg
gv
iBtn
lnk
ddl
lst
Esempio
dgResults
gvResults2
iBtnSave
lnkHomePage
ddlCompany
lstCompany
um 4 di 202
DataList
DataSet
DataTable
DataRow
Repeater
Checkbox
CheckBoxList
RadioButton
RadioButtonList
Image
Panel
PlaceHolder
Calendar
AdRotator
Table
dLst
ds
dt
dr
rep
chk
chk
rBtn
rBtn
img
pnl
plh
txt
adr
tbl
dlstAddress
dsInvoices
dtClients
drUser
repSection
chkMailList
chkAddress
rBtnSex
rBtnAgeGroup
imgLogo
pnlSevtion
plhHeader
txtMyDate
adrBanner
tblResults
Associare dei metodi agli eventi del controllo
Ogni controllo definisce, il termine tecnico è pubblica, un certo numero di eventi di varia
natura, molti dei quali sono generati in risposta alle azioni dell’utente.
Se ad esempio, si desidera che in risposta al clic del mouse su un pulsante l’applicazione
svolga un determinato compito, è necessario scrivere un gestore di evento, un metodo che
è eseguito automaticamente in risposta all’evento al quale è attaccato, per esempio
l’evento Click del pulsante in questione.
private void bntPulsante_Click(object sender, EventArgs e)
Il metodo deve definire due parametri e non ritornare alcun valore.
sender, il mandante, rappresenta un riferimento al controllo che ha sollevato l’evento.
Le informazioni che qualificano l’evento sono contenute nel parametro e, il cui tipo dipende
dal tipo dell’evento gestito.
Nel caso l’evento sia di sola notifica, non necessita d’informazioni aggiuntive, il tipo in
questione è: EventArgs.
In questo caso, ovviamente, il parametro e può essere ignorato.
In alcuni casi, la semplice notifica è sufficiente, ad esempio per l’evento Click non importa
conoscere altre informazioni se non quella che l’utente ha cliccato con il mouse sul
controllo.
S il cursore si trova su un TextBox, l’utente preme un tasto, è generato l’evento KeyPress
che non implica la sola notifica che un tasto è stato premuto ma anche l’informazione di
quale tasto è stato premuto.
Il concetto di evento acquista significati distinti, anche se collegati, in base alla prospettiva
dalla quale si considera tale termine.
Evento reale
S’intende qualcosa che accade ed è potenzialmente in grado d’influenzare lo stato di
esecuzione dell’applicazione, ad esempio, la pressione di un tasto o del mouse.
Gli eventi reali non corrispondono in modo diretto alle azioni dell’utente, ad esempio,
l’apertura e la chiusura del form.
Un evento reale è dunque qualcosa che è rilevato dall’interfaccia, nella fattispecie da uno
o più controlli.
A ogni evento reale corrisponde una risposta, anche nulla, da parte dei controlli che lo
ricevono; tale risposta è chiamata predefinita poiché i controlli sono in grado di metterla in
atto senza che il programmatore debba scrivere alcuna riga di codice.
Visual C#
um 5 di 202
Evento Visual C#
È generato dai controlli, il termine tecnico è innescato, lanciato o notificato.
Lo scopo è di consentire al programmatore di scrivere le risposte dell’applicazione agli
eventi reali, in aggiunta o in alternativa, alle risposte predefinite messe in atto dai controlli.
Dopo aver ricevuto l’evento reale, il controllo può compiere le seguenti azioni.
 Mettere comunque in atto la risposta predefinita e poi sollevare l’evento Visual C#.
 Sollevare l’evento Visual C# prima di mettere in atto la risposta predefinita.
Un evento Visual C# rappresenta un meccanismo che consente al programmatore di
“agganciare” il proprio codice, il gestore di evento, agli eventi reali ma solo se lo desidera.
È una delega: il controllo delega il gestore di evento per rispondere all’evento reale che ha
ricevuto.
Se si volesse eseguire un’azione in fase di caricamento del form, occorre creare un
gestore di eventi, chiamato event handler, per l’evento Load del form.
Occorre aggiungere il seguente codice nel costruttore della classe.
this.Load += new EventHandler(Form1_Load);
Per installare un event handler, occorre specificare il nome dell’oggetto che espone
l’evento: in questo caso si è usato this che rappresenta la classe corrente, il form; di
seguito, dopo il punto, s’indica il nome dell’evento da gestire.
Visual C#
um 6 di 202
L’operatore (+=) indica che si sta aggiungendo un gestore per l’evento.
Si usa (+=) e non solo (=), perché è possibile specificare più gestori per lo stesso evento.
FORM
Il ciclo di vita di un’applicazione Windows parte con la creazione del form principale e la
sua visualizzazione a schermo e termina con la pulitura delle risorse impegnate e la
distruzione del form.
Il controllo Form è l’elemento principale dell’interfaccia e fa da contenitore a tutti gli altri
controlli; includere nel progetto il namespace System.Windows.Forms.
In genere, l’applicazione ha un form di avvio o form principale.
using System.Windows.Forms;
class PrimaForm : Form {
public PrimaForm()
{}
static void Main(string[] args)
{ Application.Run(new PrimaForm()); }
}
Compilare ed eseguire l’applicazione, per default il compilatore usa l’opzione /target:exe.
In fase di test e debug è utile poiché è possibile stampare sulla console le informazioni su
quanto avviene nell’applicazione in run-time.
Per non visualizzare la console usare l’opzione /target:winexe, in Visual Studio, fare clic su
Visual C#
um 7 di 202
Progetto/Proprietà di esempio…, selezionare la scheda Applicazione e modificare la
proprietà Tipo di output: Applicazione Windows.
Eseguire l’applicazione nuovamente.
PrimaForm è il nome della classe principale dell’applicazione che contiene il metodo Main.
La notazione : Form indica che la classe PrimaForm deriva dalla classe Form, la quale
designa il tipo del controllo principale di un’interfaccia Windows.
public PrimaForm() rappresenta il costruttore della classe PrimaForm.
L’istruzione dentro il Main crea un oggetto della classe Form e dunque un controllo form e
lo passa come parametro al metodo Run della classe Application.
Il metodo Run che prende come unico parametro in ingresso un’istanza di una classe
derivata da Form, svolge la funzione di avvio dell’applicazione; visualizza, infatti, il form
principale sullo schermo; prima di questo, però dev’essere creato, ciò è fatto, come per
ogni oggetto, attraverso l’operatore new.
La classe Application fornisce il metodo Exit che provoca la chiusura del form e la fine
dell’applicazione ma è buona regola di programmazione usare il metodo Close sulla
finestra principale per rilasciare le risorse occupate dall’applicazione stessa.
using System.Windows.Forms;
class PrimaForm : Form {
public PrimaForm()
{}
static void Main(string[] args)
{ PrimaForm f = new PrimaForm();
f.Show();
}
}
L’istruzione new crea un’istanza di PrimaForm ma non è sufficiente a visualizzarla sullo
schermo perché bisogna invocare il metodo Show, un’alternativa è l’assegnazione del
valore true alla proprietà Visible; il metodo per nascondere ma non distruggere, è Hide.
La finestra creata sarà una finestra senza controllo e senza titolo ma non si fa in tempo a
vederla a schermo perché subito dopo la chiamata del metodo Show, l’esecuzione del
codice ritorna al Main e l’applicazione termina.
Per questo motivo si deve usare Application.Run per il form principale dell’applicazione,
Visual C#
um 8 di 202
mentre per i form successivi si usa il metodo Show.
using System.Windows.Forms;
class PrimaForm : Form {
public PrimaForm() {
Form frmForm2 = new Form();
frmForm2.Text = "Finestra due";
frmForm2.Show();
Form frmForm3 = new Form();
frmForm3.Text = "Finestra tre";
frmForm3.Visible = true;
}
static void Main(string[] args)
{ PrimaForm f = new PrimaForm();
f.Text = "Finestra principale";
Application.Run(f);
}
}
Chiudendo il form principale, l’applicazione termina chiudendo tutti i form aperti, mentre i
form secondari che sono indipendenti, possono essere chiusi senza che l’applicazione ne
risenta.
In questo caso, la finestra è visualizzata ma una volta chiusa, l’applicazione è bloccata.
using System;
using System.Windows.Forms;
class PrimaForm : Form {
public PrimaForm()
{}
static void Main(string[] args)
{ PrimaForm f = new PrimaForm();
f.Text = "Finestra principale";
Application.Run(f);
Console.Write("Non riesco ad avanzare ...");
Console.ReadKey();
}
}
Esempio, creare un effetto trasparenza con la proprietà Opacity.
using System.Windows.Forms;
class PrimaForm : Form {
public void ShowSplash() {
this.Opacity = 0.0;
this.Visible = true;
for (double i = 0; i <= 1.0; i += 0.05) {
Visual C#
um 9 di 202
this.Opacity = i;
System.Threading.Thread.Sleep(100);
// si ferma per 100 millisecondi
}
this.Opacity = 1.0;
}
static void Main(string[] args)
{ PrimaForm f = new PrimaForm();
f.Text = "Finestra principale";
f.ShowSplash();
}
}
Esempio: progettare un form che aspetta.
using System.Threading;
using System.Windows.Forms;
using System.Drawing;
class PrimaForm : Form {
public PrimaForm() {
Text = "Aspetta";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
// ritardo di 3 secondi
Thread.Sleep(3000);
Text = "Thread e aspetta...";
}
static void Main(string[] args)
{ Application.Run(new PrimaForm()); }
}
Esempio, aggiungere effetti speciali ad una finestra.
Si utilizza la funzione AnimateWindow, esportata dalla libreria di sistema USER32.DLL
che specifica l’handle di una finestra; aggiungere e combinare tra loro una serie di effetti
speciali quali dissolvenza, scorrimento orizzontale e verticale, sia al caricamento della
finestra sia alla sua chiusura, semplicemente passando gli opportuni flag.
using System;
using System.Windows.Forms;
using System.Drawing;
class MainForm : Form {
// funzione per animare una finestra, esportata dalla libreria user32.dll
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern bool AnimateWindow(IntPtr hwnd, int time,AnimateWindowFlags flags);
// flag utilizzabili nella funziona AnimateWindow, possono essere combinati
enum AnimateWindowFlags {
HorPositive= 0x00000001,
// anima la finestra da sx a dx con AnimationSlide
HorNegative = 0x00000002,
// anima la finestra da dx a sx con AnimationSlide
VerPositive = 0x00000004,
// anima la finestra dall'alto verso il basso
VerNegative = 0x00000008,
// anima la finestra dal basso vero l'alto
Visual C#
um 10 di 202
Center = 0x00000010,
Hide = 0x00010000,
Activate = 0x00020000,
Slide = 0x00040000,
Blend = 0x00080000
// centra la finestra
// nasconde la finestra
// attiva la finestra
// usa l'animazione slide
// attiva un effetto fading
}
public MainForm() {
Text = "Effetti speciali";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
AnimateWindow(Handle, 500, AnimateWindowFlags.Activate |
AnimateWindowFlags.Blend);
AnimateWindow(Handle, 700, AnimateWindowFlags.Hide |
AnimateWindowFlags.Slide | AnimateWindowFlags.Center);
AnimateWindow(Handle, 500, AnimateWindowFlags.Slide |
AnimateWindowFlags.HorPositive);
AnimateWindow(Handle, 500, AnimateWindowFlags.Hide |
AnimateWindowFlags.Slide | AnimateWindowFlags.HorNegative |
AnimateWindowFlags.VerPositive);
AnimateWindow(Handle, 500, AnimateWindowFlags.Activate |
AnimateWindowFlags.Slide | AnimateWindowFlags.HorPositive |
AnimateWindowFlags.VerNegative);
}
static void Main(string[] args)
{ Application.Run(new MainForm()); }
}
Proprietà e metodi
f.StartPosition=FormStartPosition.CenterParent;
// centra rispetto al form padre
f.Location=new Point(10,10);
// posizione sullo schermo
// impostano dimensione e posizione della form
f.Bounds = new Rectangle(10, 10, 300, 400);
// x, y, larghezza e altezza
Point p = new Point(10, 10);
Size s = new Size(300, 400);
f.DesktopBounds = new Rectangle(p, s);
// equivalente
// l’enumerazione FormBorderStyle permette di definire i bordi di una form
public SplashForm() {
this.FormBorderStyle = FormBorderStyle.None;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.StartPosition = FormStartPosition.CenterScreen;
this.ControlBox = false;
}
Aggiungere controlli
Aggiungere un controllo all’interfaccia significa aggiungerlo al form.
Questo mantiene internamente una collezione di controlli chiamata Controls, la quale
espone un metodo chiamato Add per l’inserimento dei controlli.
Ad esempio, per aggiungere il pulsante button1 al form si scrive il codice seguente.
form1.Controls.Add(button1);
Solo dopo l’invocazione di Add, il pulsante appartiene realmente all’interfaccia ed è in
grado di ricevere le azioni dell’utente.
Visual C#
um 11 di 202
Per rimuovere il controllo basta invocare il metodo: Controls.Remove(controllo);.
È possibile usare la proprietà Parent della classe Control.
Button button1 = new Button();
button1.Text = "Conferma";
button1.Location = new Point(10,10);
button1.Parent = form1;
Assegnare alla proprietà button1.Parent un form form1 è equivalente all’uso del metodo
form1.Controls.Add(button1), mentre assegnando il valore null alla proprietà Parent, si
rimuove il controllo dalla collezione Controls del form Parent.
È possibile creare array di controlli e aggiungerli con il metodo AddRange.
TextBox[] caselle=new TextBox[7];
form1.Controls.Addrange(caselle);
FINESTRE DI DIALOGO
L’evento che permette di caricare un form nella memoria è Load ma non lo visualizza sullo
schermo, allora si usano i metodi Show e Showdialog.
Ci sono due modi per aprire un form.
1. Modale: significa che non si può passare ad altri form finché non si chiude il form
corrente in cui si è posizionati, sono utili dove si elaborano dei dati e si vuole avere la
certezza che una sola operazione di gestione possa essere fatta sui dati.
2. Non Modale: passare da un form all’altro senza problemi.
new frmDati().ShowDialog();
new frmDati().Show();
// apro in modalità modale
// apro in modalità non modale
Quando con Show si visualizza un form non modale, le istruzioni successive alla chiamata
del metodo sono eseguite immediatamente dopo la visualizzazione del form, mentre nel
caso di form modale il codice successivo non è eseguito finché il form non è chiuso.
Quando con Showdialog si visualizza un form modale, l’input dalla tastiera o dal mouse è
valido solo relativamente agli oggetti presenti sul form, per effettuare input da un altro form
il form modale dev’essere scaricato.
Per scaricare il form dalla memoria si usa l’evento FormClosing che include Finalize.
Il metodo Hide permette di nascondere un form senza scaricarlo dalla memoria: maggiore
velocità, il metodo Move: permette di muovere un form ridimensionandolo.
Esempio, per aggiungere il pulsante button1 al form si scrive il codice seguente, solo dopo
l’invocazione di Add, il pulsante appartiene realmente all’interfaccia ed è in grado di
ricevere le azioni dell’utente.
using System;
using System.Windows.Forms;
using System.Drawing;
class PrimaForm : Form {
public PrimaForm() {
Text = "Due pannelli";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
}
static void Main(string[] args) {
PrimaForm form1 = new PrimaForm();
Visual C#
um 12 di 202
form1.Text = "Finestra modale";
Button button1 = new Button();
Button button2 = new Button();
button1.Text = "Conferma";
button1.Location = new Point(10, 10);
button2.Text = "Esci";
button2.Location = new Point(button1.Left, button1.Height + button1.Top + 10);
form1.AcceptButton = button1;
form1.CancelButton = button2;
form1.Controls.Add(button1);
form1.Controls.Add(button2);
form1.ShowDialog();
}
}
TEXTBOX
Le caselle di testo svolgono l’analoga funzione del metodo ReadLine, l’acquisizione di
stringhe di caratteri; l’utente può digitare caratteri in un TextBox soltanto se è selezionato,
dentro di esso lampeggia il cursore testo; si dice che possiede il focus.
Esempio, casella di testo multi linea.
using System.Windows.Forms;
using System.Drawing;
class PrimaForm : Form {
TextBox txtMultilinea;
public PrimaForm() {
Text = "TextBox Multilinea";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
txtMultilinea = new TextBox();
txtMultilinea.Location = new Point(10, 10);
txtMultilinea.Multiline = true;
txtMultilinea.ScrollBars = ScrollBars.Vertical;
txtMultilinea.WordWrap = true;
txtMultilinea.Lines = new string[] { "Linea1", "Linea2", "Linea3" };
Controls.Add(txtMultilinea);
}
static void Main(string[] args)
{ Application.Run(new PrimaForm()); }
}
Visual C#
um 13 di 202
Esempio, casella di testo per password.
using System.Windows.Forms;
using System.Drawing;
class PrimaForm : Form {
TextBox txtPassword;
public PrimaForm() {
Text = "Password";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
txtPassword = new TextBox();
txtPassword.Location = new Point(10, 10);
txtPassword.PasswordChar = '*';
Controls.Add(txtPassword);
}
static void Main(string[] args)
{ Application.Run(new PrimaForm()); }
}
RICHTEXTBOX
Funzionalità avanzate d’immissione e modifica testo in formato RTF (Rich Text Format).
using System.Windows.Forms;
using System.Drawing;
class PrimaForm : Form {
RichTextBox richTextBox1;
public PrimaForm() {
Text = "Word Processor";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
richTextBox1 = new RichTextBox();
Controls.Add(richTextBox1);
richTextBox1.Dock = DockStyle.Fill;
// carica il file, se non esiste genera un’eccezione
richTextBox1.LoadFile("F:\\documento.rtf");
// cerca la stringa hello
richTextBox1.Find("Ciao");
// colora la seleziona e cambia il font
richTextBox1.SelectionFont = new Font("Verdana", 12, FontStyle.Bold);
richTextBox1.SelectionColor = Color.Red;
// salva il file
richTextBox1.SaveFile("F:\\documento.rtf", RichTextBoxStreamType.RichText);
}
static void Main(string[] args)
{ Application.Run(new PrimaForm()); }
}
Visual C#
um 14 di 202
LABEL
Le etichette svolgono la stessaa funzione del metodo WriteLine, la visualizzazione di
stringhe di caratteri e quindi non devono ricevere le azioni dell’utente; qualificano i controlli
TextBox, specificando le informazioni da visualizzare e/o inserire.
using System.Windows.Forms;
using System.Drawing;
class PrimaForm : Form {
Label lblPrompt1;
Label lnlPrompt2;
public PrimaForm() {
// imposta il testo della barra del titolo
Text = "Finestra di prova";
// imposta l'icona dell'applicazione
Icon = new Icon("max.ico");
ForeColor = Color.Red;
// dimensione massima e minima alla form
MinimumSize = new Size(50, 50);
MaximumSize = new Size(250, 150);
// determina la posizione iniziale del form
StartPosition = FormStartPosition.CenterScreen;
lblPrompt1 = new Label();
lblPrompt1.Location = new Point(50, 50);
lblPrompt1.Text = "Ciao, mondo!";
lblPrompt1.ForeColor = Color.Green;
Controls.Add(lblPrompt1);
lnlPrompt2 = new Label();
lnlPrompt2.Location = new Point(50, 80);
lnlPrompt2.Text = "Andrea Sperelli";
Controls.Add(lnlPrompt2);
}
static void Main(string[] args)
{ Application.Run(new PrimaForm()); }
}
Visual C#
um 15 di 202
Esempio.
using System.Windows.Forms;
using System.Drawing;
class PrimaForm : Form {
Label lblPrompt1;
Label lblPrompt2;
TextBox txtTesto1;
TextBox txtTesto2;
public PrimaForm() {
Text = "Label & TextBox";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
lblPrompt1 = new Label();
lblPrompt1.Location = new Point(30, 50);
lblPrompt1.Text = "&Testo di prova 1";
lblPrompt1.ForeColor = Color.White;
lblPrompt1.BackColor = Color.Blue;
lblPrompt1.AutoSize = true;
Controls.Add(lblPrompt1);
txtTesto1 = new TextBox();
txtTesto1.Location = new Point(30, 70);
txtTesto1.Text = "7777";
txtTesto1.TextAlign = HorizontalAlignment.Right;
txtTesto1.BackColor = Color.Aqua;
txtTesto1.ReadOnly = true;
Controls.Add(txtTesto1);
lblPrompt2 = new Label();
lblPrompt2.Location = new Point(150, 50);
lblPrompt2.Size = new Size(100, 50);
lblPrompt2.Text = "Testo di prova 2";
lblPrompt2.TextAlign = ContentAlignment.MiddleCenter;
lblPrompt2.ForeColor = Color.Yellow;
lblPrompt2.BackColor = Color.Blue;
Controls.Add(lblPrompt2);
txtTesto2 = new TextBox();
txtTesto2.Location = new Point(150, 100);
txtTesto2.MaxLength = 5;
txtTesto2.CharacterCasing = CharacterCasing.Upper;
Controls.Add(txtTesto2);
}
static void Main(string[] args)
{ Application.Run(new PrimaForm()); }
}
Visual C#
um 16 di 202
Poiché il controllo txtTesto1 è stato aggiunto al form immediatamente dopo lblPrompt1, il
primo è associato al secondo; di conseguenza, la combinazione di tasti ALT+T dove T è il
tasto di accesso definito nel testo dell’etichetta, determina la selezione di txtTesto1,
ovunque si trovi il focus in quel momento.
Il valore true nella proprietà ReadOnly di txtTesto1 ne impedisce la modifica ma non la
selezione; per questo motivo l’impiego di TextBox per la sola visualizzazione non è molto
usuale, eccetto che in quei casi nei quali si vuol dare all’utente la possibilità di eseguire
operazioni di copia e incolla del contenuto del TextBox.
L’unico evento della classe TextBox preso in considerazione è l’evento di sola notifica
TextChanged, il quale è sollevato in risposta ad una qualsiasi modifica del testo contenuto
nel TextBox: inserimento, cancellazione, modifica dei caratteri contenuti in Text.
La gestione dell’evento TextChanged è utile quando lo stato di alcuni controlli deve
dipendere dal contenuto di uno o più TextBox.
Esempio, etichetta con immagine.
using System.Windows.Forms;
using System.Drawing;
class PrimaForm : Form {
Label lblImmagine;
public PrimaForm() {
Text = "Label";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
lblImmagine = new Label();
lblImmagine.Text = "Icona";
ImageList immagini=new ImageList();
immagini.Images.Add(Image.FromFile("max.ico"));
lblImmagine.ImageList=immagini;
lblImmagine.ImageIndex=0;
Controls.Add(lblImmagine);
}
static void Main(string[] args)
{ Application.Run(new PrimaForm()); }
}
LINKLABEL
È un’etichetta il cui testo può contenere un o o più collegamenti ipertestuali, grazie alla
proprietà Links, mentre il clic è gestito dall’evento LinkCliked.
link.Text = @"Clicca." + Environment.NewLine + @"Apri la cartella C:\appo";
link.Links.Add(23, 4, "www.miosito.it");
link.Links.Add(48, 10, "C:\\appo");
link.LinkClicked += new LinkLabelLinkClickedEventHandler(link_LinkClicked);
private void link_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{ string target = e.Link.LinkData.ToString();
LinkLabel label = (LinkLabel)sender;
label.Links[label.Links.IndexOf(e.Link)].Visited = true;
Visual C#
um 17 di 202
System.Diagnostics.Process.Start(target);
}
BUTTON
Associano un metodo all’evento Click del pulsante.
using System;
using System.Windows.Forms;
using System.Drawing;
class PrimaForm : Form {
Button btnClicca;
public PrimaForm() {
Text = "Pulsante";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
btnClicca = new Button();
btnClicca.Location = new Point(10, 10);
btnClicca.Width = 100;
btnClicca.Height = 50;
btnClicca.BackColor = Color.LightSteelBlue;
btnClicca.ForeColor = Color.Crimson;
btnClicca.Text = "&Clicca qui!";
btnClicca.FlatStyle = FlatStyle.Popup;
btnClicca.TextAlign = ContentAlignment.MiddleRight;
btnClicca.Image = Image.FromFile(@"max.ico");
btnClicca.ImageAlign = ContentAlignment.MiddleLeft;
Controls.Add(btnClicca);
btnClicca.Click += new EventHandler(btnClicca_Click);
}
void btnClicca_Click(object sender, EventArgs e)
{ MessageBox.Show("Hai fatto clic!"); }
static void Main(string[] args)
{ Application.Run(new PrimaForm()); }
}
Esempio: progettare un form con due scritte di tipo grafico.
Il namespace System.Drawing fornisce una serie di classi per creare o modificare
immagini di diverso formato.
Utilizzando queste classi è possibile creare grafici con dati recuperati da DB (DataBase)
oppure modificare immagini preesistenti; contiene la classe Graphics: è un pennello.
Questa classe contiene una varietà di metodi per disegnare oggetti come linee, cerchi o
rettangoli.
Possiamo suddividere i metodi per il disegno in due categorie.
Visual C#
um 18 di 202
1. DrawForma: come ad esempio DrawRectangle, sono i metodi per disegnare i contorni
delle figure.
2. FillForma: come ad esempio FillRectangle, sono i metodi per colorare l’interno delle
figure.
Altre classi sono le seguenti.
 Brush: il pennello con cui colorare gli oggetti.
 Font: il carattere per il testo.
 Pen: per disegnare le linee.
Rectangle rappresenta un rettangolo, Point rappresenta un punto.
using System.Windows.Forms;
using System.Drawing;
class PrimaForm : Form {
public PrimaForm() {
Text = "Due scritte grafiche";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
BackColor = Color.White;
Paint += new PaintEventHandler(PaintHandler1);
Paint += new PaintEventHandler(PaintHandler2);
}
static void PaintHandler1(object objSender, PaintEventArgs pea) {
Form form = (Form)objSender;
Graphics grfx = pea.Graphics;
grfx.DrawString("Prima scritta grafica", form.Font, Brushes.Black, 0, 0);
}
static void PaintHandler2(object objSender, PaintEventArgs pea) {
Form form = (Form)objSender;
Graphics grfx = pea.Graphics;
grfx.DrawString("Seconda scritta grafica", form.Font, Brushes.Black, 0, 50);
}
static void Main(string[] args)
{ Application.Run(new PrimaForm()); }
}
MESSAGE BOX
In molte situazioni c’è la necessità di comunicare all’utente lo stato di un’elaborazione,
oppure di chiedere una conferma prima di procedere all’esecuzione di un’operazione.
La MessageBox assume il pieno controllo sulle azioni dell’utente; in altre, parole, finché la
finestra non è chiusa, non è possibile accedere al resto dell’interfaccia.
È impiegata per i seguenti scopi.
 Visualizzare un messaggio informativo.
 Visualizzare un messaggio informativo di errore.
 Visualizzare un messaggio di avvertimento o di richiesta di conferma.
Ha un solo metodo statico, Show ma sono disponibili diversi overload.
using System.Windows.Forms;
Visual C#
um 19 di 202
class Messaggio {
static void Main(string[] args)
{ MessageBox.Show("Ciao, mondo in .NET", "C#", MessageBoxButtons.YesNo,
MessageBoxIcon.Asterisk); }
}
using System;
using System.Windows.Forms;
class Messaggio {
static void Main(string[] args)
{MessageBox.Show(Environment.GetFolderPath(Environment.SpecialFolder.Personal),
"La mia cartella"); }
}
Può contenere al massimo tre pulsanti, il tipo enumerativo MessageBoxButtons permette
di specificare il tipo e il numero, per default solo il pulsante OK, la scelta dell’utente sarà
restituita come valore di ritorno del metodo che è di tipo enumerativo DialogResult.
Se in una MessageBox sono visualizzati più pulsanti, è possibile specificare quale fra essi
deve possedere il focus tramite MessageBoxDefaultButton che è un tipo enumerativo che
può assumere i valori Button1, Button2 e Button3.
Elenco dei possibili valori prodotti del metodo Show.
Nome
None
OK
Cancel
Abort
Retry
Ignore
Yes
No
Visual C#
Valore restituito della finestra di dialogo
Nothing, ciò significa che il dialogo modale è ancora in esecuzione.
Generalmente inviato da un pulsante con etichetta OK.
Generalmente inviato da un pulsante con etichetta Annulla.
Generalmente inviato da un pulsante con etichetta Interrompi.
Generalmente inviato da un pulsante con etichetta Riprova.
Generalmente inviato da un pulsante con etichetta Ignora.
Generalmente inviato da un pulsante con etichetta Sì.
Generalmente inviato da un pulsante con etichetta No.
um 20 di 202
Il parametro MessageBoxIcon è usato per visualizzare un’icona.
Nome
None
Hand
La finestra di messaggio
Non contiene simboli.
Simbolo formato da una X bianca racchiusa da un cerchio su sfondo rosso.
Simbolo formato da un (?) racchiuso da un cerchio. L’icona di messaggio del
punto interrogativo non è più consigliata in quanto non rappresenta
Question
chiaramente un tipo di messaggio specifico ma anche perché la
formulazione di un messaggio come domanda potrebbe essere applicata a
qualsiasi tipo di messaggio.
Exclamation Simbolo formato da un (!) racchiuso da un triangolo su sfondo giallo.
Asterisk
Simbolo formato da una lettera minuscola racchiusa da un cerchio.
Stop
Simbolo formato da una X bianca racchiusa da un cerchio su sfondo rosso.
Error
Simbolo formato da una X bianca racchiusa da un cerchio su sfondo rosso.
Warning
Simbolo formato da un (!) racchiuso da un triangolo su sfondo giallo.
Information Simbolo formato da una lettera minuscola racchiusa da un cerchio.
Esempio: visualizzare in sequenza tutte le icone.
using System;
using System.Windows.Forms;
public class TestMessageBoxIcon {
static void Main(string[] args)
{ Array icons = Enum.GetValues(typeof(MessageBoxIcon));
foreach (MessageBoxIcon icon in icons)
MessageBox.Show("Visualizzo un’icona " + icon, "MessageBoxIcon " + icon,
MessageBoxButtons.OK, icon);
}
}
LISTBOX
Mantengono internamente una collezione dinamica di elementi, ai quali si può accedere
usando la stessa sintassi impiegata per i vettori.
L’uso più comune di tali controlli prevede che l’utente selezioni con il mouse uno o più
elementi dalla lista, determinando quindi le successive elaborazioni dell’applicazione.
È possibile popolare un ListBox attraverso in due modi.
1. Assegnando un vettore alla proprietà DataSource.
2. Aggiungendo elementi attraverso i metodi Items.Add e Items.AddRange.
Gli elementi della lista mantenuta da un ListBox sono di tipo Object; ciò fa sì che tale
controllo possa essere popolato con valori di qualsiasi natura, addirittura anche con valori
di tipo diverso tra loro.
Visual C#
um 21 di 202
D’altra parte, quando si accede a tali elementi, ad esempio in risposta alla selezione da
parte dell’utente, occorre applicare l’operatore di cast per convertirli nel tipo appropriato.
Il controllo ListBox definisce la proprietà Text per ottenere una rappresentazione stringa
dell’elemento attualmente selezionato.
using System;
using System.Windows.Forms;
using System.Drawing;
class MainForm : Form {
ListBox lista1;
ListBox lista2;
string[] filosofi = { "Socrate", "Platone", "Kant", "Popper" };
int[] altezze = { 18, 19, 17 };
string[] nomi = { "Piero", "Giorgio", "Mario", "Pippo" };
public MainForm() {
Text = "ListBox";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
lista1 = new ListBox();
lista1.Top = 0;
lista1.Left = 150;
Controls.Add(lista1);
lista1.SelectedIndexChanged += new EventHandler(nomi_SelectedIndexChanged);
lista1.Items.Add("Bella");
// aggiunge una stringa
lista1.Items.Add("Giornata");
// aggiunge una stringa
lista1.Items.Add(200.2);
// aggiunge un double
lista1.Items.AddRange(filosofi);
foreach(int altezza in altezze)
lista1.Items.Add(altezza);
lista2 = new ListBox();
Controls.Add(lista2);
lista2.DataSource = nomi;
}
void nomi_SelectedIndexChanged(object sender, EventArgs e)
{ MessageBox.Show(lista1.Text); }
static void Main(string[] args)
{ Application.Run(new MainForm()); }
}
lb.Size = new System.Drawing.Size(200, 100);
lb.Location = new System.Drawing.Point(10, 10);
lb.MultiColumn = true;
Visual C#
// elementi su più colonne
um 22 di 202
lb.SelectionMode = SelectionMode.MultiExtended;
Aggiungere elementi.
public void FillList(ListBox lb) {
for(int i=0;i<10;i++)
lb.Items.Add("Elemento "+i);
}
lb.SetSelected(1, true);
lb.SetSelected(3, false);
// seleziona l’elemento d’indice 1
// deseleziona l’elemento d’indice 3
Ricercare un elemento.
string str="elemento3";
int x=lb.FindString(str);
if(x>-1) {
MessageBox.Show(str+" Si trova all’indice "+x);
}
else
MessageBox.Show(str+" Non trovato");
COMBOBOX
Combinano le caratteristiche di un ListBox con quelle di un TextBox, laddove è richiesta la
possibilità di selezionare un elemento da una lista ma lo spazio nel form non è sufficiente
perché la lista sia visibile.
Il controllo può comunicare con l’utente sia attraverso il mouse, caratteristica tipica del
ListBox, sia attraverso la tastiera, caratteristica tipica del TextBox.
L’utente può compiere le seguenti azioni.
1. Cliccare sulla freccia per visualizzare l’elenco a discesa e quindi selezionare un
elemento; l’elemento selezionato è visualizzato nella casella.
2. Digitare all’interno della casella.
Si accede all’elemento selezionato o al testo digitato, attraverso la proprietà Text, come
avviene con i controlli TextBox.
using System;
using System.Windows.Forms;
using System.Drawing;
class MainForm : Form {
Label label1;
ComboBox combo1;
string[] nomi = { "Piero", "Giorgio", "Mario", "Pippo" };
public MainForm() {
Text = "ComboBox";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
combo1 = new ComboBox();
combo1.Location = new Point(20, 20);
combo1.DropDownStyle = ComboBoxStyle.Simple;
combo1.DataSource = nomi;
combo1.DropDownStyle = ComboBoxStyle.DropDownList;
combo1.SelectedIndexChanged +=new
EventHandler(combo1_SelectedIndexChanged);
Controls.Add(combo1);
Visual C#
um 23 di 202
label1 = new Label();
label1.Location = new Point(150, 20);
Controls.Add(label1);
}
void combo1_SelectedIndexChanged(object sender, EventArgs e)
{ label1.Text = combo1.Text; }
static void Main(string[] args)
{ Application.Run(new MainForm()); }
}
Aggiungere un elemento.
cbo.Items.Add("Nuovo elemento");
cbo.Items.AddRange(new string[]{"a","b","c"});
c.Remove("nuovo elemento");
cbo.Items.RemoveAt(0);
// rimuove il primo elemento
L’elemento selezionato oppure il suo indice è restituito dalle proprietà seguenti.
object selected=cbo.SelectedItem;
int indexSel=cbo.SelectedIndex;
RADIOBUTTON
Svolge la funzione di un interruttore, il quale può trovarsi soltanto nello stato ON/OFF;
analogamente, può assumere due soli stati possibili: Cheked o Uncheked.
Funziona sempre in collaborazione con altri RadioButton, insieme con i quali definiscono
la lista dei possibili stati che può assumere un determinato valore; in ogni momento uno
solo tra i RadioButton può essere checked.
using System;
using System.Windows.Forms;
using System.Drawing;
class MainForm : Form {
TextBox textBox1;
RadioButton radiobutton1;
RadioButton radiobutton2;
Button button1;
public MainForm() {
Text = "RadioButton";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
// prima classe
radiobutton1 = new RadioButton();
radiobutton1.Location = new Point(40, 50);
radiobutton1.Text = "Prima classe";
Visual C#
um 24 di 202
Controls.Add(radiobutton1);
// classe economica - opzione impostata inizialmente
radiobutton2 = new RadioButton();
radiobutton2.Location = new Point(40, 80);
radiobutton2.AutoSize = true;
radiobutton2.Size = new Size(100, 30);
radiobutton2.Text = "Classe economica";
radiobutton2.Checked = true;
Controls.Add(radiobutton2);
textBox1 = new TextBox();
textBox1.Location = new Point(40, 20);
Controls.Add(textBox1);
button1 = new Button();
button1.Location = new Point(20, 120);
button1.Text = "Conferma prenotazione";
button1.Width = 150;
button1.Click += new EventHandler(btnConferma_Click);
Controls.Add(button1);
}
void btnConferma_Click (object sender, EventArgs e) {
string s;
if (radiobutton1.Checked == true)
s = " -- Prima classe: confermata";
else
s = " -- Classe economica: confermata";
MessageBox.Show("Cliente: " + textBox1.Text + s, "Conferma prenotazione");
}
static void Main(string[] args)
{ Application.Run(new MainForm()); }
}
CHECKBOX
Svolge la funzione di un interruttore, il quale può trovarsi soltanto nello stato ON/OFF.
Un CheckBox non agisce in collaborazione con altri CheckBox, esso, infatti, definisce lo
stato, Checked, rappresenta lo stato: true o false o Unchecked, di un’opzione che è
indipendente da eventuali altre opzioni rappresentate nell’interfaccia.
using System;
using System.Windows.Forms;
using System.Drawing;
class MainForm : Form {
TextBox textBox1;
Visual C#
um 25 di 202
CheckBox checkboxEnabled;
CheckBox checkboxVisible;
public MainForm() {
Text = "CheckBox";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
// CheckBox proprietà Enabled
checkboxEnabled = new CheckBox();
checkboxEnabled.Location = new Point(40, 50);
checkboxEnabled.Width = 200;
checkboxEnabled.Text = "TextBox abilitato";
checkboxEnabled.Checked = true;
checkboxEnabled.CheckedChanged += new
EventHandler(chkEnabled_CheckedChanged);
Controls.Add(checkboxEnabled);
// CheckBox proprietà Visible
checkboxVisible = new CheckBox();
checkboxVisible.Location = new Point(40, 80);
checkboxVisible.Width = 200;
checkboxVisible.Text = "TextBox visibile";
checkboxVisible.Checked = true;
checkboxVisible.CheckedChanged += new
EventHandler(chkVisible_CheckedChanged);
Controls.Add(checkboxVisible);
textBox1 = new TextBox();
textBox1.Location = new Point(40, 20);
Controls.Add(textBox1);
}
void chkEnabled_CheckedChanged(object sender, EventArgs e)
{ textBox1.Enabled = checkboxEnabled.Checked; }
void chkVisible_CheckedChanged(object sender, EventArgs e)
{ textBox1.Visible = checkboxVisible.Checked; }
static void Main(string[] args)
{ Application.Run(new MainForm()); }
}
Può assumere un terzo stato indeterminato.
chk.ThreeState = true;
chk.CheckState = CheckState.Indeterminate;
GROUPBOX
Esistono due controlli, Panel e GroupBox, il cui ruolo non è quello di dialogare con l’utente
ma di fungere da contenitori per gli altri controlli, entrambi svolgono una funzione analoga
a quella del form, in altre parole ospitano altri controlli.
Visual C#
um 26 di 202
I motivi per raggruppare dei controlli sono i seguenti.
 Creare un riquadro o un particolare colore di sfondo, delle aree visivamente separate
dal resto dell’interfaccia, dedicate a ospitare dei controlli logicamente correlati tra loro.
 Facilitare l’aggiornamento dello stato di alcune proprietà appartenenti a controlli
logicamente correlati tra loro; ad esempio, impostando a false la proprietà Enabled di
un Panel sono automaticamente disabilitati tutti i controlli in esso contenuti.
 Creare insiemi di RadioButton funzionalmente indipendenti tra loro.
Il controllo GroupBox è usato come contenitore per RadioButton, consentendo così di
gestire insiemi di RadioButton indipendenti tra loro.
Infatti, all’interno di un controllo contenitore, di norma il form, un solo RadioButton per volta
può trovarsi nello stato Checked; dunque, se è necessario gestire due valori distinti, i cui
stati sono rappresentati attraverso due insiemi di RadioButton, è necessario collocare tali
insiemi in contenitori a loro volta distinti.
Esempio, due GroupBox che svolgono la funzione di contenitori, consentendo alle due
coppie di RadioButton di funzionare in modo indipendente l’una dall’altra.
La proprietà Checked dei RadioButton rbuMaiusc e rbuLeft dev’essere impostata dopo
che sono stati attaccati i rispettivi gestori di evento, altrimenti, all’inizio, lo stato del
TextBox non è aggiornato.
Il TextBox dev’essere creato prima dei RadioButton, poiché l’impostazione iniziale,
all’interno del costruttore, determina l’esecuzione dei
gestori di evento
Case_CheckedChanged e Align_CheckedChanged, i quali accedono al TextBox che
ovviamente deve già essere stato creato.
using System;
using System.Windows.Forms;
using System.Drawing;
class MainForm : Form {
GroupBox grpCase, grpAlign;
RadioButton radiobutton1, radiobutton2, radiobutton3, radiobutton4;
TextBox textBox1;
public MainForm() {
Text = "GroupBox";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
// TextBox dev'essere creato per primo
textBox1 = new TextBox();
textBox1.Location = new Point(80, 24);
Controls.Add(textBox1);
// GroupBox
grpCase = new GroupBox();
grpCase.Location = new Point(32, 64);
grpCase.Size = new Size(96, 80);
grpCase.Text = "Case";
Controls.Add(grpCase);
radiobutton2 = new RadioButton();
radiobutton2.Text = "Maiuscolo";
radiobutton2.Location = new Point(8, 24);
radiobutton2.Width = 80;
radiobutton2.CheckedChanged += new EventHandler(Case_CheckedChanged);
radiobutton2.Checked = true;
grpCase.Controls.Add(radiobutton2);
radiobutton1 = new RadioButton();
radiobutton1.Text = "Minuscolo";
Visual C#
um 27 di 202
radiobutton1.Location = new Point(8, 48);
radiobutton1.Width = 80;
radiobutton1.CheckedChanged += new EventHandler(Case_CheckedChanged);
grpCase.Controls.Add(radiobutton1);
// GroupBox bordo
grpAlign = new GroupBox();
grpAlign.Location = new Point(160, 64);
grpAlign.Size = new Size(96, 80);
grpAlign.Text = "Allineamento";
Controls.Add(grpAlign);
radiobutton3 = new RadioButton();
radiobutton3.Text = "Sinistra";
radiobutton3.Location = new Point(8, 24);
radiobutton3.Width = 80;
radiobutton3.CheckedChanged += new EventHandler(Align_CheckedChanged);
grpAlign.Controls.Add(radiobutton3);
radiobutton4 = new RadioButton();
radiobutton4.Text = "Destra";
radiobutton4.Location = new Point(8, 48);
radiobutton4.Width = 80;
radiobutton4.CheckedChanged += new EventHandler(Align_CheckedChanged);
radiobutton4.Checked = true;
grpAlign.Controls.Add(radiobutton4);
}
void Case_CheckedChanged(object sender, EventArgs e) {
if (radiobutton2.Checked == true)
textBox1.CharacterCasing = CharacterCasing.Upper;
else
textBox1.CharacterCasing = CharacterCasing.Lower;
}
void Align_CheckedChanged(object sender, EventArgs e) {
if (radiobutton3.Checked == true)
textBox1.TextAlign = HorizontalAlignment.Left;
else
textBox1.TextAlign = HorizontalAlignment.Right;
}
static void Main(string[] args)
{ Application.Run(new MainForm()); }
}
PANEL
Si usa quando un’area del form è troppo piccola per il numero e/o le dimensioni dei
Visual C#
um 28 di 202
controlli che deve ospitare.
Infatti, mediante due barre di scorrimento, verticale e orizzontale, è in grado di
rappresentare un’area più grande di quella effettivamente visualizzabile, consentendo di
rendere visibile e quindi accessibile all’utente, soltanto una parte di tale area in un dato
momento, insieme ai controlli che essa contiene.
Per abilitare il Panel all’impiego delle barre di scorrimento è necessario impostare a true la
proprietà AutoScroll.
Esempio, utilizzare un Panel per abilitare o disabilitare un gruppo di controlli in relazione
allo stato di un CheckBox.
using System;
using System.Windows.Forms;
using System.Drawing;
class MainForm : Form {
Label label1, label2, label3;
TextBox textBox1, textBox2, textBox3;
Panel pnlLaurea;
CheckBox chkLaureato;
public MainForm() {
Text = "CheckBox";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
label1 = new Label();
label1.Location = new Point(8, 16);
label1.AutoSize = true;
label1.Text = "Nome e cognome";
Controls.Add(label1);
textBox1 = new TextBox();
textBox1.Location = new Point(8, 40);
textBox1.Text = "";
Controls.Add(textBox1);
chkLaureato = new CheckBox();
chkLaureato.Location = new Point(144, 40);
chkLaureato.Text = "Laureato";
chkLaureato.Checked = false;
chkLaureato.CheckedChanged +=new EventHandler(chkLaureato_CheckedChanged);
Controls.Add(chkLaureato);
// Panel
pnlLaurea = new Panel();
pnlLaurea.Location = new Point(8, 72);
pnlLaurea.Size = new Size(232, 72);
pnlLaurea.BackColor = Color.Coral;
pnlLaurea.Enabled = false;
Controls.Add(pnlLaurea);
// controlli nel Panel
label2 = new Label();
label2.Location = new Point(8, 8);
label2.AutoSize = true;
label2.Text = "Facolta";
pnlLaurea.Controls.Add(label2);
textBox2 = new TextBox();
textBox2.Location = new Point(8, 32);
pnlLaurea.Controls.Add(textBox2);
Visual C#
um 29 di 202
label3 = new Label();
label3.Location = new Point(120, 8);
label3.AutoSize = true;
label3.Text = "Anno di laurea";
pnlLaurea.Controls.Add(label3);
textBox3 = new TextBox();
textBox3.Location = new Point(120, 32);
pnlLaurea.Controls.Add(textBox3);
}
void chkLaureato_CheckedChanged(object sender, EventArgs e)
{ pnlLaurea.Enabled = chkLaureato.Checked; }
static void Main(string[] args)
{ Application.Run(new MainForm()); }
}
Esempio.
using System;
using System.Windows.Forms;
using System.Drawing;
class MainForm : Form {
Panel panel1;
Panel panel2;
public MainForm() {
Text = "Due pannelli";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
Panel panel1 = new Panel();
panel1.Parent = this;
panel1.Dock = DockStyle.Fill;
panel1.BackColor = Color.Lime;
panel1.Resize += new EventHandler(PanelOnResize);
panel1.Paint += new PaintEventHandler(PanelOnPaint);
Splitter split = new Splitter();
split.Parent = this;
split.Dock = DockStyle.Right;
Panel panel2 = new Panel();
panel2.Parent = this;
panel2.Dock = DockStyle.Right;
panel2.BackColor = Color.Red;
panel2.Resize += new EventHandler(PanelOnResize);
panel2.Paint += new PaintEventHandler(PanelOnPaint);
}
Visual C#
um 30 di 202
void PanelOnResize(object obj, EventArgs ea)
{ ((Panel)obj).Invalidate(); }
void PanelOnPaint(object obj, PaintEventArgs pea) {
Panel panel = (Panel)obj;
Graphics grfx = pea.Graphics;
grfx.DrawEllipse(Pens.Black, 0, 0, panel.Width - 1, panel.Height - 1);
}
static void Main(string[] args)
{ Application.Run(new MainForm()); }
}
COMMON DIALOG
Le finestre di dialogo sono finestre modali, alla chiusura ritornano DialogResult.
Per selezionare e aprire file, è usata la classe OpenFileDialog, la proprietà Title imposta il
titolo della barra del titolo, la proprietà Filter è un filtro per la tipologia di file.
Per selezionare e salvare file, è usata la classe SaveFileDialog.
using System;
using System.Windows.Forms;
class PrimaForm : Form {
[STAThread]
static void Main(string[] args)
{ OpenFileDialog dlg = new OpenFileDialog();
dlg.Title = "Apri un file C#";
dlg.Filter = "C# File (*.cs)|*.cs|Text Files (*.txt)|*.txt|All Files (*.*)|*.*";
dlg.FilterIndex = 2;
dlg.InitialDirectory = @"C:\compiler";
dlg.ShowReadOnly = true;
dlg.ReadOnlyChecked = true;
dlg.CheckFileExists = false;
if (dlg.ShowDialog() == DialogResult.OK)
MessageBox.Show("Apertura file " + dlg.FileNames[0]);
}
}
Visual C#
um 31 di 202
using System;
using System.Windows.Forms;
using System.Drawing;
class MainForm : Form {
protected ColorDialog clrdlg = new ColorDialog();
public MainForm() {
Text = "Finestre carattere e colore";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
Menu = new MainMenu();
Menu.MenuItems.Add("&Formato");
Menu.MenuItems[0].MenuItems.Add("&Carattere...", new
EventHandler(MenuFontOnClick));
Menu.MenuItems[0].MenuItems.Add("&Colore di Background...", new
EventHandler(MenuColorOnClick));
}
void MenuFontOnClick(object obj, EventArgs ea)
{ FontDialog fontdlg = new FontDialog();
fontdlg.Font = Font;
fontdlg.Color = ForeColor;
fontdlg.ShowColor = true;
fontdlg.ShowApply = true;
fontdlg.Apply += new EventHandler(FontDialogOnApply);
if (fontdlg.ShowDialog() == DialogResult.OK) {
Font = fontdlg.Font;
ForeColor = fontdlg.Color;
Invalidate();
}
}
void MenuColorOnClick(object obj, EventArgs ea) {
Visual C#
um 32 di 202
clrdlg.Color = BackColor;
if (clrdlg.ShowDialog() == DialogResult.OK)
BackColor = clrdlg.Color;
}
void FontDialogOnApply(object obj, EventArgs ea) {
FontDialog fontdlg = (FontDialog)obj;
Font = fontdlg.Font;
ForeColor = fontdlg.Color;
Invalidate();
}
protected override void OnPaint(PaintEventArgs pea) {
Graphics grfx = pea.Graphics;
grfx.DrawString("Modifica il tipo di carattere e il colore background!", Font, new
SolidBrush(ForeColor), 0, 0);
}
static void Main(string[] args)
{ Application.Run(new MainForm()); }
}
Per impostare le proprietà di stampa, è usata la classe PrintDialog.
Per visualizzare l’anteprima di stampa, è usata la classe PageSetupDialog.
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Printing;
class MainForm : Form {
const int iNumberPages = 3;
int iPagesToPrint, iPageNumber;
public MainForm() {
Text = "Finestra stampa";
Icon = new Icon("max.ico");
Visual C#
um 33 di 202
StartPosition = FormStartPosition.CenterScreen;
Menu = new MainMenu();
Menu.MenuItems.Add("&File");
Menu.MenuItems[0].MenuItems.Add("S&tampa...", new
EventHandler(MenuFilePrintOnClick));
}
void MenuFilePrintOnClick(object obj, EventArgs ea) {
// crea il documento e la dialog box di stampa
PrintDocument prndoc = new PrintDocument();
PrintDialog prndlg = new PrintDialog();
prndlg.Document = prndoc;
// insieme delle pagine
prndlg.AllowSomePages = true;
prndlg.PrinterSettings.MinimumPage = 1;
prndlg.PrinterSettings.MaximumPage = iNumberPages;
prndlg.PrinterSettings.FromPage = 1;
prndlg.PrinterSettings.ToPage = iNumberPages;
// se la dialog box returna OK, stampa
if (prndlg.ShowDialog() == DialogResult.OK) {
prndoc.DocumentName = Text;
prndoc.PrintPage += new PrintPageEventHandler(OnPrintPage);
// determina quale pagina stampare
switch (prndlg.PrinterSettings.PrintRange) {
case PrintRange.AllPages:
iPagesToPrint = iNumberPages;
iPageNumber = 1;
break;
case PrintRange.SomePages:
iPagesToPrint = 1 + prndlg.PrinterSettings.ToPage prndlg.PrinterSettings.FromPage;
iPageNumber = prndlg.PrinterSettings.FromPage;
break;
}
prndoc.Print();
}
}
void OnPrintPage(object obj, PrintPageEventArgs ppea) {
Graphics grfx = ppea.Graphics;
Font font = new Font("Times New Roman", 360);
string str = iPageNumber.ToString();
SizeF sizef = grfx.MeasureString(str, font);
grfx.DrawString(str, font, Brushes.Black, (grfx.VisibleClipBounds.Width - sizef.Width) /
2, (grfx.VisibleClipBounds.Height - sizef.Height) / 2);
iPageNumber += 1;
iPagesToPrint -= 1;
ppea.HasMorePages = iPagesToPrint > 0;
}
static void Main(string[] args)
{ Application.Run(new MainForm()); }
}
Visual C#
um 34 di 202
TREVIEW
È la vista ad albero TreeView e dei suoi nodi, TreeNode.
Consente di visualizzare una gerarchia di nodi, nello stesso modo in cui file e cartelle sono
visualizzate nel riquadro sinistro di Esplora risorse.
using System;
using System.Windows.Forms;
using System.Drawing;
class MainForm : Form {
TreeView tree;
public MainForm() {
Text = "Tree View";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
TreeView tree = new TreeView();
tree.Parent = this;
tree.Dock = DockStyle.Fill;
tree.Nodes.Add("Animali");
tree.Nodes[0].Nodes.Add("Cane");
tree.Nodes[0].Nodes[0].Nodes.Add("Segugio");
tree.Nodes[0].Nodes[0].Nodes.Add("Irish Setter");
tree.Nodes[0].Nodes[0].Nodes.Add("German Shepherd");
tree.Nodes[0].Nodes.Add("Gatti");
tree.Nodes[0].Nodes[1].Nodes.Add("Calico");
tree.Nodes[0].Nodes[1].Nodes.Add("Siamese");
tree.Nodes[0].Nodes.Add("Primate");
tree.Nodes[0].Nodes[2].Nodes.Add("Chimpanzee");
tree.Nodes[0].Nodes[2].Nodes.Add("Ape");
tree.Nodes[0].Nodes[2].Nodes.Add("Human");
tree.Nodes.Add("Minerali");
tree.Nodes[1].Nodes.Add("Calcio");
tree.Nodes[1].Nodes.Add("Zinco");
tree.Nodes[1].Nodes.Add("Iron");
Visual C#
um 35 di 202
tree.Nodes.Add("Vegetali");
tree.Nodes[2].Nodes.Add("Carote");
tree.Nodes[2].Nodes.Add("Asparagi");
tree.Nodes[2].Nodes.Add("Broccoli");
}
static void Main(string[] args)
{ Application.Run(new MainForm()); }
}
LISTVIEW
Permette di rappresentare una lista di elementi con testo ed eventuale icona.
Quattro modi di visualizzazione con la proprietà View: Details, List, SmallIcon e LargeIcon.
Nella modalità più complessa gli elementi sono visualizzati per righe e colonne, nelle altre
tre modalità è visualizzata una lista semplice
ListView lv = new ListView();
lv.View = View.Details;
MENU
Da questa classe derivano due diversi tipi di menu.
1. Classici: MainMenu.
2. Contestuali: ContextMenu.
Per costruire un MainMenu ci sono due costruttori.
MainMenu();
MainMenu(MenuItem[]);
Associare ad un form un menu.
MainMenu menuPrincipale=new MainMenu(menuItems);
miaForm.Menu= menuPrincipale;
Impostazione di un singolo menu.
Visual C#
um 36 di 202
MenuItem mnuFileNuovo=new MenuItem();
mnuFileNuovo.Text="&Nuovo";
MenuItem mnuFileApri=new MenuItem("Apri…");
MenuItem mnuSeparatore=new MenuItem("-");
Menu che appare sotto la barra del titolo.
MenuItem mnuFile=new MainMenu("&File",new MenuItem[]{mnuFileNuovo,mnuFileApri});
I menu creati non eseguono nessuna azione, occorre implementare il metodo di gestione
dell’evento.
mnuFileApri.Click+= new System.EventHandler(this. mnuFileApri_Click);
private void mnuFileApri_Click(object sender, System.EventArgs e) {
OpenFileDialog fd = new OpenFileDialog();
fd.ShowDialog();
}
Esempio.
using System;
using System.Windows.Forms;
using System.Drawing;
class AboutDialogBox : Form {
public AboutDialogBox() {
Text = "Informazioni su";
StartPosition = FormStartPosition.CenterParent;
FormBorderStyle = FormBorderStyle.FixedDialog;
ControlBox = false;
MaximizeBox = false;
MinimizeBox = false;
ShowInTaskbar = false;
Label label1 = new Label();
label1.Parent = this;
label1.Text = " Informazioni su ... Version 1.0 ";
label1.Font = new Font(FontFamily.GenericSerif, 14, FontStyle.Italic);
label1.AutoSize = true;
label1.TextAlign = ContentAlignment.MiddleCenter;
PictureBox picbox = new PictureBox();
picbox.Parent = this;
picbox.SizeMode = PictureBoxSizeMode.AutoSize;
picbox.Location = new Point(label1.Font.Height / 2, label1.Font.Height / 2);
label1.Location = new Point(picbox.Right, label1.Font.Height / 2);
int iClientWidth = label1.Right;
Label label2 = new Label();
label2.Parent = this;
label2.Text = "\x00A9 2023 by SYSOP SLY ";
label2.Font = new Font(FontFamily.GenericSerif, 10);
label2.Location = new Point(0, label1.Bottom + label2.Font.Height);
label2.Size = new Size(iClientWidth, label2.Font.Height);
label2.TextAlign = ContentAlignment.MiddleCenter;
Button button = new Button();
button.Parent = this;
Visual C#
um 37 di 202
button.Text = "OK";
button.Size = new Size(4 * button.Font.Height, 2 * button.Font.Height);
button.Location = new Point((iClientWidth - button.Size.Width) / 2, label2.Bottom + 2 *
button.Font.Height);
button.DialogResult = DialogResult.OK;
CancelButton = button;
AcceptButton = button;
ClientSize = new Size(iClientWidth, button.Bottom + 2 * button.Font.Height);
}
class MainForm : Form {
public MainForm() {
Text = "Menu";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
Menu = new MainMenu();
Menu.MenuItems.Add("?");
Menu.MenuItems[0].MenuItems.Add("Informazioni su... ", new
EventHandler(MenuAboutOnClick));
}
void MenuAboutOnClick(object obj, EventArgs ea) {
AboutDialogBox dlg = new AboutDialogBox();
dlg.ShowDialog();
}
}
static void Main(string[] args)
{ Application.Run(new MainForm()); }
}
Esempio: barra dei menu, barra degli strumenti e barra di stato.
Il namespace System.Drawing contiene la classe Bitmap, è una tela su cui si disegna.
using System;
using System.Windows.Forms;
using System.Drawing;
class MainForm : Form {
StatusBarPanel sbpMenuHelp, sbpDate, sbpTime;
string strSavePanelText;
public MainForm() {
Text = "Menu";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
Visual C#
um 38 di 202
// inizio barra degli strumenti
Bitmap bm = new Bitmap("StandardButtons.bmp");
ImageList imglst = new ImageList();
imglst.Images.AddStrip(bm);
imglst.TransparentColor = Color.Cyan;
ToolBar tbar = new ToolBar();
tbar.Parent = this;
tbar.ImageList = imglst;
tbar.ShowToolTips = true;
string[] astr = { "Nuovo", "Apri", "Salva", "Stampa", "Taglia", "Copia", "Incolla" };
for (int i = 0; i < 7; i++) {
ToolBarButton tbarbtn = new ToolBarButton();
tbarbtn.ImageIndex = i;
tbarbtn.ToolTipText = astr[i];
tbar.Buttons.Add(tbarbtn);
}
// fine barra degli strumenti
BackColor = SystemColors.Window;
ForeColor = SystemColors.WindowText;
// crea la barra di stato con un solo pannello
StatusBar sb = new StatusBar();
sb.Parent = this;
sb.ShowPanels = true;
sbpMenuHelp = new StatusBarPanel();
sbpMenuHelp.Text = "Barra di stato";
sbpMenuHelp.BorderStyle = StatusBarPanelBorderStyle.None;
sbpMenuHelp.AutoSize = StatusBarPanelAutoSize.Spring;
// data e ora nella barra di stato
sbpDate = new StatusBarPanel();
sbpDate.AutoSize = StatusBarPanelAutoSize.Contents;
sbpDate.ToolTipText = "Data Corrente: ";
sbpTime = new StatusBarPanel();
sbpTime.AutoSize = StatusBarPanelAutoSize.Contents;
sbpTime.ToolTipText = "Ora corrente: ";
sb.Panels.AddRange(new StatusBarPanel[] { sbpMenuHelp, sbpDate, sbpTime });
Timer timer = new Timer();
timer.Tick += new EventHandler(TimerOnTick);
timer.Interval = 1000;
timer.Start();
// inizia la costruzione del menu
Menu = new MainMenu();
EventHandler ehSelect = new EventHandler(MenuOnSelect);
// menu file
MenuItem mi = new MenuItem("File");
mi.Select += ehSelect;
Menu.MenuItems.Add(mi);
mi = new MenuItem("Apri...");
mi.Select += ehSelect;
Menu.MenuItems[0].MenuItems.Add(mi);
mi = new MenuItem("Chiudi");
mi.Select += ehSelect;
Menu.MenuItems[0].MenuItems.Add(mi);
mi = new MenuItem("Salva");
mi.Select += ehSelect;
Visual C#
um 39 di 202
Menu.MenuItems[0].MenuItems.Add(mi);
// menu modifica
mi = new MenuItem("Modifica");
mi.Select += ehSelect;
Menu.MenuItems.Add(mi);
mi = new MenuItem("Taglia");
mi.Select += ehSelect;
Menu.MenuItems[1].MenuItems.Add(mi);
mi = new MenuItem("Copia");
mi.Select += ehSelect;
Menu.MenuItems[1].MenuItems.Add(mi);
mi = new MenuItem("Incolla");
mi.Select += ehSelect;
Menu.MenuItems[1].MenuItems.Add(mi);
}
private void InitializeComponent() {
this.SuspendLayout();
// MenuHelpFirstTry
this.ClientSize = new System.Drawing.Size(292, 268);
this.Name = "MenuHelpFirstTry";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.ResumeLayout(false);
// data e ora
this.ClientSize = new System.Drawing.Size(292, 268);
this.Name = "DateAndTimeStatus";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.ResumeLayout(false);
}
void TimerOnTick(object obj, EventArgs ea) {
DateTime dt = DateTime.Now;
sbpDate.Text = dt.ToShortDateString();
sbpTime.Text = dt.ToShortTimeString();
}
protected override void OnMenuStart(EventArgs ea)
{ strSavePanelText = sbpMenuHelp.Text; }
protected override void OnMenuComplete(EventArgs ea)
{ sbpMenuHelp.Text = strSavePanelText; }
void MenuOnSelect(object obj, EventArgs ea) {
MenuItem mi = (MenuItem)obj;
string str;
switch (mi.Text) {
case "File": str = "Comandi per lavorare con il documento."; break;
case "Apri...": str = "Apre un documento esistente."; break;
case "Chiudi": str = "Chiude il documento attivo."; break;
case "Salva": str = "Salva il documento attivo."; break;
case "Modifica": str = "Comandi per modificare il documento"; break;
case "Taglia": str = "Taglia la selezione e la inserisce negli Appunti."; break;
case "Copia": str = "Copia la selezione e la inserisce negli Appunti."; break;
case "Incolla": str = "Inserisce il contenuto degli Appunti."; break;
default: str = ""; break;
}
sbpMenuHelp.Text = str;
}
static void Main(string[] args)
Visual C#
um 40 di 202
{ Application.Run(new MainForm()); }
}
PICTUREBOX
È caratterizzato da due elementi, rappresentati da altrettante proprietà.
1. L’immagine da visualizzare.
2. I modi di visualizzazione dell’immagine.
using System;
using System.Windows.Forms;
using System.Drawing;
class MainForm : Form {
PictureBox picture1, picture2;
Button button1;
public MainForm() {
Text = "PictureBox";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
picture1 = new PictureBox();
picture1.Location = new Point(16, 40);
picture1.Size = new Size(98, 82);
picture1.Image = Image.FromFile(@"mezzi.gif");
picture1.SizeMode = PictureBoxSizeMode.StretchImage;
Controls.Add(picture1);
picture2 = new PictureBox();
picture2.Location = new Point(160, 40);
picture2.Size = new Size(98, 82);
picture2.Image = Image.FromFile(@"mezzi.gif");
picture2.SizeMode = PictureBoxSizeMode.CenterImage;
Controls.Add(picture2);
button1 = new Button();
button1.Location = new Point(78, 144);
button1.Size = new Size(116, 24);
button1.Text = "Scambia modalità";
button1.Click += new EventHandler(btnScambia_Click);
Controls.Add(button1);
}
void btnScambia_Click(object sender, EventArgs e) {
if (picture1.SizeMode == PictureBoxSizeMode.StretchImage)
picture1.SizeMode = PictureBoxSizeMode.CenterImage;
else
picture1.SizeMode = PictureBoxSizeMode.StretchImage;
if (picture2.SizeMode == PictureBoxSizeMode.StretchImage)
Visual C#
um 41 di 202
picture2.SizeMode = PictureBoxSizeMode.CenterImage;
else
picture2.SizeMode = PictureBoxSizeMode.StretchImage;
}
static void Main(string[] args)
{ Application.Run(new MainForm()); }
}
MOUSE
using System;
using System.Windows.Forms;
using System.Drawing;
class MainForm : Form {
bool bBlocking, bValidBox;
Point ptBeg, ptEnd;
Rectangle rectBox;
public MainForm() {
Text = "Rettangoli con il mouse";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
BackColor = SystemColors.Window;
ForeColor = SystemColors.WindowText;
}
protected override void OnMouseDown(MouseEventArgs mea) {
if (mea.Button == MouseButtons.Left) {
ptBeg = ptEnd = new Point(mea.X, mea.Y);
Graphics grfx = CreateGraphics();
grfx.DrawRectangle(new Pen(ForeColor), Rect(ptBeg, ptEnd));
grfx.Dispose();
bBlocking = true;
}
}
protected override void OnMouseMove(MouseEventArgs mea) {
if (bBlocking) {
Graphics grfx = CreateGraphics();
grfx.DrawRectangle(new Pen(BackColor), Rect(ptBeg, ptEnd));
ptEnd = new Point(mea.X, mea.Y);
grfx.DrawRectangle(new Pen(ForeColor), Rect(ptBeg, ptEnd));
grfx.Dispose();
Invalidate();
}
Visual C#
um 42 di 202
}
protected override void OnMouseUp(MouseEventArgs mea) {
if (bBlocking && mea.Button == MouseButtons.Left) {
Graphics grfx = CreateGraphics();
rectBox = Rect(ptBeg, new Point(mea.X, mea.Y));
grfx.DrawRectangle(new Pen(ForeColor), rectBox);
grfx.Dispose();
bBlocking = false;
bValidBox = true;
Invalidate();
}
}
protected override void OnPaint(PaintEventArgs pea) {
Graphics grfx = pea.Graphics;
if (bValidBox)
grfx.FillRectangle(new SolidBrush(ForeColor), rectBox);
if (bBlocking)
grfx.DrawRectangle(new Pen(ForeColor), Rect(ptBeg, ptEnd));
}
Rectangle Rect(Point ptBeg, Point ptEnd) {
return new Rectangle(Math.Min(ptBeg.X, ptEnd.X),
Math.Min(ptBeg.Y, ptEnd.Y),
Math.Abs(ptEnd.X - ptBeg.X),
Math.Abs(ptEnd.Y - ptBeg.Y));
}
static void Main(string[] args)
{ Application.Run(new MainForm()); }
}
TASTIERA
using System;
using System.Windows.Forms;
using System.Drawing;
class MainForm : Form {
enum EventType { None,KeyDown,KeyUp, KeyPress
}
struct KeyEvent {
public EventType evttype;
public EventArgs evtargs;
}
const int iNumLines = 25;
int iNumValid = 0, iInsertIndex = 0;
KeyEvent[] akeyevt = new KeyEvent[iNumLines];
Visual C#
um 43 di 202
int xEvent, xChar, xCode, xMods, xData,xShift, xCtrl, xAlt, xRight;
public MainForm() {
Text = "Tastiera";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
BackColor = SystemColors.Window;
ForeColor = SystemColors.WindowText;
xEvent = 0;
xChar = xEvent + 5 * Font.Height;
xCode = xChar + 5 * Font.Height;
xMods = xCode + 8 * Font.Height;
xData = xMods + 8 * Font.Height;
xShift = xData + 8 * Font.Height;
xCtrl = xShift + 5 * Font.Height;
xAlt = xCtrl + 5 * Font.Height;
xRight = xAlt + 5 * Font.Height;
ClientSize = new Size(xRight, Font.Height * (iNumLines + 1));
FormBorderStyle = FormBorderStyle.Fixed3D;
MaximizeBox = false;
}
protected override void OnKeyDown(KeyEventArgs kea) {
akeyevt[iInsertIndex].evttype = EventType.KeyDown;
akeyevt[iInsertIndex].evtargs = kea;
OnKey();
}
protected override void OnKeyUp(KeyEventArgs kea) {
akeyevt[iInsertIndex].evttype = EventType.KeyUp;
akeyevt[iInsertIndex].evtargs = kea;
OnKey();
}
protected override void OnKeyPress(KeyPressEventArgs kpea) {
akeyevt[iInsertIndex].evttype = EventType.KeyPress;
akeyevt[iInsertIndex].evtargs = kpea;
OnKey();
}
void OnKey() {
if (iNumValid < iNumLines) {
Graphics grfx = CreateGraphics();
DisplayKeyInfo(grfx, iInsertIndex, iInsertIndex);
grfx.Dispose();
}
else
ScrollLines();
iInsertIndex = (iInsertIndex + 1) % iNumLines;
iNumValid = Math.Min(iNumValid + 1, iNumLines);
}
protected virtual void ScrollLines() {
Rectangle rect = new Rectangle(0, Font.Height, ClientSize.Width, ClientSize.Height Font.Height);
Invalidate(rect);
}
protected override void OnPaint(PaintEventArgs pea) {
Graphics grfx = pea.Graphics;
BoldUnderline(grfx, "Event", xEvent, 0);
Visual C#
um 44 di 202
BoldUnderline(grfx, "KeyChar", xChar, 0);
BoldUnderline(grfx, "KeyCode", xCode, 0);
BoldUnderline(grfx, "Modifiers", xMods, 0);
BoldUnderline(grfx, "KeyData", xData, 0);
BoldUnderline(grfx, "Shift", xShift, 0);
BoldUnderline(grfx, "Control", xCtrl, 0);
BoldUnderline(grfx, "Alt", xAlt, 0);
if (iNumValid < iNumLines) {
for (int i = 0; i < iNumValid; i++)
DisplayKeyInfo(grfx, i, i);
}
else {
for (int i = 0; i < iNumLines; i++)
DisplayKeyInfo(grfx, i, (iInsertIndex + i) % iNumLines);
}
}
void BoldUnderline(Graphics grfx, string str, int x, int y) {
Brush brush = new SolidBrush(ForeColor);
grfx.DrawString(str, Font, brush, x, y);
grfx.DrawString(str, Font, brush, x + 1, y);
// underline
SizeF sizef = grfx.MeasureString(str, Font);
grfx.DrawLine(new Pen(ForeColor),x,y+sizef.Height,x+ sizef.Width, y + sizef.Height);
}
void DisplayKeyInfo(Graphics grfx, int y, int i) {
Brush br = new SolidBrush(ForeColor);
y = (1 + y) * Font.Height; // converte y in coordinate pixel
grfx.DrawString(akeyevt[i].evttype.ToString(), Font, br, xEvent, y);
if (akeyevt[i].evttype == EventType.KeyPress) {
KeyPressEventArgs kpea = (KeyPressEventArgs)akeyevt[i].evtargs;
string str=String.Format("\x202D{0} (0x{1:X4})", kpea.KeyChar, (int)kpea.KeyChar);
grfx.DrawString(str, Font, br, xChar, y);
}
else {
KeyEventArgs kea = (KeyEventArgs)akeyevt[i].evtargs;
string str = String.Format("{0} ({1})", kea.KeyCode, (int)kea.KeyCode);
grfx.DrawString(str, Font, br, xCode, y);
grfx.DrawString(kea.Modifiers.ToString(), Font, br, xMods, y);
grfx.DrawString(kea.KeyData.ToString(), Font, br, xData, y);
grfx.DrawString(kea.Shift.ToString(), Font, br, xShift, y);
grfx.DrawString(kea.Control.ToString(), Font, br, xCtrl, y);
grfx.DrawString(kea.Alt.ToString(), Font, br, xAlt, y);
}
}
static void Main(string[] args)
{ Application.Run(new MainForm()); }
}
Visual C#
um 45 di 202
TabIndex
Ottiene o imposta l’ordine di tabulazione del controllo all’interno del relativo controllo
contenitore compreso il form.
Un indice di tabulazione può essere composto da numeri interi validi, maggiori di o uguali
a 0, con i numeri più bassi situati nelle prime posizioni dell’ordine di tabulazione.
Per includere un controllo nell’ordine di tabulazione si deve impostare la relativa proprietà
TabStop a true, per default il primo controllo creato ha TabIndex = 0.
TabStop
Ottiene o imposta un valore che indica se l’utente è in grado di attivare il controllo
utilizzando il tasto TAB.
Il valore è true default, se l’utente è in grado di attivare il controllo utilizzando il tasto TAB;
in caso contrario assume il valore false.
Quando l’utente preme il tasto TAB, lo stato attivo per l’input passa al successivo controllo,
nell’ordine di tabulazione, che abbia il valore della proprietà TabStop impostato a true.
Visual C#
um 46 di 202
EQUAZIONE DI 1°
finestre di dialogo: INPUT/OUTPUT
Avviene in due modi.
1. Casella di testo: TextBox (Input/Ouput)
È considerata una variabile, per cui si può utilizzare il suo contenuto; il valore Text è di
tipo string, se si deve eseguire la lettura di un numero bisogna convertirlo da string a
valore numerico.
int a = Convert.ToDecimal(textBox1.Text);
// input
textBox1.Text = a;
// output
2. Finestra di dialogo: Label (Prompt, Output), Message Box (Ouput)
Visual C++
CUI
#include
system("cls");
printf
scanf
system("pause");
Visual C#
CUI
using
Console.Clear();
Console.Write
Console.ReadLine
Console.ReadKey();
GUI
using
Controllo Label
Controllo TextBox
DISEGNO DELLA FINESTRA
La prima operazione è il disegno della finestra, per valutare dove inserire i controlli nella
finestra di progettazione; il window layout, la parte grafica della finestra dell’applicazione
per il calcolo dell’equazione, è così composto.
1. La barra del titolo in alto.
2. Due controlli Label per il prompt dei dati.
3. Due controlli TextBox per l’input dei dati.
4. Un controllo Button per calcolare l’equazione di primo grado.
5. Un controllo Label per visualizzare il risultato.
using System;
using System.Windows.Forms;
using System.Drawing;
Visual C#
um 47 di 202
class MainForm : Form {
// etichetta informativa associata al TextBox txtCoefficienteA
Label lblPromptA;
// etichetta informativa associata al TextBox txtCoefficienteB
Label lblPromptB;
// TextBox per l’acquisizione del coefficiente A
TextBox txtCoefficienteA;
// TextBox per l’acquisizione del coefficiente B
TextBox txtCoefficienteB;
// etichetta che visualizza la soluzione
Label lblSoluzione;
// pulsante che determina il calcolo della soluzione
Button btnCalcola;
public MainForm()
{ // imposta il testo della barra del titolo
Text = "Equazione 1°";
// imposta l'icona dell'applicazione
Icon = new Icon("max.ico");
// imposta le dimensioni dela finestra
Size = new Size(300, 210);
// determina la posizione iniziale del form
StartPosition = FormStartPosition.CenterScreen;
// crea e imposta le proprietà dell'etichetta lblPromptA
// etichetta informativa associata al txtCoefficienteA
lblPromptA = new Label();
lblPromptA.Text = "Coefficiente A";
lblPromptA.Top = 15;
lblPromptA.Left = 30;
// aggiunge lblPromptA al form
Controls.Add(lblPromptA);
// crea e imposta le proprietà del TextBox txtCoefficienteA
// TextBox per l’acquisizione del coefficiente A
txtCoefficienteA = new TextBox();
txtCoefficienteA.Top = 40;
txtCoefficienteA.Left = 30;
// aggiunge txtCoefficienteA al form
Controls.Add(txtCoefficienteA);
// crea e imposta le proprietà dell'etichetta lblPromptB
// etichetta informativa associata al txtCoefficienteB
lblPromptB = new Label();
lblPromptB.Text = "Coefficiente B";
lblPromptB.Top = 15;
lblPromptB.Left = 150;
// aggiunge lblPromptB al form
Controls.Add(lblPromptB);
// crea e imposta le proprietà del TextBox txtCoefficienteB
// TextBox per l’acquisizione del coefficiente B
txtCoefficienteB = new TextBox();
txtCoefficienteB.Top = 40;
txtCoefficienteB.Left = 150;
// aggiunge txtCoefficienteB al form
Controls.Add(txtCoefficienteB);
// crea e imposta le proprietà del bottone btnCalcola
// che determina il calcolo della soluzione
Visual C#
um 48 di 202
btnCalcola = new Button();
btnCalcola.Text = "Calcola";
btnCalcola.Top = 100;
btnCalcola.Left = 100;
// aggiunge btnCalcola al form
Controls.Add(btnCalcola);
btnCalcola.Click += new EventHandler(btnCalcola_Click);
// crea e imposta le proprietà dell’etichetta lblSoluzione
// etichetta che visualizza la soluzione
lblSoluzione = new Label();
lblSoluzione.Text = "";
lblSoluzione.Top = 150;
lblSoluzione.Left = 90;
lblSoluzione.ForeColor = Color.Blue;
lblSoluzione.AutoSize = true;
// aggiunge lblSoluzione al form
Controls.Add(lblSoluzione);
}
void btnCalcola_Click(object sender, EventArgs e)
{ double a = 0.0, b = 0.0, x = 0.0;
// acquisisce i coefficienti dai due TextBox
a = Convert.ToDouble(txtCoefficienteA.Text);
b = Convert.ToDouble(txtCoefficienteB.Text);
if (a == 0.0) {
if (b == 0.0)
lblSoluzione.Text = "Equazione indeterminata!";
else
lblSoluzione.Text = "Equazione impossibile!";
}
else {
if (b == 0.0)
lblSoluzione.Text = "Radice = 0.0!";
else {
x = -b / a;
lblSoluzione.Text = "Radice = " + x;
}
}
}
static void Main(string[] args)
{ Application.Run(new MainForm()); }
}
Visual C#
um 49 di 202
VERIFICA DELL’ESISTENZA DEI DATI
Un requisito fondamentale di qualsiasi interfaccia è di garantire che i dati siano stati
effettivamente inseriti prima che l’applicazione svolga qualsiasi elaborazione su di essi.
L’applicazione presenta due limiti nella gestione dell’interazione con l’utente.
1. Consente l’esecuzione del calcolo dell’equazione senza che siano stati inseriti i due
coefficienti.
2. Consente l’esecuzione del calcolo dell’equazione a prescindere dal fatto che i valori
inseriti siano effettivamente di natura numerica, per esempio caratteri qualsiasi,
dunque anche lettere, nei TextBox relativi ai due coefficienti.
L’interfaccia deve permettere la validazione dei dati, in altre parole dev’essere in grado di
garantire che le azioni e i dati inseriti dall’utente siano coerenti con la natura delle
elaborazioni effettuate dall’applicazione.
Il problema può essere affrontato in due modi distinti.
1. Verifica posticipata
Si controlla, all’interno del metodo che gestisce l’elaborazione dei dati, se questi esistono
davvero; se la verifica dà esito negativo, l’elaborazione è interrotta e l’utente è informato
che i dati non rispettano i requisiti.
if (txtCoefficienteA.Text == "" || txtCoefficienteB.Text == "")
{ MessageBox.Show("Uno o entrambi i coefficienti non sono stati inseriti","Equazione
1°",MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
2. Verifica anticipata
Impedisce all’utente di dare l’avvio all’elaborazione fino a quando tutti i dati non sono stati
inseriti; ciò si ottiene abilitando o disabilitando il pulsante Calcola.
Si crea una relazione tra lo stato del pulsante, abilitato o non abilitato e il contenuto dei
due TextBox grazie all’evento TextChanged generato dai due TextBox.
L’evento è generato ogni qual volta è modificato il contenuto del TextBox: ogni volta che
avviene una modifica, basta verificare se il contenuto di entrambi i TextBox sia o no
diverso dalla stringa nulla; se sì, la proprietà Enabled di btnCalcola è impostata a true,
altrimenti è impostata a false.
void txtCoefficienteAetxtCoefficienteB_TextChanged(object sender, EventArgs e)
{ btnCalcola.Enabled = (txtCoefficienteA.Text != "" && txtCoefficienteB.Text != ""); }
Il gestore di evento dev’essere attaccato a entrambi i TextBox.
txtCoefficienteA.TextChanged += new
Visual C#
um 50 di 202
EventHandler(txtCoefficienteAetxtCoefficienteB_TextChanged);
txtCoefficienteA.KeyPress += new
KeyPressEventHandler(txtCoefficienteAetxtCoefficienteB_KeyPress);
Se si esegue l’applicazione e l’utente inserisce il solo coefficiente A o B: il pulsante è
disabilitato.
Se si esegue l’applicazione, i due TextBox sono vuoti ma il pulsante è abilitato perché
l’utente non ha modificato il contenuto dei TextBox, l’evento TextChanged non è generato,
il gestore dell’evento non è eseguito e di conseguenza lo stato del pulsante non è
aggiornato.
È sufficiente impostare lo stato iniziale del pulsante in questo modo.
btnCalcola.Enabled = false;
VERIFICA DEL TIPO DI DATI
L’avere inserito “qualcosa” da parte dell’utente non è un requisito sufficiente perché possa
essere eseguita l’elaborazione.
Occorre che l’input sia coerente con il tipo di dati dell’applicazione: valori numerici, nomi e
codici fiscali.
Il problema può essere affrontato in due modi distinti.
1. Verifica posticipata
Sulla natura dei dati implica anche, per definizione, una verifica sulla loro esistenza; per
esempio verificare che il contenuto dei due TextBox sia effettivamente di natura numerica.
2. Verifica anticipata
Sfrutta gli eventi di tastiera sollevati dai controlli TextBox; ha lo scopo d’impedire all’utente
d’inserire caratteri che non siano coerenti con la natura dei dati, per esempio qualsiasi
carattere non appartenente all’insieme: 0-9, punto decimale e segno (-).
Si usa l’evento KeyPress.
void txtCoefficienteAetxtCoefficienteB_KeyPress(object sender, KeyPressEventArgs e)
{ char ch = e.KeyChar;
if (ch == 8)
// tasto BACKSPACE
return;
if (char.IsDigit(ch) == false && ch != ',' && ch != '-')
e.Handled = true;
}
La variabile ch contiene il codice del tasto premuto, proprietà KeyChar, è verificato se
Visual C#
um 51 di 202
corrisponde al tasto BACKSPACE: in questo caso, infatti, il metodo deve terminare e
lasciare che il controllo elabori il tasto.
In tutti gli altri casi è verificato se appartengono o no all’insieme dei caratteri coerenti con
la natura numerica dei dati.
Se non è così, la proprietà Handled è impostata a true, in pratica informando il controllo di
non elaborare e dunque di scartare, il carattere digitato.
Ci sono due osservazioni da fare.
1. Per verificare se un carattere appartiene all’insieme (0..9) è usato del metodo IsDigit
esposto dal tipo char che ritorna il valore true se l’argomento rientra nell’intervallo,
altrimenti ritorna false.
2. Il gestore di evento dev’essere attaccato a entrambi i TextBox.
txtCoefficienteA.KeyPress += new
KeyPressEventHandler(txtCoefficienteAetxtCoefficienteB_KeyPress);
txtCoefficienteB.KeyPress += new
KeyPressEventHandler(txtCoefficienteAetxtCoefficienteB_KeyPress);
Il codice del progetto con la validazione dei dati è il seguente.
using System;
using System.Windows.Forms;
using System.Drawing;
class MainForm : Form {
// etichetta informativa associata al TextBox txtCoefficienteA
Label lblPromptA;
// etichetta informativa associata al TextBox txtCoefficienteB
Label lblPromptB;
// TextBox per l’acquisizione del coefficiente A
TextBox txtCoefficienteA;
// TextBox per l’acquisizione del coefficiente B
TextBox txtCoefficienteB;
// etichetta che visualizza la soluzione
Label lblSoluzione;
// pulsante che determina il calcolo della soluzione
Button btnCalcola;
public MainForm()
{ // imposta il testo della barra del titolo
Text = "Equazione 1°";
// imposta l'icona dell'applicazione
Icon = new Icon("max.ico");
// imposta le dimensioni dela finestra
Size = new Size(300, 210);
// determina la posizione iniziale del form
StartPosition = FormStartPosition.CenterScreen;
// crea e imposta le proprietà dell'etichetta lblPromptA
// etichetta informativa associata al txtCoefficienteA
lblPromptA = new Label();
lblPromptA.Text = "Coefficiente A";
lblPromptA.Top = 15;
lblPromptA.Left = 30;
// aggiunge lblPromptA al form
Controls.Add(lblPromptA);
// crea e imposta le proprietà del TextBox txtCoefficienteA
// TextBox per l’acquisizione del coefficiente A
Visual C#
um 52 di 202
txtCoefficienteA = new TextBox();
txtCoefficienteA.Top = 40;
txtCoefficienteA.Left = 30;
// aggiunge txtCoefficienteA al form
Controls.Add(txtCoefficienteA);
// crea e imposta le proprietà dell'etichetta lblPromptB
// etichetta informativa associata al txtCoefficienteB
lblPromptB = new Label();
lblPromptB.Text = "Coefficiente B";
lblPromptB.Top = 15;
lblPromptB.Left = 150;
// aggiunge lblPromptB al form
Controls.Add(lblPromptB);
// validazione dati
txtCoefficienteA.TextChanged += new
EventHandler(txtCoefficienteAetxtCoefficienteB_TextChanged);
txtCoefficienteA.KeyPress += new
KeyPressEventHandler(txtCoefficienteAetxtCoefficienteB_KeyPress);
// crea e imposta le proprietà del TextBox txtCoefficienteB
// TextBox per l’acquisizione del coefficiente B
txtCoefficienteB = new TextBox();
txtCoefficienteB.Top = 40;
txtCoefficienteB.Left = 150;
// aggiunge txtCoefficienteB al form
Controls.Add(txtCoefficienteB);
// validazione dati
txtCoefficienteB.TextChanged += new
EventHandler(txtCoefficienteAetxtCoefficienteB_TextChanged);
txtCoefficienteB.KeyPress += new
KeyPressEventHandler(txtCoefficienteAetxtCoefficienteB_KeyPress);
// crea e imposta le proprietà del bottone btnCalcola
// che determina il calcolo della soluzione
btnCalcola = new Button();
btnCalcola.Text = "Calcola";
btnCalcola.Top = 100;
btnCalcola.Left = 100;
// aggiunge btnCalcola al form
Controls.Add(btnCalcola);
btnCalcola.Click += new EventHandler(btnCalcola_Click);
// crea e imposta le proprietà dell’etichetta lblSoluzione
// etichetta che visualizza la soluzione
lblSoluzione = new Label();
lblSoluzione.Text = "";
lblSoluzione.Top = 150;
lblSoluzione.Left = 90;
lblSoluzione.ForeColor = Color.Blue;
lblSoluzione.AutoSize = true;
// aggiunge lblSoluzione al form
Controls.Add(lblSoluzione);
}
void btnCalcola_Click(object sender, EventArgs e)
{ // verifica posticipata dell'esistenza dei dati
double a = 0.0, b = 0.0, x = 0.0;
if (txtCoefficienteA.Text == "" || txtCoefficienteB.Text == "") {
Visual C#
um 53 di 202
MessageBox.Show("Uno o entrambi i coefficienti non sono stati inseriti",
"Equazione 1°", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// acquisisce i coefficienti dai due TextBox
a = Convert.ToDouble(txtCoefficienteA.Text);
b = Convert.ToDouble(txtCoefficienteB.Text);
if (a == 0.0) {
if (b == 0.0)
lblSoluzione.Text = "Equazione indeterminata!";
else
lblSoluzione.Text = "Equazione impossibile!";
}
else {
if (b == 0.0)
lblSoluzione.Text = "Radice = 0.0!";
else {
x = -b / a;
lblSoluzione.Text = "Radice = " + x;
}
}
}
// verifica anticipata dell'esistenza dei dati
void txtCoefficienteAetxtCoefficienteB_TextChanged(object sender, EventArgs e)
{ btnCalcola.Enabled = (txtCoefficienteA.Text != "" && txtCoefficienteB.Text != ""); }
// verifica anticipata del tipo di dati
void txtCoefficienteAetxtCoefficienteB_KeyPress(object sender, KeyPressEventArgs e)
{ char ch = e.KeyChar;
if (ch == 8) // tasto BACKSPACE
return;
if (char.IsDigit(ch) == false && ch != ',' && ch != '-')
e.Handled = true;
}
static void Main(string[] args)
{ Application.Run(new MainForm()); }
}
Visual C#
um 54 di 202
APPLICAZIONI WINDOWS FORMS
INTRODUZIONE
Per creare un’applicazione è necessario eseguire tre passaggi fondamentali.
1. Creare l’UI (User Interface) aggiungendo ad un form i controlli desiderati.
2. Impostare le proprietà del form e dei controlli per definire gli attributi con il comando
Visualizza/Finestra Proprietà (F4).
3. Scrivere il codice per fornire la risposta agli eventi.
L’ambiente di progettazione diventa un potente generatore di codice.
Quando si disegna un controllo o s’imposta una sua proprietà, dalla finestra delle
Proprietà, sono generate, in automatico, le istruzioni che consentono di disegnare e
assegnare i valori delle proprietà.
Tale codice è racchiuso in una regione “collapsed” in modo che non sia possibile
modificarlo per sbaglio.
Per creare un nuovo progetto, fare clic su File/Nuovo/Progetto… (CTRL+N).
Nella finestra di dialogo Nuovo Progetto selezionare Altri linguaggi/Visual C#, quindi
selezionare il tipo di applicazione, per esempio Applicazione Windows Form.
Immettere
il
nome
del
progetto
nel
campo
Nome:,
per
esempio
WindowsFormsApplication1, quindi per creare il progetto, premere OK.
Visual C# crea una soluzione costituita da un progetto dallo stesso nome, composto a sua
volta da una finestra vuota denominata FORM1.CS.
In automatico, inoltre, mostrerà la finestra Progettazione in cui sarà visualizzato il form e
dove è possibile progettare rapidamente e visivamente la GUI (Graphic User Interface)
trascinando semplicemente i controlli selezionati.
Per visualizzare la finestra del codice, fare clic sul pulsante Visualizza/Codice (F7).
Per visualizzare la finestra di progettazione, fare clic su Visualizza/Finestra di
progettazione (MAIUSC+F7).
Fare clic su Visualizza/Finestra Proprietà (F4), per definire le proprietà dell’oggetto
selezionato.
È un vero e proprio editor che di default ordina le proprietà degli oggetti raggruppandoli
per tipologia, le proprietà di tipo complesso possono, a loro volta, aprire menu o finestre
Visual C#
um 55 di 202
aggiuntive.
Nel nuovo progetto, Visual C# inserisce automaticamente i seguenti file.
Properties
File RESOURCES.RSX
Risorse utilizzate dall’applicazione.
File SETTINGS.SETTINGS
Impostazioni dell’applicazione.
File FORM1.CS
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1()
{ InitializeComponent(); }
}
}
È la finestra di progettazione del form, le clausole using specificano quali sono i
namespace in cui cercare le classi che saranno utilizzate.
È poi definito il namespace WindowsFormsApplication1.
Tutti i form creati così come le classi, le interfacce e i controlli sono automaticamente
inseriti all’interno di un namespace che per impostazione predefinita ha lo stesso nome del
progetto.
Quest’impostazione può essere cambiata dal menu Progetto/Proprietà di
Visual C#
um 56 di 202
WindowsFormsApplication1…; nella scheda Applicazione è possibile specificare il tipo
di applicazione, il tipo di .NET Framework, il form di avvio nel caso di applicazioni con più
form, il nome dell’assembly, l’icona che identifica l’applicazione e altre informazioni.
Fare clic sul pulsante Informazioni assemby…, è possibile specificare il numero della
versione, il titolo, nome assegnato all’applicazione, le informazioni sulla versione,
copyright, descrizione del file e marchi registrati.
Visual C#
um 57 di 202
Nella scheda Compila, fare clic sul pulsante Piattaforma di destinazione: per
selezionare il tipo di CPU.
Il codice evidenzia che anche una Windows Form è una classe che eredita dalla classe
Form, la parola chiave partial permette di definire classi parziali.
Di norma, ogni classe risiede in un file separato ma utilizzando partial è possibile creare
classi definite su più file.
Da ciascun file partial è possibile accedere alle variabili, ai metodi, alle proprietà, di tutti gli
altri file partial relativi alla stessa classe, proprio come se l’intera dichiarazione della classe
fosse contenuta in un unico file.
Questo nuovo concetto favorisce una maggiore pulizia e leggibilità del codice della
Windows Form.
Il codice di progettazione che imposta le proprietà della finestra e dei controlli in essa
inseriti, è memorizzato in un file di supporto gestito internamente dall’ambiente di sviluppo
e chiamato nel modo seguente.
File FORM1.DESIGNER.CS
namespace WindowsFormsApplication1 {
partial class Form1 {
/// <summary>
/// Variabile di progettazione necessaria.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Liberare le risorse in uso.
/// </summary>
/// <param name="disposing">ha valore true se le risorse gestite devono essere
eliminate, false in caso contrario.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Codice generato da Progettazione Windows Form
/// <summary>
/// Metodo necessario per il supporto della finestra di progettazione. Non modificare
/// il contenuto del metodo con l'editor di codice.
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Text = "Form1";
}
#endregion
}
}
Visual C#
um 58 di 202
È lo stesso concetto di code behind nelle applicazioni web.
L’unico indizio dell’esistenza di questo file è il metodo InitializeComponent che è
richiamato nel costruttore del form: esso ha il compito d’inizializzare tutti gli oggetti
contenuti nel form.
Le classi partial e il loro utilizzo nelle Windows Forms si fanno apprezzare quando si crea
una finestra con un numero elevato di controlli.
Visual Studio consente un posizionamento dei controlli sul form molto agevole: i
controlli sembrano calamitati dai bordi della finestra e dagli altri oggetti; inoltre, durante le
operazioni di trascinamento, sono visualizzate delle linee guida per consentire un
posizionamento più preciso degli elementi nella finestra.
File PROGRAM.CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1 {
static class Program
{ /// <summary>
/// Punto di ingresso principale dell'applicazione.
/// </summary>
[STAThread]
static void Main(string[] args)
{ Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
Per aggiungere l’applicazione nell’area di notifica, dalla Casella degli strumenti
selezionare il controllo NotifyIcon e posizionarlo sul form, nella parte bassa comparirà
un’etichetta che indica che il componente è utilizzabile all’interno del progetto.
Controllo
Proprietà
Text
Form1
StartPosition
Icon
notifyIcon1 Icon
Valore
Prima applicazione
CenterScreen
max.ico
max.ico
Nella sezione Eventi alla voce DoubleClick fare un doppio clic per generare il template di
gestione dell’evento; qui dentro si scrive il codice seguente.
private void notifyIcon1_DoubleClick(object sender, EventArgs e)
{ Show();
Visual C#
um 59 di 202
WindowState = FormWindowState.Normal;
}
Selezionare il form e nella sezione Eventi, selezionare l’evento Resize, fare un doppio clic
per generare il template di gestione dell’evento; qui dentro si scrive il codice seguente.
private void Form1_Resize(object sender, EventArgs e)
{ if (FormWindowState.Minimized == WindowState)
Hide();
}
EVENTI
Gli eventi disponibili sono riconoscibili, nell’elenco visualizzato tramite l’IntelliSense o dalla
finestra Proprietà, dal simbolo di un fulmine.
L’IntelliSense propone un nome per il gestore degli eventi: è sufficiente premere il tasto
TAB per confermare il suggerimento.
Il metodo che rappresenta l’azione collegata all’evento è chiamato delegate.
Infatti, è delegata a quel metodo la gestione dell’evento.
Form1_Load in questo caso è un delegate.
EventHandler e tutte le classi derivate sono gestori di eventi.
Ogni oggetto istanza di event hanlder richiede il riferimento ad un delegate per la gestione
di un evento, la stringa Form1_Load senza parentesi altro non è che un riferimento, un
puntatore, al metodo Form1_Load.
Un delegate in astratto ha però una certa firma e per rimpiazzarlo con un altro metodo
come Form_Load occorre che questo abbia i medesimi parametri in tipo e numero e lo
stesso tipo di ritorno.
Con EventHandler(Form1_Load) si esprime il fatto che il metodo Form1_Load avrà
parametri uguali in numero e tipo a quelli indicati nella dichiarazione del delegate
EventHandler e avrà come tipo di ritorno il medesimo tipo del delegate.
Un’altra pressione del tasto TAB aggiunge automaticamente, all’interno della classe, il
metodo che sarà richiamato quando si genera l’evento in questione.
Visual C#
um 60 di 202
private void Form1_Load(object sender, EventArgs e)
{ throw new Exception("Metodo non implementato."); }
L’istruzione throw new Exception solleva un’eccezione.
I parametri di questo metodo sono due.
1. object sender: rappresenta l’oggetto, il controllo, la classe che ha generato l’evento ed
è utile nel caso in cui lo stesso metodo sia eseguito in risposta ad eventi lanciati da
oggetti diversi, per sapere chi effettivamente lo ha generato.
2. EventArgs e: come pure tutte le classi da essa derivate, contiene i parametri associati
all’evento, e è di tipo EventArgs ma, può essere specializzata a seconda dell’evento;
ad esempio, nel caso di eventi di gestione del mouse MouseClick, MouseMove, è di
tipo MouseEventArgs e permette di conoscere la posizione del cursore o quale tasto
del mouse è stato premuto.
Gli event handler possono essere creati anche in un altro modo.
All’interno della finestra di progettazione, selezionare il controllo per cui si vuole definire il
gestore, quindi spostarsi nella finestra Proprietà e fare clic sull’icona raffigurante un
fulmine.
Comparirà un elenco contenente tutti gli eventi generati dal controllo in questione.
Selezionandone uno, nella parte bassa della finestra sarà visualizzato un breve
messaggio che indica quando tal evento è lanciato.
Cliccare su un evento, digitare il nome di un metodo nella casella di testo corrispondente e
premere il tasto INVIO, Visual Studio aggiungerà automaticamente il codice per installare
l’event handler all’interno della finestra di progettazione e inserirà nel file del form un
metodo con il nome indicato.
In alternativa, facendo doppio clic sul nome dell’evento, sarà creato un event handler e
una routine di gestione con il nome predefinito formato da nome del controllo, trattino
basso e nome dell’evento.
private void Form1_Load(object sender, EventArgs e)
{}
Per eliminare un gestore degli eventi creato in questo modo, fare clic con il tasto destro del
mouse sull’evento e selezionare il comando Annulla.
Infine, per tornare alla visualizzazione delle proprietà del controllo, premere il pulsante
Proprietà che si trova a sinistra dell’icona con il fulmine.
Ancora, facendo doppio clic su di un oggetto, Visual Studio definirà automaticamente un
event handler per l’evento predefinito dell’oggetto in questione, Load per i form, Click per i
pulsanti, TextChanged per le caselle di testo.
Il .NET Framework nasconde la complessità di basso livello dei messaggi grazie al
concetto di evento.
Un oggetto genera degli eventi per un altro oggetto per cui intraprende delle azioni: è il
modello del produttore-consumatore.
Tipicamente sono usati per gestire nelle Windows Forms le notifiche dai controlli al
contenitore: il form.
Si parla.
 Publisher: invia eventi a tutti i subscriber.
 Subscriber: chi è interessato a ricevere gli eventi.
Visual C#
um 61 di 202
Controlli
La Casella degli strumenti contiene l’elenco dei componenti che possono essere inseriti
all’interno del form e prendono il nome di controlli.
Usati per disegnare, muovere o ridimensionare gli oggetti nel form.
Eventuali oggetti di terze parti sono registrati in automatico e raggruppati in maniera
opportuna.
Derivano dalla classe: System.Windows.Forms.Control.
Quando si crea un controllo, è creata una copia o istanza della classe di controllo.
Si può scegliere lo strumento voluto semplicemente cliccando su di esso.
Fare clic su Visualizza/Casella degli strumenti (CTRL+ALT+X).
Tutti impacchettati in classi con metodi e proprietà che ne permettono la personalizzazione
Visual C#
um 62 di 202
secondo le esigenze, i controlli consentono di realizzare applicazioni dall’aspetto
professionale con uno sforzo minimo.
All’interno della Casella degli strumenti di Visual Studio i controlli sono divisi in categorie.
 Controlli comuni contiene gli oggetti presenti in ogni finestra, Label, TextBox, Button,
ListBox, ComboBox.
 Contenitori raggruppa i controlli che permettono di gestire il layout del form, in altre
parole la disposizione degli oggetti.
 Menu e barre degli strumenti contiene oggetti che permettono di aggiungere dei
menu, barre degli strumenti, barra di stato e menu contestuali all’applicazione.
 Dati contiene gli strumenti che permettono di lavorare con fonti di dati esterne come i
DB.
 Componenti, raggruppa oggetti i quali consentono d’interagire con il SO (Sistema
Operativo) per gestire le Active Directory e il registro eventi di Windows, monitorare le
modifiche al file system.
 Stampa contiene gli oggetti necessari per aggiungere le funzionalità di stampa
all’applicazione.
 Finestre di dialogo contiene i controlli che consentono di visualizzare le finestre di
dialogo comuni di Windows, come Apri e Salva con nome.
PROPERTYGRID
Una volta creato il controllo basta impostare la proprietà SelectedObject, passandogli
come argomento un’istanza di una qualunque classe.
Il funzionamento è basato sul concetto di reflection del codice: analizza i componenti di
una classe e produce in automatico i campi necessari a controllare l’oggetto.
Le proprietà possono essere suddivise per categoria e sempre tramite reflection, sono
ricavati i valori di particolari attributi applicati alle proprietà stesse.
Oltre a questa visualizzazione, si ha anche la possibilità di ordinare le proprietà in ordine
alfabetico, agendo sui pulsanti della toolbar.
Nella parte bassa, è mostrato un riquadro contenente l’eventuale descrizione.
È possibile personalizzare l’aspetto della PropertyGrid, ad esempio impostando a false le
proprietà ToolbarVisible e HelpVisible si nascondono rispettivamente la toolbar e il
pannello di descrizione.
Senza la toolbar è possibile impostare da codice la modalità di ordinamento alfabetico
ponendo al valore true la proprietà a PropertySort.Alphabetical.
Per usufruire della suddivisione in categorie e della possibilità di visualizzare una
descrizione per le proprietà basta aggiungere dei particolari attributi alle proprietà.
Occorre aggiungere il namespace System.ComponentModel.
Per esempio, per inserire la proprietà Foto della classe Automobile in una categoria
Aspetto e di voler dare una descrizione alla proprietà stessa, è sufficiente fare uso degli
attributi Category e Description.
Le proprietà per le quali non si specifica alcuna categoria, saranno inserite per default
nella categoria Misc (miscellaneous).
La PropertyGrid usa la reflection per ottenere le proprietà, ogni proprietà pubblica sarebbe
visualizzata ma nel caso in cui si voglia evitare questo comportamento, per qualche
proprietà in particolare, è possibile utilizzare l’attributo [Browsable(false)].
Un’altro modo per filtrare le proprietà da visualizzare è quella di utilizzare la proprietà
BrowsableAttributes, specificando le categorie che si vogliono rendere visibili.
propertyGrid1.BrowsableAttributes=new AttributeCollection(new Attribute[]
{ new CategoryAttribute("Name"),new CategoryAttribute("Layout") }
Il controllo PropertyGrid supporta molteplici tipi in maniera automatica, ad esempio la
classe Automobile possiede la proprietà Foto di classe Bitmap, allora è visualizzata una
Visual C#
um 63 di 202
miniatura della bitmap utilizzata, se presente e le varie informazioni su di essa, come
risoluzione e dimensioni.
La proprietà DataImmatricolazione di classe DateTime permette di selezionare la data
d’immatricolazione tramite un controllo DateTimePicker e il Colore per mezzo della
tavolozza dei colori.
Invece, per un tipo definito dal programmatore, bisognerà gestire le conversioni
manualmente, fornendo metodi ad hoc per convertire un tipo da e verso una stringa o
anche delle interfacce grafiche per impostare in maniera rapida i valori di una proprietà di
un tipo personalizzato.
SYSTEM.COMPONENTMODEL
Fornisce le classi usate per implementare il comportamento di controlli e componenti sia
run-time sia a design-time.
Contiene ad esempio le classi base per i type converter, per gli attributi, per il databinding
e per la gestione del licensing dei controlli.
Esempio, cambiare il numero di targa di un’auto.
Creare la classe Automobile tramite codice.
Trascinare i componenti PropertyGrid e Label sul form.
using System;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
namespace Auto {
public partial class Form1 : Form {
class Automobile {
private int cilindrata;
private string targa;
private Bitmap foto;
private DateTime dataImmatricolazione;
private Color colore;
[Category("Informazioni Legali")]
[Description("Data d'immatricolazione dell'auto")]
public DateTime DataImmatricolazione
{ get { return dataImmatricolazione; }
set { dataImmatricolazione = value; }
}
[Category("Informazioni Legali")]
[Description("Targa dell'auto")]
public string Targa
{ get { return targa; }
set { targa = value; }
}
[Category("Aspetto")]
[Description("Colore dell'auto")]
public Color Colore
{ get { return colore; }
set { colore = value; }
}
[Category("Aspetto")]
[Description("Foto dell'auto")]
public Bitmap Foto
{ get { return foto; }
Visual C#
um 64 di 202
set { foto = value; }
}
[Category("Meccanica")]
[Description("Cilindrata del motore")]
public int Cilindrata
{ get { return cilindrata; }
set { cilindrata = value; }
}
public Automobile()
{}
}
public Form1()
{ InitializeComponent();
Automobile auto = new Automobile();
auto.Targa = "EM497XM";
auto.Cilindrata = 1600;
propertyGrid1.SelectedObject = auto;
}
private void propertyGrid1_SelectedGridItemChanged(object
sender,System.Windows.Forms.SelectedGridItemChangedEventArgs e)
{ label1.Text=e.NewSelection.Value.ToString(); }
private void propertyGrid1_PropertyValueChanged(object
s,System.Windows.Forms.PropertyValueChangedEventArgs e)
{ label1.Text=e.ChangedItem.Value.ToString(); }
}
}
Visual C#
um 65 di 202
DATAGRIDVIEW
Visualizza i dati in una griglia personalizzabile suddivisa in colonne (Columns), righe
(Rows) a loro volta composte di celle (Cells).
Il controllo fornisce numerosi metodi, proprietà ed eventi per personalizzare il suo aspetto
e il suo comportamento sia a design time, in modo visuale da Visual Studio sia a run-time,
in maniera programmatica.
Se l’applicazione contiene già una DataSource, essa può essere assegnata alla griglia per
mezzo del suo smart tag.
Caratteristiche.
 Un’origine dati: Dataset, Datatable, Dataview e Matrice.
 Oggetti DataGridTableStyle che controllano l’aspetto delle griglie e appartengono alla
collezione GridTableStylesCollection.
 Ogni oggetto DataGridTableStyle ha una collezione di oggetti che derivano dall’oggetto
astratto DataGridColumnStyle.
Gli oggetti derivanti da DataGridColumnStyle che sono impiegati nella griglia sono
DataGridTextBoxColumn che controlla le colonne con celle contenenti testo e
DataGridBoolColumn che controlla le colonne con valori true/false rappresentati con
checkbox.
Nulla vieta di creare un oggetto derivante da DataGridColumnStyle per mettere nelle celle
tutto quello che si desidera.
La classe DataGridTableStyle rappresenta solo la griglia come disegnata nel controllo.
Visual C#
um 66 di 202
Non confondere questa griglia con la classe DataTable che può rappresentare un’origine
di dati per la griglia.
Tramite la classe DataGridTableStyle è perciò possibile controllare l’aspetto della griglia di
ogni oggetto DataTable.
Diversi tipi di colonne
Utilizzabili per i dati, ognuno dei quali è una classe derivata dalla classe
DataGridViewColumn.
 IDataGridViewTextBoxColumn: utilizzata per rappresentare valori visibili come testo,
dunque in genere numeri e stringhe.
 IDataGridViewCheckBoxColumn: usata con i tipi bool e CheckState.
 IDataGridViewImageColumn: in genere è associata a oggetti Image oppure Icon.
 IDataGridViewComboBoxColumn: quando si vogliono selezionare dei valori da una
ComboBox per inserirli in una riga, non è automaticamente associata alla DataSource
ma bisogna generarle e popolarle manualmente.
 IDataGridViewLinkColumn: permette di mostrare dei link ipertestuali all’interno delle
celle e anche questa è associata manualmente ai dati contenuti nella griglia.
 IDataGridViewButtonColumn: se si ha bisogno di una colonna con un pulsante
mediante il quale eseguire comandi o altre operazioni.
using System.Windows.Forms;
namespace Vettore {
public partial class Form1 : Form {
const int N = 10;
int[] vet = new int[N];
void carica()
{ for (int i = 0; i < N; ++i)
vet[i] = i+1;
}
public Form1()
{ InitializeComponent();
carica();
}
private void btnEsegui_Click(object sender, System.EventArgs e)
{ string StringaDaAggiungere;
dgvVettore.Columns.Add("Vettore", "Stampa di un vettore");
dgvVettore.ColumnHeadersVisible = true;
dgvVettore.RowHeadersVisible = false;
dgvVettore.AutoSizeColumnsMode =
DataGridViewAutoSizeColumnsMode.AllCells;
dgvVettore.ScrollBars = ScrollBars.None;
dgvVettore.DefaultCellStyle.Format ="N2";
for (int i = 0; i < N; ++i) {
StringaDaAggiungere = "Elemento in posizione vet[" + i + "] = " +
vet[i].ToString();
dgvVettore.Rows.Add(StringaDaAggiungere);
}
}
private void btnNuovo_Click(object sender, System.EventArgs e)
{ dgvVettore.Columns.Remove("Vettore");
btnEsegui.Focus();
}
private void btnEsci_Click(object sender, System.EventArgs e)
{ Application.Exit(); }
Visual C#
um 67 di 202
}
}
TREEVIEW
File TREE.CS
using System.IO;
using System.Windows.Forms;
class DirectoryTreeView : TreeView {
public DirectoryTreeView() {
Width *= 2;
ImageList = new ImageList();
RefreshTree();
}
public void RefreshTree() {
BeginUpdate();
Nodes.Clear();
string[] astrDrives = Directory.GetLogicalDrives();
foreach (string str in astrDrives) {
TreeNode tnDrive = new TreeNode(str, 0, 0);
Nodes.Add(tnDrive);
AddDirectories(tnDrive);
if (str == "F:\\") SelectedNode = tnDrive;
}
EndUpdate();
Visual C#
um 68 di 202
}
void AddDirectories(TreeNode tn) {
tn.Nodes.Clear();
string strPath = tn.FullPath;
DirectoryInfo dirinfo = new DirectoryInfo(strPath);
DirectoryInfo[] adirinfo;
try
{ adirinfo = dirinfo.GetDirectories(); }
catch
{ return; }
foreach (DirectoryInfo di in adirinfo) {
TreeNode tnDir = new TreeNode(di.Name, 1, 2);
tn.Nodes.Add(tnDir);
}
}
protected override void OnBeforeExpand(TreeViewCancelEventArgs tvcea) {
base.OnBeforeExpand(tvcea);
BeginUpdate();
foreach (TreeNode tn in tvcea.Node.Nodes)
AddDirectories(tn);
EndUpdate();
}
}
File PROGRAM.CS
using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
class DirectoriesAndFiles : Form {
DirectoryTreeView dirtree;
Panel panel;
TreeNode tnSelect;
static void Main(string[] args)
{ Application.Run(new DirectoriesAndFiles()); }
public DirectoriesAndFiles() {
Text = "Cartelle e File";
Icon = new Icon("max.ico");
StartPosition = FormStartPosition.CenterScreen;
BackColor = SystemColors.Window;
ForeColor = SystemColors.WindowText;
panel = new Panel();
panel.Parent = this;
panel.Dock = DockStyle.Fill;
panel.Paint += new PaintEventHandler(PanelOnPaint);
Splitter split = new Splitter();
split.Parent = this;
split.Dock = DockStyle.Left;
split.BackColor = SystemColors.Control;
dirtree = new DirectoryTreeView();
dirtree.Parent = this;
dirtree.Dock = DockStyle.Left;
dirtree.AfterSelect += new TreeViewEventHandler(DirectoryTreeViewOnAfterSelect);
Menu = new MainMenu();
Visual C#
um 69 di 202
Menu.MenuItems.Add("Vedi");
MenuItem mi = new MenuItem("Refresh",new EventHandler(MenuOnRefresh),
Shortcut.F5);
Menu.MenuItems[0].MenuItems.Add(mi);
}
void DirectoryTreeViewOnAfterSelect(object obj, TreeViewEventArgs tvea) {
tnSelect = tvea.Node;
panel.Invalidate();
}
void PanelOnPaint(object obj, PaintEventArgs pea) {
if (tnSelect == null) return;
Panel panel = (Panel)obj;
Graphics grfx = pea.Graphics;
DirectoryInfo dirinfo = new DirectoryInfo(tnSelect.FullPath);
FileInfo[] afileinfo;
Brush brush = new SolidBrush(panel.ForeColor);
int y = 0;
try
{
afileinfo = dirinfo.GetFiles();
}
catch
{
return;
}
foreach (FileInfo fileinfo in afileinfo) {
grfx.DrawString(fileinfo.Name, Font, brush, 0, y);
y += Font.Height;
}
}
void MenuOnRefresh(object obj, EventArgs ea)
{ dirtree.RefreshTree(); }
}
Focus
Se si hanno diverse caselle di testo, la casella nella quale sarà immesso il testo digitato
sarà quella con il focus.
Per spostare il focus tra i controlli in fase di run-time, occorre premere il tasto TAB.
L’ordine di spostamento del focus tra i controlli di un modulo non è arbitrario.
A mano a mano che s’inseriscono controlli in un modulo, a essi è assegnato un valore per
la proprietà TabIndex.
Il primo controllo a essere posizionato nel modulo ha una TabIndex uguale a 0, il secondo
Visual C#
um 70 di 202
uguale a 1, il terzo uguale a 2 e così via.
Questo è lo stesso ordine che seguirà il focus nei controlli quando si fa clic su TAB.
Dopo aver posizionato tutti i controlli nel modulo, se non si è soddisfatti dell’ordine di
tabulazione risultante, si può cambiarlo manualmente utilizzando la finestra Proprietà per
impostare TabIndex.
Visual C# genera 6 eventi diversi quando un controllo riceve o perde lo stato attivo.
Per stato attivo s’intende la capacità di un controllo di ricevere l’input dell’utente tramite il
mouse o la tastiera.
Gli eventi di attivazione del focus si verificano nel seguente ordine.
Enter generato quando è immesso il controllo.
GotFocus generato quando il controllo riceve lo stato attivo.
Leave generato quando lo stato attivo esce dall’area del controllo.
Validating generato quando il controllo è convalidato.
Validated generato al termine della convalida del controllo.
LostFocus il controllo perde lo stato attivo.
Gli eventi Validating e Validated sono scatenati soltanto se la proprietà CausesValidation è
impostata a true, sia nel controllo che possiede il focus sia in quello che cerca di ottenerlo.
Generalmente l’evento Validating è usato per intercettare i valori non validi di un campo.
Se il valore di un controllo non è del tipo previsto dal codice, si deve semplicemente
impostare la proprietà Cancel del parametro al valore true.
Per la gestione del focus, si possono utilizzare oltre alle proprietà Enabled, TabIndex e
TabStop le due seguenti.
CanFocus consente di determinare se un dato controllo è in grado di ottenere il focus di
input cosa che accade quando entrambe le proprietà Visible ed Enabled risultino
impostate a true.
Focused indica se il controllo possiede il focus.
Visual C#
um 71 di 202
EQUAZIONE DI 1°
DISEGNO DELLA FINESTRA
I controlli sono disegnati nella finestra di progettazione.
L’applicazione risolve lo stesso problema del suo omologo console e possiede la stessa
business logic.
Il modello di comunicazione impiegato non presuppone una sequenza predefinita di azioni
da parte dell’utente e di elaborazioni da parte dell’applicazione.
Infatti, l’utente è libero di compiere le seguenti azioni.
 Chiudere l’applicazione, cliccando sull’icona di chiusura del form.
 Cliccare sul pulsante Calcola, determinando così il calcolo della soluzione, senza aver
inserito alcun valore in uno dei due TextBox o in entrambi.
 Inserire prima il valore del coefficiente b e poi quello del coefficiente a o viceversa.
 Inserire i due valori dei coefficienti ma non eseguire il calcolo della soluzione, cliccando
invece sull’icona di chiusura del form.
 Calcolare la soluzione di quante equazioni desidera, modificando ogni volta i valori dei
due coefficienti e cliccando nuovamente sul pulsante Calcola.
Dal punto di vista dell’applicazione, invece, la parte business logic è eseguita se e quando
l’utente clicca sul pulsante Calcola, in risposta all’evento Click generato dal pulsante
stesso.
Nell’applicazione console l’esecuzione della business logic è implicitamente garantita dalla
struttura del codice: infatti, il flusso di esecuzione, per raggiungere l’ultima istruzione del
metodo Main deve per forza “passare” per la parte business logic.
Anche in un’applicazione Windows, dunque, l’elaborazione avviene in risposta alle azioni
dell’utente ma queste ultime non sono predeterminate dalla struttura del codice.
L’interfaccia dell’applicazione contiene sei controlli, oltre al form.
Controllo
Proprietà
Text
StartPosition
Form1
Icon
Size
Top
txtCoefficienteA
Left
txtCoefficienteB Top
Visual C#
Valore
Equazione 1°
CenterScreen
max.ico
300, 210
40
30
40
um 72 di 202
lblPromptA
lblPromptB
lblSoluzione
btnCalcola
Left
Text
Top
Left
Text
Top
Left
Text
Top
Left
ForeColor
AutoSize
Text
Top
Left
Enable
150
Coefficiente A
15
30
Coefficiente B
15
150
x non calcolata
150
90
Colore Blue
True
Calcola
100
100
False
File FORM1.CS
using System;
using System.Windows.Forms;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1()
{ InitializeComponent(); }
private void btnCalcola_Click(object sender, EventArgs e)
{ double a = 0.0, b = 0.0, x = 0.0;
// acquisisce i coefficienti dai due TextBox
a = Convert.ToDouble(txtCoefficienteA.Text);
b = Convert.ToDouble(txtCoefficienteB.Text);
if (a == 0.0) {
if (b == 0.0)
lblSoluzione.Text = "Equazione indeterminata!";
else
lblSoluzione.Text = "Equazione impossibile!";
Visual C#
um 73 di 202
}
else {
if (b == 0.0)
lblSoluzione.Text = "Radice = 0.0!";
else {
x = -b / a;
lblSoluzione.Text = "Radice = " + x;
}
}
}
}
}
In grassetto sono evidenziate le istruzioni della business logic, le sole digitate.
Eseguire l’applicazione.
VERIFICA DELL’ESISTENZA DEI DATI
Un requisito fondamentale di qualsiasi interfaccia è di garantire che i dati siano stati
effettivamente inseriti prima che l’applicazione svolga qualsiasi elaborazione su di essi.
L’applicazione presenta due limiti nella gestione dell’interazione con l’utente.
1. Consente l’esecuzione del calcolo dell’equazione senza che siano stati inseriti i due
coefficienti.
2. Consente l’esecuzione del calcolo dell’equazione a prescindere dal fatto che i valori
inseriti siano effettivamente di natura numerica, per esempio caratteri qualsiasi,
dunque anche lettere, nei TextBox relativi ai due coefficienti.
L’interfaccia deve permettere la validazione dei dati, in altre parole dev’essere in grado di
garantire che le azioni e i dati inseriti dall’utente siano coerenti con la natura delle
elaborazioni effettuate dall’applicazione.
Il problema può essere affrontato in due modi distinti.
1. Verifica posticipata
Si controlla, all’interno del metodo che gestisce l’elaborazione dei dati, se questi esistono
davvero; se la verifica dà esito negativo, l’elaborazione è interrotta e l’utente è informato
che i dati non rispettano i requisiti.
private void btnCalcola_Click(object sender, EventArgs e)
{ double a = 0.0, b = 0.0, x = 0.0;
#region VERIFICA_POSTICIPATA_ESISTENZA_DATI
// verifica posticipata dell'esistenza dei dati
Visual C#
um 74 di 202
if (txtCoefficienteA.Text == "" || txtCoefficienteB.Text == "") {
MessageBox.Show("Uno o entrambi i coefficienti non sono stati inseriti",
"Equazione 1°", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
2. Verifica anticipata
Impedisce all’utente di dare l’avvio all’elaborazione fino a quando tutti i dati non sono stati
inseriti; ciò si ottiene abilitando o disabilitando il pulsante Calcola.
Si crea una relazione tra lo stato del pulsante, abilitato o non abilitato e il contenuto dei
due TextBox grazie all’evento TextChanged generato dai due TextBox.
L’evento è generato ogni qual volta è modificato il contenuto del TextBox: ogni volta che
avviene una modifica, basta verificare se il contenuto di entrambi i TextBox sia o no
diverso dalla stringa nulla; se sì, la proprietà Enabled di btnCalcola è impostata a true,
altrimenti è impostata a false.
#region VERIFICA_ANTICIPATA_ESISTENZA_DATI
// verifica anticipata dell'esistenza dei dati
private void txtCoefficienteA_TextChanged(object sender, EventArgs e)
{ btnCalcola.Enabled = (txtCoefficienteA.Text != "" && txtCoefficienteB.Text != ""); }
private void txtCoefficienteB_TextChanged(object sender, EventArgs e)
{ btnCalcola.Enabled = (txtCoefficienteA.Text != "" && txtCoefficienteB.Text != ""); }
#endregion
Se si esegue l’applicazione e l’utente inserisce il solo coefficiente A o B: il pulsante è
disabilitato.
Se si esegue l’applicazione, i due TextBox sono vuoti ma il pulsante è abilitato perché
l’utente non ha modificato il contenuto dei TextBox, l’evento TextChanged non è generato,
il gestore dell’evento non è eseguito e di conseguenza lo stato del pulsante non è
aggiornato.
Visual C#
um 75 di 202
È sufficiente impostare lo stato iniziale del pulsante in questo modo.
VERIFICA DEL TIPO DI DATI
L’avere inserito “qualcosa” da parte dell’utente non è un requisito sufficiente perché possa
essere eseguita l’elaborazione.
Occorre che l’input sia coerente con il tipo di dati dell’applicazione: valori numerici, nomi e
codici fiscali.
Il problema può essere affrontato in due modi distinti.
1. Verifica posticipata
Sulla natura dei dati implica anche, per definizione, una verifica sulla loro esistenza; per
esempio verificare che il contenuto dei due TextBox sia effettivamente di natura numerica.
2. Verifica anticipata
Sfrutta gli eventi di tastiera sollevati dai controlli TextBox; ha lo scopo d’impedire all’utente
d’inserire caratteri che non siano coerenti con la natura dei dati, per esempio qualsiasi
carattere non appartenente all’insieme: 0-9, punto decimale, segno (-).
Si usa l’evento KeyPress.
#region VERIFICA_ANTICIPATA_TIPO_DI_DATI
// verifica anticipata del tipo di dati
private void txtCoefficienteA_KeyPress(object sender, KeyPressEventArgs e)
{ char ch = e.KeyChar;
if (ch == 8)
// tasto BACKSPACE
return;
if (char.IsDigit(ch) == false && ch != ',' && ch != '-')
e.Handled = true;
}
private void txtCoefficienteB_KeyPress(object sender, KeyPressEventArgs e)
{ char ch = e.KeyChar;
if (ch == 8) // tasto BACKSPACE
return;
if (char.IsDigit(ch) == false && ch != ',' && ch != '-')
e.Handled = true;
}
#endregion
La variabile ch contiene il codice del tasto premuto, proprietà KeyChar, è verificato se
corrisponde al tasto BACKSPACE: in questo caso, infatti, il metodo deve terminare e
lasciare che il controllo elabori il tasto.
In tutti gli altri casi è verificato se appartengono o no all’insieme dei caratteri coerenti con
la natura numerica dei dati.
Se non è così, la proprietà Handled è impostata a true, in pratica informando il controllo di
non elaborare e dunque di scartare, il carattere digitato.
Ci sono due osservazioni da fare.
1. Per verificare se un carattere appartiene all’insieme (0..9) è usato del metodo IsDigit
esposto dal tipo char.
Visual C#
um 76 di 202
2. Il metodo ritorna il valore true se l’argomento rientra nell’intervallo, altrimenti ritorna
false.
VERIFICA DELL’ESISTENZA E DEL TIPO DI DATI CON TRY CATCH
using System;
using System.Windows.Forms;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1()
{ InitializeComponent(); }
private void btnCalcola_Click(object sender, EventArgs e)
{ double a = 0.0, b = 0.0, x = 0.0;
try {
// acquisisce i coefficienti dai due TextBox
a = Convert.ToDouble(txtCoefficienteA.Text);
b = Convert.ToDouble(txtCoefficienteB.Text);
}
catch (Exception)
{ MessageBox.Show("Uno o entrambi i coefficienti non sono stati inseriti o non
sono corretti", "Equazione 1°", MessageBoxButtons.OK, MessageBoxIcon.Error); }
if (a == 0.0) {
if (b == 0.0)
lblSoluzione.Text = "Equazione indeterminata!";
else
lblSoluzione.Text = "Equazione impossibile!";
}
else {
if (b == 0.0)
lblSoluzione.Text = "Radice = 0.0!";
else {
x = -b / a;
lblSoluzione.Text = "Radice = " + x;
}
}
}
}
}
Classe ErrorProvider
Permette di dare all’utente dell’applicazione un feedback su determinate situazioni di
errore, è utilizzata quando dei controlli da valorizzare sono riempiti con valori non validi.
Per esempio, una TextBox che deve contenere solo numeri, oppure che dev’essere
validata secondo certi criteri, come numeri solo positivi o stringhe in un certo formato.
Mediante i metodi della classe ErrorProvider, è possibile mostrare all’utente un’icona al
lato di un determinato controllo che non è stato valorizzato correttamente.
Fare clic sulla Casella degli strumenti gruppo Componenti e trascinare il componente
ErrorProvider sul form, se non sono stati inseriti altri componenti di questo tipo, assumerà
il nome ErrorProvider1.
Una volta aggiunto il componente, è possibile aggiungere anche il codice di validazione
delle caselle di testo, nei gestori degli eventi Validating di ogni controllo che c’interessa
verificare.
Il componente ErrorProvider permette di realizzare un sistema di validazione dei dati
inseriti dall’utente in modo facile e con un impatto visuale efficace.
Visual C#
um 77 di 202
Questo non significa che i dati non debbano anche essere verificati da codice, per evitare
che l’utente ignori le segnalazioni di errore e prosegua comunque nell’elaborazione.
Un’altra possibilità per evitare questo comportamento dell’utente è quella di disattivare i
pulsanti che avviano le elaborazioni, finché ci sono degli errori di validazione.
Esempio, il nome del cliente è un campo obbligatorio, quando si valida il form,
chiudendolo, è possibile verificare la presenza del nome del cliente e se il campo è vuoto
mostrare un’icona di errore.
using System;
using System.Windows.Forms;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1()
{ InitializeComponent();
errorProvider1.SetIconAlignment(txtInput, ErrorIconAlignment.MiddleRight);
errorProvider1.SetIconPadding(txtInput, 2);
// 2 pixel di padding
errorProvider1.BlinkRate = 1000;
errorProvider1.BlinkStyle = ErrorBlinkStyle.AlwaysBlink;
}
private bool IsNameValid()
{ // determini se la casella di testo contiene una stringa nulla
return (txtInput.Text.Length > 0);
Visual C#
um 78 di 202
}
private void txtInput_Validated(object sender, EventArgs e)
{ if (IsNameValid())
// pulisce l'errore nell'error provider
errorProvider1.SetError(txtInput, "");
else
// il nome non è valido
errorProvider1.SetError(txtInput, "E' richiesto il nome!");
}
private void btnInserisci_Click(object sender, EventArgs e)
{ if (txtInput.Text == String.Empty) {
errorProvider1 = new ErrorProvider();
errorProvider1.SetError(txtInput, "Inserire il nome del cliente");
}
}
}
}
L’icona mostra un punto esclamativo, per default sulla destra del controllo interessato che
lampeggia un determinato numero di volte e che mostra un ToolTip posizionandovi sopra il
puntatore.
È possibile configurare diversi aspetti dell’ErrorProvider, per mezzo di proprietà e metodi.
Impostando la proprietà BlinkRate è possibile variare la velocità di lampeggio dell’icona di
errore, data in millisecondi e che per default è di 250 millisecondi.
Se invece, ad esempio, si vuole che l’icona lampeggi ad intervalli di un secondo, sarà
sufficiente impostare il valore di BlinkRate a1000.
La proprietà BlinkStyle permette di definire quando l’icona deve lampeggiare, usando uno
dei valori dell’enumerazione ErrorBlinkStyle.
Per default il blink è sempre attivo, quindi impostata a AlwaysBlink e quindi esso si verifica
ad ogni occorrenza dell’errore.
Se invece si desidera che l’icona rimanga fissa, si può usare il valore NeverBlink, o ancora
se il lampeggio deve verificarsi solo su errori diversi da eventuali errori precedenti sullo
stesso controllo, bisogna utilizzare il valore BlinkIfDifferentError.
È possibile configurare il posizionamento dell’icona stessa oppure un’icona personalizzata
al posto del punto esclamativo.
Il metodo SetIconAlignment imposta l’allineamento dell’icona rispetto al controllo
associato, utilizzando uno dei valori dell’enumerazione ErrorIconAlignment.
La distanza dal controllo è controllata dal metodo SetIconPadding, con cui è possibile
specificare il numero di pixel da utilizzare per il distanziamento.
Per utilizzare un’icona personalizzata basta impostare la proprietà Icon.
errorProvider1.Icon = new Icon("erroricon.ico");
Visual C#
um 79 di 202
GRAFICA
INTRODUZIONE
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1()
{ InitializeComponent(); }
private void Form1_Paint(object sender, PaintEventArgs e)
{ ShowLineJoin(e); }
private void ShowLineJoin(PaintEventArgs e) {
// create una nuova penna
Pen skyBluePen = new Pen(Brushes.DeepSkyBlue);
// setta la dimensione della penna
skyBluePen.Width = 8.0F;
// setta le proprietà LineJoin
skyBluePen.LineJoin = System.Drawing.Drawing2D.LineJoin.Bevel;
// disegna il rettangolo
e.Graphics.DrawRectangle(skyBluePen, new Rectangle(40, 40, 150, 200));
skyBluePen.Dispose();
}
}
}
Visual C#
um 80 di 202
Esempio, disegno di forme.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Visual {
public class Form1 : System.Windows.Forms.Form {
private System.Windows.Forms.Button button1;
private System.Windows.Forms.RadioButton radioButton1;
private System.Windows.Forms.RadioButton radioButton2;
private System.Windows.Forms.ComboBox comboBox1;
private System.ComponentModel.Container components = null;
private Color c = Color.Yellow;
public Form1()
{ InitializeComponent(); }
protected override void Dispose ( bool disposing )
{ if( disposing ) {
if (components != null)
components.Dispose();
}
base.Dispose( disposing );
}
protected override void OnPaint (PaintEventArgs e)
{
Graphics g = e.Graphics;
Brush brush = new SolidBrush(c);
if (radioButton1.Checked)
g.FillRectangle(brush,100,100,100,100);
else
g.FillEllipse(brush,100,100,100,100);
base.OnPaint( e );
}
[STAThread]
static void Main()
{ Application.Run(new Form1()); }
private void button1_Click(object sender,System.EventArgs e)
{ if (comboBox1.SelectedItem.ToString() == "Rosso" )
c = Color.Red;
else
if (comboBox1.SelectedItem.ToString() == "Verde")
c = Color.Green;
else
c = Color.Blue;
Invalidate();
}
}
Visual C#
um 81 di 202
}
Visual C#
um 82 di 202
MODULO 8
XAML
WPF
XAML
Applicazioni desktop
Applicazioni browser
Equazione di 1°
Grafica 2D
Grafica 3D
Guida in linea
Visual C#
um 83 di 202
WPF (WINDOWS PRESENTATION FOUNDATION)
INTRODUZIONE
Qualunque sia la complessità di un’applicazione, quello che l’utente vede è l’UI.
WPF è un sistema di presentazione per la compilazione di applicazioni in grado di fornire
le funzionalità destinate al PL (Presentation Layer).
WPF nasce per sostituire GDI+ che non ha grosse limitazioni ma è orientato al pixel.
GDI+ (Graphics Device Interface)
È un set di API (Application Programming Interface) per la grafica 2D, rappresenta la
versione avanzata della GDI inclusa nelle versioni precedenti di Windows.
Gli oggetti grafici sono renderizzati secondo il metodo raster; sono wrappate dal .NET
Framework per la gestione delle classi Windows Forms e sono utilizzate anche da ATL
(Active Template Library) e MFC (Microsoft Foundation Classes).
È possibile gestire l’antialiasing, le coordinate in virgola mobile, il gradiente di
ombreggiatura, i formati di file JPEG (Joint Photographic Experts Group) e PNG (Portable
Network Graphics), le sfumature e le funzionalità tipografiche.
Il namespace System.Drawing consente di accedere alle funzioni grafiche di base GDI, il
namespace System.Drawing.Graphics fornisce i metodi per il disegno di varie forme come
cerchi e archi.
Funzioni più avanzate sono fornite nei namespace System.Drawing.Drawing2D,
System.Drawing.Imaging e System.Drawing.Text.
Rendering
È il processo di generazione di un’immagine a partire da una descrizione degli oggetti 3D.
In particolare, è il procedimento con cui, una volta creati tutti gli elementi di una scena 3D,
si sviluppa la resa visiva da un particolare punto di vista.
Quest’attività impegna molto le capacità di calcolo e di elaborazione della GPU, a seconda
della complessità della scena e del metodo con cui sono calcolate le traiettorie delle
radiazioni luminose e i loro effetti.
WPF ha un motore di rendering vettoriale indipendente dalla risoluzione del dispositivo,
compilato per sfruttare i vantaggi dei moderni componenti H/W grafici.
A differenza dei controlli Windows Forms che usano Win32, le API native di Windows,
fonda il suo meccanismo di elaborazione sulle librerie grafiche Direct3D.
Inoltre, offre un set completo di funzionalità di sviluppo applicazioni che includono XAML,
controlli, associazione dati, layout, grafica 2D e 3D, animazione, trasformazioni, stili,
modelli, documenti, elementi multimediali, testo e tipografia
Direct3D
È un insieme di librerie grafiche progettate come piattaforma in collegamento con i driver
video della scheda grafica per sfruttare l’accelerazione H/W fornita dalla stessa senza che
siano penalizzate le performance generali del sistema.
MIL (Media Integration Layer)
Risiede nella libreria MILCORE.DLL che è scritta in codice nativo per consentire una più
stretta integrazione con le DirectX e di conseguenza con la scheda video.
Visual C#
um 84 di 202
L’architettura di WPF si basa sia su managed code sia su unmanaged code, per esempio
il motore di composizione che renderizza le applicazioni WPF è un componente nativo.
I valori utilizzati per le proprietà non sono specificati in pixel ma in una nuova unità di
misura chiamata DIP (Device Independent Pixels) che equivale ad 1/96 di pollice, per
permettere alle applicazioni di poter adattarsi alla risoluzione dello schermo su cui sono
visualizzate e quindi poter essere viste senza problemi su device molto diversi tra loro,
quali ad esempio tablet e smartphone.
Ogni DIP è ridimensionato automaticamente in modo da corrispondere all’impostazione in
DPI (Dots Per Inch) del sistema in cui è eseguito il rendering.
WPF è in grado di rendere possibile la creazione di qualsiasi tipo di applicazione.
 Applicazioni classiche.
 Applicazioni vettoriali 2D.
 Applicazioni vettoriali 3D, anche animate.
 Applicazioni con video, audio e immagini.
 Applicazioni con navigazione, stile web.
Il modello applicativo di WPF distingue fra due tipi di applicazioni.
1. Standalone: l’applicazione mostra il contenuto grafico nella finestra Windows, con stile
di navigazione tramite menu.
2. Browser: l’applicazione mostra il contenuto grafico nella finestra di un browser, con
stile di navigazione tramite collegamento ipertestuale.
PATTERN MVVM (MODEL VIEW VIEWMODEL)
Con WPF è stata crescente la necessità di definire delle linee guida robuste che
guidassero gli architetti e i programmatori nelle prime fasi di organizzazione del codice.
Lo scopo principale del ViewModel e di accedere ai dati e adattarli per il loro utilizzo nella
View, in pratica nei controlli dell’interfaccia.
Divide in tre componenti la parte di presentazione di un’applicazione.
1. Model: rappresenta le entità che fanno da repository delle informazioni.
2. View: indica il markup XAML del quale se ne occupa il grafico.
3. ViewModel: descrive l’intermediario tra il Model e il View che espone le funzionalità
adattandosi alle caratteristiche di astrazione di WPF.
Visual C#
um 85 di 202
XAML (EXTENSIBLE APPLICATION MARKUP LANGUAGE)
INTRODUZIONE
È un linguaggio dichiarativo, basato su XML (eXtensible Markup Language), per
implementare la struttura gerarchica dell’UI nelle applicazione Windows con WPF ma è
utilizzato anche da Silverlight e WinRT (Windows Runtime).
L’implementazione di una finestra include sia l’aspetto sia il comportamento.
L’aspetto definisce le caratteristiche visive della finestra: implementato tramite XAML.
Il comportamento definisce il funzionamento nel momento in cui gli utenti interagiscono
con essa: implementato tramite code behind.
Ogni pagina è contraddistinta dalla presenza di due file.
1. XAML: implementa l’aspetto e a compile-time è convertito in codice.
2. CS serve a tenere separata l’UI che implementa l’aspetto da quella di code behind che
implementa il comportamento.
Dietro ogni elemento XAML c’è sempre una classe .NET, quindi tutto ciò che può essere
fatto tramite XAML, può essere fatto via codice.
Questo approccio è simile a HTML (HyperText Markup Language), ogni nodo all’interno
del documento corrisponde ad una specifica funzionalità, in altre parole i TAG corrispondo
ai controlli, tramite gli attributi e gli elementi figli s’impostano i valori delle proprietà e si
definiscono i metodi di gestione degli eventi.
Infatti, XAML dichiara al suo interno una serie di elementi, ognuno dei quali rappresenta
un’istanza di un oggetto.
Dichiarare l’elemento <Grid> è equivalente da codice della creazione di una nuova istanza
del controllo Grid.
Il compito d’interpretare i TAG presenti nello XAML e d’istanziare gli oggetti relativi è a
carico del parser XAML.
Qualsiasi applicazione che supporti la grafica vettoriale potrebbe offrire un’esportazione in
XAML che è il formato attraverso il quale sono definiti gli oggetti all’interno di WPF, dato
che si tratta solamente di definire dentro un file di testo una serie di direttive che poi, una
volta eseguite con WPF, diventeranno oggetti vettoriali sullo schermo dell’utente.
Ad esempio, la suite di strumenti per i designer, all’interno di Expression, ha l’obiettivo di
rendere possibile il lavoro di un designer all’interno di un team di programmatori.
È un approccio diverso allo sviluppo d’UI, anziché definirle attraverso il designer di un tool,
lo si può fare con dei marcatori testuali.
Questo rende XAML praticamente interoperabile con qualsiasi tool di sviluppo, anche di
terze parti che convertono dai più diffusi formati vettoriali in XAML; ad esempio, c’è un tool
che consente di convertire clipart dal formato EMF (Windows Meta File), usato anche da
Office, in XAML.
D’altra parte XML, di cui XAML è solamente un “dialetto”, è una tecnologia facile: è un file
di testo ASCII (American Standard Code for Information Interchange).
Visual Studio consente di creare XAML non solo scrivendo direttamente il codice XML ma
passando attraverso il designer dedicato ad esso.
Creare un’UI utilizzando XAML ha diversi vantaggi rispetto alle tecnologie tradizionali.
 XAML è molto più facile da leggere ed espressivo rispetto al codice Visual C#.
 XAML consente di separare la business logic dell’applicazione dalla definizione dell’UI
e quindi consente un maggior riutilizzo del codice scritto oltre che una maggiore
robustezza dell’insieme.
 Semplifica il passaggio da una piattaforma ad un’altra.
Visual C#
um 86 di 202
MICROSOFT EXPRESSION BLEND/BLEND PER VISUAL STUDIO
È uno strumento della famiglia Expression specificamente pensato per lo sviluppo delle
interfacce basate su XAML.
È utilizzato da una figura che si chiama interactive designer che si occupa, nel team di
sviluppo, di progettare non solo l’UI ma anche di curare l’UX (User eXperience).
Visual Studio e Blend sono integrati e possono lavorare sulla stessa soluzione.
Il designer trova un ambiente che gli consente di concentrarsi espressamente sul lato
grafico dell’applicazione, generando però un output in formato XAML che poi i
programmatori potranno animare.
SINTASSI
I documenti devono essere well-formed, corretti dal punto di vista sintattico secondo le
regole XML e validi, in pratica devono rispettare il formato dello schema XAML che è case
sensitive.
Gli oggetti inseriti nello spazio sono agganciati ad una griglia che è il contenitore presente
nel template di default.
Ogni oggetto può contenere, in base alle proprie possibilità, altri oggetti funzionando da
contenitore.
La maggior parte degli attributi non è case sensitive, eccezione è Name che è utilizzato
per assegnare un identificatore ad un controllo.
XAML supporta diverse modalità d’inserimento degli oggetti nel layout.
Visual C#
um 87 di 202
Object element
È un’istanza di un oggetto, ogni elemento è formato da una (<), il nome della classe da
istanziare.
Se lo si desidera, è quindi possibile dichiarare attributi nell’elemento oggetto.
Per completare il TAG dell’elemento oggetto, terminare con una (>).
In alternativa, è possibile utilizzare una forma di chiusura automatica senza contenuto,
completando il TAG con una (/>).
Consente di evitare di definire le proprietà in maniera esplicita per limitare il codice da
scrivere.
Esempio, l’inserimento della stringa di testo “Ciao, mondo!” è equivalente a impostare la
proprietà Content della classe Button.
<Button>
Ciao, mondo!
</Button>
Property attribute
Permette d’impostare le proprietà di un oggetto utilizzando il nome della proprietà come
attributo di un elemento.
In pratica, s’istanzia un oggetto e contestualmente s’impostano le sue proprietà, per
esempio, impostare il colore di sfondo del pulsante.
Esempio, il nome dell’attributo rappresenta il nome della proprietà e la stringa dopo (=) ne
rappresenta il valore.
<Grid>
<TextBlock Text = "Cliccami" ></TextBlock>
<TextBlock Width = "100" >
Cliccami
</TextBlock>
</Grid>
Property collection
Sono collezioni, anche a sola lettura perchè non è inizializzata la collezione ma sono
aggiunti gli elementi specificati.
<TextBlock>
<TextBlock.RenderTransform>
<TransformGroup>
<RotateTransform Angle=" 1" CenterX=" 2" CenterY=" 3"/>
<TranslateTransform X ="10" Y = "15"/>
</TransformGroup>
</TextBlock.RenderTransform>
Visual C#
um 88 di 202
</TextBlock>
Property element
Ci sono proprietà che non possono essere rappresentate da una stringa.
Il valore è espresso con l’Object element e la proprietà da impostare ha la sintassi:
Type.PropertyName e prende forma come elemento interno all’oggetto che espone.
Esempio, creare un’istanza di una classe Button e impostare la proprietà Color usando
una nuova istanza di SolidColorBrush.
<Button>
<Button.Background>
<SolidColorBrush Color=" Blue"/>
</Button.Background>
</Button>
Esempio.
<TextBlock>
<TextBlock.Text>Cliccami</TextBlock.Text>
<TextBlock.Width>100</TextBlock.Width>
</TextBlock>
Il parser analizzando il valore della proprietà, è in grado di capire quale genere di oggetti
Visual C#
um 89 di 202
può ricevere e provvede a crearne uno in automatico, senza necessità di un ulteriore
intervento via codice.
Esempio, impostare la proprietà Background tramite la classe SolidColorBrush.
<Grid>
<Button Width=" 100" Height=" 100">
<Button.Background >
<SolidColorBrush Color=" Red" Opacity=" 50"/>
</Button.Background>
<TextBlock>Pulsante</TextBlock>
</Button>
</Grid>
L’attached property, disponibile nella forma Class.Property serve per impostare, nel
controllo corrente, il valore di una proprietà che in realtà si riferisce al proprio parent.
Per creare un’istanza della classe dichiarata nel markup è necessario che il tipo
disponga di un costruttore public senza parametri.
namespace
In un documento XAML, l’elemento root dichiara uno o più namespace comuni a ogni
piattaforma.
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
È il namespace di default e non necessita di prefissi, questa dichiarazione mappa una
grande quantità di namespace delle librerie.
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
È un namespace a supporto delle caratteristiche di XAML; ha il prefisso x e serve,
mediante l’attributo x:Class, a legare il file .XAML al relativo file di code behind.
I namespace, indicano al parser in quale assembly cercare i tipi da utilizzare per creare le
istanze degli elementi definiti nel documento XAML.
Poichè un namespace XAML può mappare più assembly e più namespace del CLR
(Common Language Runtime), è necessario che non vi sia sovrapposizione dei nomi tra le
classi, altrimenti il parser non è in grado di distinguere quale tipo istanziare.
Esempio, definire un nuovo namespace.
using System.Windows;
namespace esempio {
public class Prova {
Visual C#
um 90 di 202
public override string ToString()
{ return "Ciao, mondo!"; }
}
}
namespace WpfApplication1 {
public partial class MainWindow : Window {
public MainWindow()
{ InitializeComponent(); }
}
}
Per usare la classe Prova in un documento XAML, basta aggiungere il namespace del
CLR al namespace XAML.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:esempio"
Title="MainWindow" Height="350" Width="525">
<Grid>
<UserControl>
<local:Prova></local:Prova>
</UserControl>
</Grid>
</Window>
La classe è legata direttamente al markup attraverso l’uso dell’attributo x:Class e
dev’essere obbligatoriamente publica.
Se è previsto un namespace all’interno della classe, questo dev’essere riportato nel valore
di x:Class e viceversa.
La classe base da cui deriva la pagina è Windows.UI.Xaml.Controls.Page che rappresenta
un generico contenitore.
LAYOUT
Quando si crea un’UI, si dispongono i controlli per percorso e dimensione per creare un
layout.
Il requisito principale di qualsiasi layout è quello di adattarsi alle modifiche nella
dimensione delle finestre e nelle impostazioni di visualizzazione.
Anziché richiedere la scrittura di codice per adattare un layout in tali circostanze, WPF
offre un efficiente sistema di layout estensibile.
Tale sistema si fonda sul posizionamento relativo che aumenta la capacità di adattamento
a condizioni di finestre e visualizzazione mutevoli.
Con il sistema di layout, inoltre, è gestita la negoziazione tra i controlli: è un processo a
due passaggi, in cui un controllo indica il percorso e le dimensioni richieste al relativo
padre, quindi il padre indica lo spazio disponibile al controllo.
Il sistema di layout è esposto ai controlli figlio tramite le classi base di WPF.
Sono d’importanza fondamentale nel definire la struttura di una pagina perché consentono
di determinare l’ordine e il posizionamento secondo cui gli oggetti appariranno nell’UI.
Visual C#
um 91 di 202
Diversamente da Windows Forms e dagli altri ambienti basati su GDI, in cui ogni controllo
propone le proprietà Top e Left per essere posizionato in modo assoluto all’interno del
form, in WPF ogni controllo non ha nessuna proprietà di posizionamento, quindi non sa
“sistemarsi autonomamente” sul form.
La posizione dei controlli è decisa dal contenitore che li ospita in base alle caratteristiche
del contenitore stesso e alle proprietà assegnate al controllo.
Non producono direttamente nessun output visuale, hanno solo il compito di disporre,
all’interno dello spazio disponibile, gli oggetti che contengono.
Ogni elemento dell’interfaccia occupa uno spazio che è chiamato bounding box che è
definito dal layout system: è un processo ricorsivo che misura, arrangia, dispone e si
occupa della renderizzazione.
È possibile recuperare tutte le informazioni sullo slot occupato da ogni elemento, usando
al classe LayoutInformation.
Quando in un documento XAML, attraverso il markup, si definiscono gli elementi dell’UI, si
delinea una gerarchia ad albero chiamata Logical Tree.
Ogni elemento logico può essere formato da uno o più elementi grafici.
Per esempio, il controllo Button può essere costituito da più elementi visuali, questa
gerarchia si chiama Visual Tree.
I controlli espongono proprietà che permettono di navigare e popolare il Logical Tree.
Per esempio, è possibile accedere al contenuto degli oggetti che ereditano dalla classe
ContentControl, attraverso la proprietà Content.
Diversamente, nessun controllo espone il proprio Visual Tree che rimane “occultato”.
È possibile navigare il Visual Tree con la classe VisualTreeHelper che espone una serie di
metodi utili per recuperare ogni informazione sull’aspetto visuale di un elemento.
Pannelli
La disposizione degli elementi è affidata ad un tipo particolare di oggetti.
Queste classi che estendono il tipo Panel sono predisposte per arrangiare gli elementi
contenuti in base ad alcune regole dipendenti dal tipo utilizzato.
La classe Panel è una abstract class che fornisce l’infrastruttura per la realizzazione di
classi specializzate per il posizionamento degli elementi dell’UI.
StackPanel
Gli elementi sono disposti l’uno di seguito all’altro, verticalmente o orizzontalmente in base
alla proprietà Orientation, modificando completamente il layout senza “toccare” le proprietà
degli elementi figli.
Esempio.
<StackPanel >
<Button>1</Button>
<Button>2</Button>
<Button>3</Button>
</StackPanel>
Visual C#
um 92 di 202
Esempio.
<StackPanel Height="100" Orientation="Horizontal" >
<Button Content="Pulsante con MinHeight" MinHeight="100" />
<TextBox Text="Casella di testo" HorizontalAlignment="Right" MaxHeight="100"
Width="216" />
<CheckBox Content="Checkbox" Width="98" />
<ListBox BorderBrush="White" BorderThickness="2" MaxWidth="350" Width="73">
<ListBoxItem Content="Voce 1" />
<ListBoxItem Content="Voce 2" />
<ListBoxItem Content="Voce 3" />
</ListBox>
<Image Source="figura.png" Margin="10,0,10,10" VerticalAlignment="Bottom" />
</StackPanel>
Nel caso lo spazio disponibile non sia sufficiente per contenere gli oggetti, la parte
eccedente non è visualizzata.
Lo StackPanel non possiede la funzionalità di scrolling ma si può integrare con
ScrollViewer, attivando però la scrollbar orizzontale che per default è nascosta.
La modalità di scrolling predefinita e unica ammessa si chiama Rails e consente di fare lo
scrolling in una sola direzione per volta, a meno di non modificare questa impostazione
con le proprietà HorizontalScrollMode e VerticalScrollMode.
<ScrollViewer HorizontalScrollBarVisibility="Visible"
HorizontalAlignment="Center" VerticalAlignment="Center" Width="600">
<StackPanel Height="200" Orientation="Horizontal" >
<Button Content="Pulsante con MinHeight" MinHeight="100" />
<TextBox Text="Casella di testo" HorizontalAlignment="Right" MaxHeight="100"
Width="216" />
<CheckBox Content="Checkbox" Width="98" />
<ListBox BorderBrush="White" BorderThickness="2" MaxWidth="350" Width="73">
<ListBoxItem Content="Voce 1" />
<ListBoxItem Content="Voce 2" />
<ListBoxItem Content="Voce 3" />
</ListBox>
<Image Source="figura.png" Margin="10,0,10,10" VerticalAlignment="Bottom" />
</StackPanel>
</ScrollViewer>
Visual C#
um 93 di 202
Canvas
La traduzione letterale di Canvas è “tela” e permette di disegnare gli oggetti dell’interfaccia
in un sistema di coordinate cartesiane, è comodo nei casi in cui la pagina deve contenere
elementi grafici.
Gli elementi figlio sono disposti, mediante coordinate assolute, relativamente ad una
coppia di margini, Left, Top, Right e Bottom e spetta al programmatore riposizionare gli
oggetti a seguito di un cambio di orientamento del dispositivo.
Bisogna indicare la disposizione degli oggetti all’interno del pannello stesso.
Quando due o più oggetti si sovrappongono per default sono visualizzati nel Canvas
nell’ordine in cui sono inseriti nello XAML.
Per modificare quest’ordine si us ala proprietà ZIndex.
Questo perchè il Canvas, in modo differente di quanto fa lo StackPanel, alloca agli
elementi figli solo lo spazio di cui hanno effettivamente bisogno e non tutto lo spazio
disponibile.
È indicato per il posizionamento assoluto ma non riesce a modificare le proprie dimensioni
in funzione dello spazio messo a disposizione dal proprio contenitore.
Ad esempio, per disporlo orizzontalmente al centro si deve assegnare una larghezza e
specificare il valore Center alla proprietà HorizontalAlignment, successivamente si deve
includere il Canvas principale all’interno di un controllo Grid senza dimensioni.
Esempio, data la finestra seguente: Height="480" Width="640", posizionare un rettangolo.
<Canvas x:Name="ContentPanel">
<Ellipse Fill="Black" Width="100" Height="100"
Canvas.Left="250" Canvas.Top="-30" />
<Rectangle Fill="Red" Width="120" Height="120"
Canvas.Left="240" Canvas.Top="-40"
Canvas.ZIndex="-1" />
</Canvas>
</Window>
Visual C#
um 94 di 202
Esempio, notare oltre alla differente posizione degli elementi, le diverse dimensioni.
<Canvas>
<Button>1</Button>
<Button Canvas.Top ="90" Canvas.Left = "100">2</Button>
<Button Canvas.Top ="130" Canvas.Left ="100">3</Button>
</Canvas>
Grid
Consente di disporre un numero arbitrario di controlli figli in una struttura tabellare,
organizzandoli per righe e colonne.
A differenza delle tabelle HTML la definizione della struttura della tabella è separata
rispetto agli elementi che essa contiene, per rendere più semplice un eventuale
cambiamento del contenuto, nelle Windows Forms, così come anche in HTML, si è soliti
posizionare un elemento in base a coordinate X e Y.
Questo approccio è però corretto se l’elemento si trova in un Canvas, specificando le
proprietà Canvas.Left e Canvas.Top ma in un Grid le informazioni che servono devono
specificare in quale colonna e riga deve posizionarsi l’elemento, nell’esempio si usano le
proprietà Grid.Column e Grid.Row.
Questo sistema è disponibile tramite un meccanismo di dipendenze: ogni elemento eredita
da una classe DependencyObject che gestisce uno o più DependencyProperty.
Queste proprietà fanno sì che sebbene sia specificato l’attributo, nell’esempio
Grid.Column, sul Button, l’informazione sia comunque salvata sul Grid stesso.
In pratica, l’inserimento di Grid.Column è tradotto dal parser in una chiamata al metodo
statico Grid.SetColumn(elemento, valore) e ciò è fortemente legato alla griglia che poi in
realtà contiene il controllo.
Esempio, disegnare tre pulsanti.
<Grid VerticalAlignment="Top" HorizontalAlignment="Left" ShowGridLines="True"
Width="300" Height="100">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button Grid.Row="0" Grid.Column="0">1</Button>
<Button Grid.Row="1" Grid.Column="1">2</Button>
Visual C#
um 95 di 202
<Button Grid.Row="2" Grid.Column="0">3</Button>
</Grid>
Esempio, il codice seguente indica che la prima colonna è larga circa la metà dell’altra,
pari a un terzo dell’intera larghezza del controllo.
<Grid ShowGridLines="True" x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.421*" />
<ColumnDefinition Width="0.579*" />
</Grid.ColumnDefinitions>
</Grid>
Esempio, disegnare quattro pulsanti, ognuno posizionato in una cella differente di una
tabella composta da due righe e due colonne.
<Grid VerticalAlignment="Top" HorizontalAlignment="Left" ShowGridLines="True"
Width="150" Height="100">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button Grid.Column="0" Grid.Row="0" Content="Pulsante 1" />
<Button Grid.Column="1" Grid.Row="0" Content="Pulsante 2" />
<Button Grid.Column="0" Grid.Row="1" Content="Pulsante 3" />
<Button Grid.Column="1" Grid.Row="1" Content="Pulsante 4" />
</Grid>
Visual C#
um 96 di 202
Esempio, progettare una tabella con due righe e due colonne, all’interno della quale sono
inseriti tre pulsanti: le celle devono essere delle stesse dimensioni.
Ogni pulsante è assegnato alla rispettiva cella utilizzando Grid.Row e Grid.Column,
mentre con Grid.ColumnSpan e Grid.RowSpan è possibile specificare se un elemento
debba estendersi per più righe o colonne.
<Grid Width="400" Height="200" HorizontalAlignment="Center"
VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Content="Pulsante alto-sinistra" HorizontalAlignment="Left"
VerticalAlignment="Top" />
<Button Grid.Column="1" Content="Pulsante alto-destra"
HorizontalAlignment="Right" VerticalAlignment="Top" />
<Button Grid.Row="1" Grid.ColumnSpan="2" HorizontalAlignment="Center"
Content="Pulsante con testo molto lungo" />
</Grid>
Esempio, progettare una tabella con tre righe e due colonne indicandone altezza e
larghezza.
<Grid Width="600" Height="200" HorizontalAlignment="Center"
VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
Visual C#
um 97 di 202
<Button Grid.Row="0" Grid.Column="0" Content="Premi!" />
<TextBox Grid.Row="1" Grid.Column="1" Text="Testo..." />
<Ellipse Grid.Row="2" Grid.Column="1" Stroke="Red" StrokeThickness="10" />
</Grid>
In questo esempio si è usato Height e Width, rispettivamente appartenenti a RowDefinition
e ColumnDefinitions per modificare le dimensioni di righe e colonne.
 Terza riga: altezza fissa di 100 px.
 Quarta riga: Auto, si ridimensionerà automaticamente a seconda delle dimensioni del
suo contenuto.
 Quinta riga: non ha impostazione, quindi occuperà tutto lo spazio disponibile.
 Ottava riga: si può pensare ad * come ad una dimensione che pur non essendo nota a
priori, consente comunque d’impostare le proporzioni tra le altezze o larghezze di righe
e colonne.
 Nona riga: li valore 3* indica che la larghezza della seconda colonna è il triplo della
prima, in pratica equivale a suddividere la larghezza disponibile in quattro parti uguali,
delle quali una sarà occupata dalla prima colonna e tre dalla seconda.
È meglio utilizzare le dimensioni proporzionali, ogni pagina ha la possibilità di funzionare in
diverse modalità di visualizzazione e con diversi DPI, perchè il layout è in grado di
adattarsi autonomamente.
VirtualizingStackPanel
La disposizione degli elementi è uguale allo StackPanel ma è ottimizzata per un numero
elevato di elementi, calcolando solamente quelli effettivamente visibili.
Esempio.
<StackPanel>
<StackPanel.Resources>
</StackPanel.Resources>
<Button VirtualizingStackPanel.VirtualizationMode="Recycling">1</Button>
<Button VirtualizingStackPanel.IsVirtualizing="True" >2</Button>
</StackPanel>
Visual C#
um 98 di 202
DockPanel
Definisce un’area dov’è possibile disporre gli elementi figlio orizzontalmente o
verticalmente, relativamente l’uno all’altro, gli elementi sono allineati ai bordi del riquadro.
La proprietà DockPanel.Dock indica l’àncoraggio dell’elemento rispetto agli altri, dato che
questi ultimi hanno la caratteristica di legarsi su uno dei lati al proprio contenitore.
Esempio.
<DockPanel LastChildFill="True">
<Border Height="25" Background="SkyBlue" BorderBrush="Black"
BorderThickness="1" DockPanel.Dock="Top">
<TextBlock Foreground="Black">Dock = "Top"</TextBlock>
</Border>
<Border Height="25" Background="SkyBlue" BorderBrush="Black"
BorderThickness="1" DockPanel.Dock="Top">
<TextBlock Foreground="Black">Dock = "Top"</TextBlock>
</Border>
<Border Height="25" Background="LemonChiffon" BorderBrush="Black"
BorderThickness="1" DockPanel.Dock="Bottom">
<TextBlock Foreground="Black">Dock = "Bottom"</TextBlock>
</Border>
<Border Width="200" Background="PaleGreen" BorderBrush="Black"
BorderThickness="1" DockPanel.Dock="Left">
<TextBlock Foreground="Black">Dock = "Left"</TextBlock>
</Border>
<Border Background="White" BorderBrush="Black" BorderThickness="1">
<TextBlock Foreground="Black">Ciao, mondo!</TextBlock>
</Border>
</DockPanel>
WrapPanel
Posiziona gli elementi figlio in sequenza da sinistra verso destra, interrompendo il
contenuto quando è raggiunto il bordo della casella contenitore e facendolo ripartire dalla
riga successiva.
Visual C#
um 99 di 202
Esempio.
<Border HorizontalAlignment="Left" VerticalAlignment="Top" BorderBrush="Black"
BorderThickness="2">
<WrapPanel Background="LightBlue" Width="200" Height="100">
<Button Width="200">Pulsante 1</Button>
<Button>Pulsante premuto 2</Button>
<Button>Pulsante premuto 3</Button>
<Button>Pulsante 4</Button>
</WrapPanel>
</Border>
CONTROLLI
I pannelli da soli non possono essere usati per creare un’UI, serve qualcosa con cui
l’utente possa interagire con i dati dell’applicazione: i controlli.
I controlli sono lookless, in altre parole privi di look, permettono di cambiare
completamente aspetto, ridefinendo il proprio Visual Tree con l’impostazione della
proprietà Template.
Ogni controllo eredita indirettamente dal tipo FrameworkElement che fornisce i servizi di
layout, il supporto al DataBinding, agli Style e ai Template ed estende il supporto alle
animazioni, già offerto dalla classe UIElement.
La classe UIElement fornisce un supporto base alla gestione dell’input dell’utente, alle
animazioni ed espone i metodi che le classi derivate possono sovrascrivere per
partecipare al sistema di layout.
Si distinguono in due categorie.
ContentControl
Tutti controlli derivati dalla classe ContentControl espongono la proprietà Content del tipo
Object.
Ciò permette, dal semplice Button allo UserControl, di ospitare qualsiasi tipo di contenuto,
dalla stringa di testo fino ad un Logical Tree.
Esempio, visualizzare un’immagine all’interno di un pulsante.
<Button>
<Image Height=" 50" Source=" freccia.gif" Width="101"/>
</Button>
Il contenuto non è limitato ad un solo elemento ma, utilizzando un Panel, è possibile
Visual C#
um 100 di 202
arrangiare più elementi all’interno di un pulsante, come succede per uno UserControl.
ItemsControl
Anche se il ContentControl può contenere, non direttamente, più di un elemento, va
considerato che ha come contenuto sempre e solo un figlio.
Per superare questo limite si usa ItemControl che espone il proprio contenuto con la
proprietà Items ed è la classe base per una serie di controlli, come ListBox.
La capacità di selezionare è fornita da Selector, uno dei suoi tipi derivati.
Sono elementi che interagiscono con l’utente, come pulsanti, TextBox, menu e ListBox.
Si differenziano dagli altri per il fatto che dispongono di uno o più Content, per la capacità
di ricevere input o di essere selettori di elementi.
Importante quando si sviluppa per WPF è il meccanismo d’input, gli eventi sono
indipendenti dal fatto che l’inserimento provenga da tastiera o da mouse, cambiando
quindi l’approccio al consumo degli eventi e aprendo la strada a nuove forme d’input.
In altre parole, in una finestra WPF non esiste il concetto di messaggio, è sostituito da un
sistema di routing dell’evento.
Al contrario di ciò che avviene con le Windows Forms, un evento non raggiunge subito il
controllo destinatario ma parte dall’elemento padre principale, scorrendo tutto l’albero dei
figli fino ad arrivare al controllo finale.
Questo processo di tunneling permette di dotare i controlli di pre eventi, come ad
esempio PreviewMouseDown, per intervenire fermando l’evento qualora ci sia la necessità
durante il suo percorso di notifica.
Una volta giunto a destinazione, l’evento subisce il percorso contrario, di bubbling,
ritornando all’elemento padre e scatenando questa volta gli eventi, come MouseDown.
Esempio.
<ListBox >
<Image Height=" 80" Source=" freccia.gif" Stretch="None"/>
<Image Height=" 80" Source=" domanda.gif" Stretch="Fill"/>
<Image Height=" 80" Source=" mezzi.gif" Stretch="Fill"/>
</ListBox>
Libreria di controlli utente WPF
User control: si ha un progetto che permette di creare un unico controllo, utilizzando anche
dei controlli già esistenti; per esempio, un controllo che unisce altri controlli per
l’inserimento e la validazione della coppia utente/password.
Visual C#
um 101 di 202
Libreria di controlli personalizzati WPF
Custom control: si crea nuovo controllo, dal suo aspetto grafico al suo comportamento;
possono essere inseriti nella Casella degli strumenti, per un utilizzo nelle applicazioni.
FONT
È incluso un set di caratteri standard: per esempio Arial, Courier New e Verdana ma è
possibile utilizzare anche stili personali compilando i file dei font nell’assembly.
Basta copiare i file in una cartella del progetto e impostarne gli attributi di compilazione.
Definire la proprietà FontFamily con un valore corrispondente alla cartella.
<Run FontFamily="./Fonts/calligra.ttf#calligra"/>
Visual C#
um 102 di 202
RISORSE
Bisogna organizzare i contenuti di un’applicazione in modo da favorirne la manutenzione e
il riutilizzo, è possibile definire due tipi di risorse.
1. Statiche.
2. Dinamiche.
Entrambe esposte dalla proprietà Resources che ha ogni elemento e si differenziano dal
fatto che possono mutare o meno in fase di run-time.
Ogni elemento visuale ha a disposizione una proprietà Resources che permette di
mantenere un dizionario (hashtable) di chiavi (x:Key="chiave") con i loro relativi valori, per
poi farne riferimento nel contesto in cui sono state definite, sono immagazzinati all’interno
di ResourceDictionary esposti da tutti i FrameworkElement.
Per contesto s’intende l’elemento all’interno del quale la risorsa è stata definita e tutto il
ramo di figli che gli appartengono, in pratica possono utilizzarla grazie alle markup
extension, per esempio StaticResource.
Markup extension
Permettono di ampliare il processo d’inizializzazione degli elementi attraverso processi
personalizzati, sono usate per fornire funzionalità quali le risorse, il data binding e
l’assegnazione dei valori.
È una sintassi concisa che esprime costrutti che altrimenti richiederebbero codice, usano
come sintassi le ({ }) per la valorizzazione degli attributi.
Esempio, definire un pennello da usare come sfondo in più parti dell’applicazione.
Dato un Button impostare la proprietà Background con la risorsa RedBrush.
XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Risorse" Height="480" Width="640" WindowStartupLocation="CenterScreen"
Loaded="Window_Loaded">
<Window.Resources>
<SolidColorBrush x:Key="RedBrush">Red</SolidColorBrush>
</Window.Resources>
<StackPanel>
<Button Background="{StaticResource RedBrush}" Content="Risorse Statiche"/>
</StackPanel>
</Window>
Visual C#
private void Window_Loaded(object sender, RoutedEventArgs e)
{ this.Resources["RedBrush"] = new SolidColorBrush(Colors.Yellow); }
Esempio, definire una risorsa di tipo Double con valore 100 e assegnarle la chiave
ButtonWidth e renderla disponibile per tutti gli elementi figli dello XAML corrente.
<Page.Resources>
<System:Double x:Key=" ButtonWidth">100</System:Double>
</Page.Resources>
Visual C#
um 103 di 202
<Button Width="{StaticResource ButtonWidth}" Content="Premi"/>
Per risolvere l’esempio, si deve ricorrere alla markup extension di nome StaticResource,
grazie a questa sintassi si può valorizzare qualsiasi proprietà, recuperandone il valore da
una risorsa.
Questo evita di duplicare codice per definire comportamenti comuni in più parti dell’app.
Nell’esempio si è definita la risorsa nel dizionario della pagina ma è possibile anche
dichiararla a livello applicazione, definendola all’interno del dizionario Resources
dell’oggetto Application che si trova nel file APP.XAML.
Questa lista è l’ultimo punto nel quale il motore di XAML va a cercare una risorsa: non
individuata localmente, la ricerca all’interno della struttura ad albero degli elementi, fino al
dizionario globale.
In alternativa, è possibile recuperare il valore di una risorsa già definita e accessibile
all’elemento, grazie al menu contestuale di ogni proprietà e creando il markup con la
StaticResource in automatico.
Definito un file di risorse, si può utilizzare ogni volta che bisogna fare riferimento ai suoi
elementi, grazie all’oggetto ResourceDictionary che tramite la proprietà Source specifica
l’URI del file da caricare.
Esempio, includere nello UserControl le risorse di un file esterno.
<UserControl.Resources >
<ResourceDictionary Source=" risorsa.xaml"/>
</UserControl.Resources>
Visual Studio, per ogni risorsa creata, chiede se bisogna definirla in uno degli elementi
padre rispetto a quello che ne ha bisogno, a livello finestra o a livello applicazione.
É possibile definire risorse a livello globale con il file APP.XAML.
Non confondere le risorse XAML (contengono le dichiarazioni degli oggetti da istanziare a
run-time) con quelle degli assembly .NET con estensione RESX che sono file binari inclusi
nelle DLL.
È possibile cambiare le risorse di un oggetto da codice, a run-time ma le modifiche non si
rispecchieranno sugli oggetti caricati ma solo su quelli che saranno istanziati
successivamente.
Attraverso le risorse è possibile specificare una serie di proprietà e racchiuderle sotto
forma di uno stile, oppure personalizzare la struttura di un controllo, definendo un
template.
STYLE
Racchiude in un unico nome un insieme di proprietà e comportamenti da associare poi ad
un elemento, evitano la duplicazione del codice necessario a specificare impostazioni
comuni, con il vantaggio che, nel caso di modifiche, le stesse si propagheranno a tutti i
controlli cui si riferiscono.
Sono paragonabili agli stili CSS (Cascaded Style Sheet) delle pagine web.
Per ogni stile dev’essere specificata la tipologia di elementi cui è dedicato: l’oggetto.
Se a Style si aggiunge un valore per l’attributo Key, indica che uno o più controlli andranno
ad usare questo particolare stile.
Style possiede una collezione di Setter per ridefinire le proprietà dell’oggetto con la coppia
Property e Value e non si limita solo a cambiarne l’aspetto in modo statico ma anche al
variarsi di una proprietà.
Tramite il meccanismo di Trigger, si può agganciarsi ad una proprietà e utilizzare di nuovo
i Setter per cambiarne l’aspetto grafico.
Ciò che rende lo stile di WPF molto potente è la capacità di ridefinire qualsiasi proprietà
Visual C#
um 104 di 202
che l’elemento possiede e con un modello per intercettare gli eventi, basato su Trigger.
A tutto questo si aggiunge la facoltà di sfruttare le risorse dinamiche, per variare tutti gli
stili a run-time, è possibile modificare il layout di un controllo, oltre che con i Trigger in
XAML, anche tramite codice Visual C#.
Mentre i trigger rendono le modifiche temporanee, in funzione dello scatenarsi degli eventi,
con il code behind è possibile renderle persistenti.
Definito lo stile, si deve usare come una risorsa impostandolo con la proprietà Style
disponibile per ogni elemento visuale.
Specificando un valore per TargetType, si dice al motore di applicare lo stile ad una
particolare tipologia di elemento, gli stili non sono vincolati ad uno specifico oggetto.
Infatti, è possibile assegnarlo ad un generico UIElement, classe base di tutti gli elementi,
oppure ad un ButtonBase, classe base di CheckBox, Button o RadioButton e ottenere così
uno stile applicabile a più oggetti.
Nelle dichiarazione dello Style non è obbligatorio dichiarare la chiave della risorsa, se
omessa assume il nome stesso della tipologia: stile implicito.
Gli stili possono ereditare da un altro stile, impostando la proprietà BasedOn, questo
permette di organizzarli con impostazioni base che poi si specializzeranno man mano a
seconda del controllo.
Esempio, dato un pulsante con lo sfondo rosso e il colore del testo bianco, impostare
queste proprietà definendo uno stile tra le risorse di nome RedButton, inoltre applicare uno
stile base a tutti i controlli che dispongono della proprietà Margin.
<Window.Resources>
<Style x:Key="BaseStyle"
TargetType="Control">
<Setter Property="Margin"
Value="4" />
</Style>
<Style x:Key="RedButton"
BasedOn="{StaticResource BaseStyle}"
TargetType="Button">
<Setter Property="Background"
Value="Red" />
<Setter Property="Foreground"
Value="White" />
<Style.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="Cursor"
Value="Hand" />
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="RedStyle">
<Setter Property="Button.ClickMode"
Value="Release" />
<Setter Property="Border.BorderBrush"
Value="Blue" />
</Style>
</Window.Resources>
<StackPanel>
<Button Style="{StaticResource RedButton}" Content="Pulsante 1"/>
<Button Style="{StaticResource RedButton}" Content="Pulsante 2"/>
Visual C#
um 105 di 202
<Button Style="{StaticResource RedStyle}" Content="Pulsante 3"/>
<Border Style="{StaticResource RedStyle}" BorderThickness="2"
Height="20"></Border>
</StackPanel>
Esempio, definire uno stile che poi si utilizzerà in un pulsante e al passaggio del mouse
modificare i colori del pulsante con la proprietà booleana IsMouseOver.
<Window.Resources>
<Style x:Key="Style1">
<Setter Property="Button.Background" Value="Blue" />
<Setter Property="Button.Foreground" Value="White" />
<Style.Triggers>
<Trigger Property="Button.IsMouseOver" Value="true">
<Setter Property="Button.Background" Value="Red" />
<Setter Property="Button.Foreground" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Viewbox>
<Canvas Height="480" Width="640">
<Button Canvas.Top="20" FontSize="20" Style="{StaticResource
Style1}">Cliccami</Button>
<Button Canvas.Top="70">
<DockPanel Height="30">
<Image>
<Image.Source>
<ImageSource>max.ico</ImageSource>
</Image.Source>
</Image>
<TextBlock FontSize="16">Pulsante con icona</TextBlock>
</DockPanel>
</Button>
</Canvas>
</Viewbox>
Visual C#
um 106 di 202
TEMPLATE
Il Button ha un evento Click che esegue un comando.
La ListBox contiene una lista di elementi e ritorna l’elemento selezionato.
La CheckBox ha uno stato: selezionato, non selezionato.
Nell’implementazione di questi controlli non c’è alcun riferimento a come devono apparire,
né informazioni su come cambiare in funzione dello stato.
Queste caratteristiche sono affidate agli Style e ai Template che determinano l’aspetto di
ogni controllo e, allo stesso tempo, permettono di ridefinirli in base alle necessità.
Permettono di ridefinire il layout di ogni classe che eredita da Control, tramite la
ridefinizione della proprietà Template e del corrispondente ControlTemplate.
Serve per definire un modello o qualsiasi elemento che si vuole riutilizzare e farne uso
dichiarando un oggetto Control nel punto in cui si vuole usare.
Per trasformare i controlli base si deve gestire la proprietà Template e ControlTemplate,
mentre per modificare lo stile si deve ridefinire le proprietà HeaderTemplate e
DataTemplate che definiscono la modalità di visualizzazione dei dati.
Un oggetto DataTemplate è utilizzato per decidere la rappresentazione grafica dei
contenuti dei controlli che, di solito, gestiscono insiemi di dati.
L’aspetto è determinato da due architetture.
1. Tipo di applicazione: WPF, Windows Store, Windows Phone.
2. Tema del SO: Windows Classic, XP, Vista, 7.
I template sono la definizione di come un controllo dev’essere rappresentato in maniera
visuale, permettono di ridefinire il layout di ogni classe che eredita da Control.
Rappresentano un modello, basato a sua volta su elementi primitivi, come rettangoli, bordi
e pannelli e si appoggiano sugli stati per fornire l’interazione con l’UI.
Tipologie di Template.
1. ControlTemplate: usato per modellare i controlli e il loro aspetto.
2. DataTemplate: usato per visualizzare i dati all’interno di liste o ContentControl.
3. ItemsPanelTemplate: usato per modellare il pannello contenitori di controlli che
mostrano liste.
Esempio, creare un pulsante composto da testo e immagine.
Ci sono tre elementi da usare nella definizione del template.
1. ContentPresenter: è il segnaposto per la proprietà Content del controllo.
2. ItemsPresenter: è il segnaposto per la proprietà Items degli ItemsControl.
3. ScrollContentPresenter: segnaposto usato dal controllo ScrollViewer, per inserire il
contenuto da scrollare.
<ControlTemplate x:Key="ButtonImage" TargetType="Button">
<Grid>
<Image Height=" 30" Width=" 30"
HorizontalAlignment="Left" VerticalAlignment="Top" Source=" max.png"/>
<ContentPresenter Margin="36,0,0,0 "/>
</Grid>
</ControlTemplate>
Esempio, posizionare un pulsante sulla finestra.
Si genera automaticamente il seguente markup.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Property element" Height="480" Width="640"
WindowStartupLocation="CenterScreen" Icon="max.ico">
Visual C#
um 107 di 202
<Button Content="Button" HorizontalAlignment="Left" Margin="130,10,0,0"
VerticalAlignment="Top" Width="312" Height="193" Click="Button_Click"/>
</Window>
Il pulsante è un controllo che reagisce al clic del mouse sollevando un evento, il suo
Template lo descrive come un rettangolo con uno sfondo e un contenuto.
Per cambiare l’aspetto di un controllo, bisogna creare un ControlTemplate personalizzato.
Selezionare l’oggetto nel designer, fare clic con il pulsante destro del mouse e dal menu
contestuale utilizzare la voce Modifica modello/Crea vuoto…
È possibile fare una copia del Template originale, oppure crearne uno vuoto.
In entrambi i casi si è posizionati automaticamente all’interno del ControlTemplate, si ha a
disposizione una struttura per inserire qualsiasi elemento necessario alla
personalizzazione dell’aspetto grafico.
Si genera automaticamente il seguente markup.
<Window.Resources>
<ControlTemplate x:Key="ButtonControlTemplate1" TargetType="{x:Type Button}">
<Grid/>
</ControlTemplate>
</Window.Resources>
<Button Content="Button" HorizontalAlignment="Left" Margin="130,10,0,0"
VerticalAlignment="Top" Width="312" Height="193" Click="Button_Click"
Template="{DynamicResource ButtonControlTemplate1}"/>
Esempio, personalizzare una CheckBox con un ControlTemplate definito nelle risorse.
Mediante una Grid, posizionare un rettangolo a fianco del contenuto, in modo che questo
riempia tutto lo spazio disponibile.
Indicare, inoltre, il TargetType del Template, in modo da specializzarlo e le propreità cui si
fa riferimento.
<Window.Resources>
Visual C#
um 108 di 202
<ControlTemplate x:Key="PointCheckBox"
TargetType="CheckBox">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Rectangle Stroke="DarkRed"
x:Name="square"
Width="10"
Height="10"
Margin="2"
VerticalAlignment="Center"
DockPanel.Dock="Left" />
<ContentPresenter VerticalAlignment="Center"
Grid.Column="1" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CheckStates">
<VisualState Name="Checked">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="square"
Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<SolidColorBrush Color="Red" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="Unchecked">
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Window.Resources>
<StackPanel>
<CheckBox IsChecked="True"
Content="IsChecked a true" />
<CheckBox Template="{StaticResource PointCheckBox}"
IsChecked="True"
Content="IsChecked a true" />
<CheckBox Template="{StaticResource PointCheckBox}"
Content="IsChecked a false" />
</StackPanel>
Il ContentPresenter è un segnaposto, indica che in quel punto va posto il contenuto della
Visual C#
um 109 di 202
CheckBox.
La proprietà Content può contenere qualsiasi testo.
La proprietà IsChecked indica se l’utente ha premuto il pulsante sull’elemento e determina
lo stato visuale del controllo: Checked e Unchecked che sono definiti nel Template.
La collezione VisualStateGroups definisce uno o più VisualStateGroup che rappresenta un
gruppo di stati visuali.
Ogni stato, identificato dall’oggetto VisualState definisce una Storyboard, in pratica una
serie di animazioni da eseguire quando lo stato visuale cambia, nell’esempio cambiano la
proprietà Fill del rettangolo di nome square.
Definire la CheckBox e usare il template grazie alla proprietà Template.
Ogni VisualStateGroup rappresenta un insieme logico di stati che un controllo può avere e
in genere si dispone di più gruppi per impostare più animazioni contemporaneamente.
La maggior parte dei controlli predispone i gruppi CommonStates e FocusStates che
possono avere rispettivamente gli stati Normal, PointerOver o MouseOver e Pressed per il
primo, mentre per il secondo Focus e Unfocused.
Ogni gruppo può essere in uno di questi stati, quindi è possibile specificare
contemporaneamente animazioni per il passaggio del mouse e per l’uso della tastiera.
Tutti i template standard di Visual Studio includono un file chiamato
STANDARDSTYLE.XAML che definisce una serie di stili di default: attenzione alcuni
sono commentati, eliminare il commento per poterli utilizzare.
DATA BINDING
Lo scopo principale di un’applicazione è di visualizzare dati provenienti da DB, servizi o
web per la consultazione, la modifica, la cancellazione o l’inserimento.
Il binding è una tecnica che permette di creare collegamenti fra dati e UI sollevando il
programmatore dalla necessità di copiare i dati da una parte all’altra del sistema, grazie ad
un meccanismo di collegamento automatico offerto dal .NET Framework che può essere
bidirezionale, quindi si può caricare un campo di testo con il contenuto di un campo del DB
e importare le modifiche semplicemente specificando un’associazione.
Nel binding ci sono tre attori.
1. Destinazione: chi deve subire il binding.
2. Sorgente: la sorgente di dati.
3. Oggetto Binding: collega tra loro sorgente e destinatario.
In XAML c’è un meccanismo che facilita queste operazioni: il data binding, è il legame che
si crea tra controlli e sorgente dati in modo da riflettere le modifiche apportate al controllo
sulla sorgente e viceversa.
Anche se è possibile dichiarare il binding a run-time via codice, è meglio farlo nello XAML,
usando le markup estension.
<TextBlock x:Name="TextLabel"
Text="{Binding ElementName= MioSlider, Path=Value}"/>
<Slider x:Name=" MioSlider"/>
Visual C#
um 110 di 202
L’espressione di binding è impostata sulla DependencyProperty dell’oggetto destinazione
e racchiude tra ({ }) la parola chiave Binding che consente di dichiarare le proprietà e il
rispettivo valore, separando ciascuna con una virgola.
La proprietà ElementName specifica la sorgente, riferendosi ad un elemento cui è stato
assegnato un nome con la proprietà Name, ereditata da FrameworkElement.
La proprietà Path specifica quale proprietà usare come sorgente di dati.
Modalità di connessione tra sorgente e destinatario
Il motore di data binding è in grado non solo di leggere i valori e mostrarli a video ma
grazie alla proprietà Mode, è in grado di ripetere l’operazione di caricamento dei dati ogni
volta che la sorgente cambia.
Il valore di Mode è automatico e dipende dalla sorgente dati ma può assumere i seguenti
valori che specifica la direzione del flusso dei dati.
1. OneWay: default, ogni cambiamento apportato alla sorgente dati è propagato fino alla
proprietà del destinatario, mentre non avviene il contrario, in altre parole è una
connessione a senso unico.
2. OneTime: i valori della sorgente dati sono riversati una sola volta, ignorando poi i
successivi cambiamenti.
3. OneWayToSource: i valori della sorgente dati sono riversati sul destinatario e ogni
cambiamento effettuato su quest’ultimo si riflette sulla sorgente.
4. TwoWay: i valori della sorgente e del destinatario sono sincronizzati, quindi ogni
modifica apportata ad una delle parti si riflette sull’altra; permette di progettare
maschere di modifica dati con controlli come TextBox o ComboBox senza digitare
codice.
Esempio, binding bidirezionale di tipo TwoWay.
Se si sposta il cursore dello slider, da sinistra verso destra, la TextBox visualizza il valore
della proprietà Value dello Slider.
Invece, digitando un valore nella TextBox, quest’ultimo è propagato allo Slider,
aggiornando la posizione del cursore in modo bidirezionale.
<TextBox x:Name="Prova"
Text ="{ Binding ElementName=mySlider, Path, =Value, Mode=TwoWay}"/>
<Slider x:FieldModifier=" mySlider"/>
Esempio, definire il contesto dati di un controllo utente.
<UserControl.DataContext>
<vm:ViewModel1/>
</UserControl.DataContext>
Il contesto dati è la classe di nome ViewModel1 nel namespace indicato con il prefisso vm.
Si farà corrispondere questo prefisso ad un namespace specifico nell’intestazione della
definizione del conrollo utente, in questo modo.
<UserControl
x:Class="TestSandbox.Views.ClickableImage"
…
xmlns:vm="clr-namespace:TestSandbox.ViewModels"
xmlns:vw="clr-namespace:TestSandbox.Views">
I due prefissi, vm e vw, fanno riferimento ai namespace TestSandbox.ViewModels e
TestSandbox.Views.
Quindi, il progetto si chiama TestSandbox, il conrollo utente si chiama ClickableImage, è in
Visual C#
um 111 di 202
una cartella e in un namespace chiamato Views.
L’applicazione è partizionata in altre due cartelle, chiamate VIEWMODELS e VIEWS, cui
fanno riferimento i prefissi definiti.
La definizione della classe cui si fa riferimento è la seguente.
namespace TestSandbox.ViewModels {
class ViewModel1 {
…}
}
Si può definire più di un contesto dati e lo si può definire a diversi livelli dell’albero di
componenti dell’interfaccia.
Il primo elemento che definisce un’associazione dati è l’origine dei dati.
Il .NET Framework permette d’interrogare oggetti, utilizzandone le proprietà.
Esempio, collegamento ad una proprietà chiamata Nome, è una dichiarazione di origini
dati.
{Binding Nome}
Questa è la classe che offre una proprietà Nome.
public class Persona : INotifyPropertyChanged {
private string nome;
// dichiarazione di evento
public event PropertyChangedEventHandler PropertyChanged;
public Persona()
{}
public Persona(string value)
{ this.nome = value; }
public string Nome
{ get { return nome; }
set {
nome = value;
OnPropertyChanged("Nome");
}
}
}
La proprietà Nome dev’essere una proprietà della classe, con il suo get e il suo set.
Non è sufficiente definire un campo pubblic.
La classe deve corrispondere all’interfaccia INotifyPropertyChanged e gestire l’evento
OnPropertyChanged.
Il primo requisito è necessario per consentire al .NET Framework di accedere
dinamicamente alle variabili di una classe per nome.
Il secondo requisito permette al .NET Framework di capire quando una variabile è
cambiata e quindi aggiornare l’UI.
L’effetto pratico di questo è che se si cambia il nome di una persona in una vista di
dettaglio, il nome visualizzato nell’elenco dei nomi sarà aggiornato di conseguenza, senza
richiedere programmazione specifica.
L’accesso a proprietà può essere reiterato, per esempio indicando di agganciare alla
proprietà Attributo dell’oggetto accessibile attraverso la proprietà Nome del contesto dati
attuale.
Visual C#
um 112 di 202
{Binding Nome.Attributo}
Se un elemento d’interfaccia aggiorna lo stato di una proprietà del contesto dati, in pratica
il collegamento ha un Mode TwoWay o OneWayToSource, si può usare l’attributo
UpdateSourceTrigger per definire quando il .NET Framework deve aggiornare i dati.
Nel caso più semplice, il trasferimento avviene quando l’utente sposta il cursore su un
elemento d’interfaccia diverso, terminando l’editing.
In questo caso il trigger è LostFocus.
A volte la business logic ha bisogno di vedere i dati mentre sono aggiornati, per esempio
per verificare la lunghezza di un campo, validare un codice fiscale.
In questi casi, il Trigger può essere impostato a PropertyChanged.
Esempio, campo di testo con con aggiornamento automatico.
<TextBox Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}"/>
La proprietà da aggiornare si chiamerà Name e l’aggiornamento avviene per ogni
carattere inserito dall’utente.
La proprietà destinazione dev’essere una proprietà dipendente e in WPF la maggior parte
degli elementi che interagiscono con l’UI ereditano indirettamente dalla classe
DependencyObject la quale può essere vista come un contenitore di proprietà, in
particolar modo di oggetti DependencyProperty.
Proprietà dipendenti
Quasi tutte le proprietà di un elemento WPF sono proprietà dipendenti.
Le proprietà Dependency consentono d’impostare il valore di un elemento in diversi modi.
Per implementarne una, è necessario creare un campo statico DependencyProperty sul
controllo, chiamando il metodo DependencyProperty.Register.
Questo metodo registra la proprietà e restituisce un’istanza del campo
DependencyProperty creato.
Una volta creato il campo DependencyProperty, è possibile utilizzarlo tramite i metodi
GetValue e SetValue.
Se è necessario aggiungere un evento, da chiamare quando la proprietà è modificata, è
possibile modificare la registrazione per includere un oggetto FrameworkPropertyMetadata
che specifica, oltre al valore predefinito, anche un metodo di callback.
La sorgente non deve per forza limitarsi ad oggetti dati del CLR ma supporta qualsiasi
oggetto come, ad esempio, controlli, oggetti list, oggetto associato ad un datasource
ADO.NET (ActiveX Data Objects), web service o un nodo XML che contiene dei dati.
È possibile agganciare tra loro i componenti visuali semplicemente usando l’attributo
ElementName per indicare la fonte di associazione.
Esempio, la proprietà Text della TextBox txt2 è collegata a quella di txt1 e, quindi, poiché
la proprietà Text è una DependencyProperty, al variare del valore di txt1, è
automaticamente aggiornato il valore di txt2.
<StackPanel>
<TextBox Name="txt1" />
<TextBox Name="txt2" Text="{Binding ElementName=txt1,Path=Text,Mode=OneWay}"/>
</StackPanel>
Da questo si capisce che in alcune situazioni l’associazione dati potrebbe sostituire
l’utilizzo di eventi per la variazione di elementi nell’UI.
Visual C#
um 113 di 202
Binding di oggetti
Anche se è comodo collegare gli elementi di un’UI, è meglio legare un elemento dell’UI ad
una proprietà del codice: in questo modo diventa possibile evitare di sfruttare eventi per
aggiornare l’UI e sfruttare, invece, la potenza dell’interfaccia NotifyPropertyChanged.
Esempio.
public class Author
{ public string Name { get; set; }
public AuthorDetails Details { get; set; }
}
public class AuthorDetails
{ public string Site { get; set; }
public string Job { get; set; }
}
Creare nel costruttore della pagina un’istanza del tipo Author.
public MainPage()
{ InitializeComponent();
Author author = new Author();
// assegnare la proprietà
mainLayout.DataContext = author;
}
Implementare in binding via XAML la classe appena istanziata.
<StackPanel x:Name ="mainLayout">
<TextBlock Text="Nome"/>
<TextBox Text="{Binding Name}" />
<TextBlock Text="Ruolo"/>
<TextBox Text="{Binding Details.job}" />
<TextBlock Text="Sito"/>
<TextBox Text="{Binding Details.site}" />
</StackPanel>
Per ogni TextBox si è definito un’espressione di binding per collegare la proprietà Text alle
rispettive proprietà dei tipi Author e AuthorDetails.
Si è omessa la proprietà Path perché è predefinita per l’oggetto di Binding e quindi può
essere valorizzata senza che sia specificata esplicitamente.
Per stabilire il contesto di binding si è valorizzato la proprietà DataContext del controllo
che funge da contenitore, questa proprietà è esposta da ogni oggetto che erediti da
FrameworkElement compreso lo StackPanel che si utilizza come contenitore e si sfrutta
per condividere la sorgente dati con tutti gli oggetti contenuti.
Nell’esempio, quando è valutata la proprietà Source ed è trovato il valore null, il sistema di
binding passa automaticamente a controllare la proprietà DataContext della TextBox sulla
quale è definita.
Poiché trova anche quest’ultima senza valore, inizia a risalire la gerarchia dei controlli fino
a quando non incontra una proprietà DataContext con un valore utilizzabile.
L’utilizzo di questa proprietà evita di dover specificare per ogni espressione di binding la
sorgnete dati, così da mantenere il codice più leggibile, pulito e manutenibile.
Binding di collezioni
L’elemento deve ereditare da ItemsControl che espone la logica necessaria alla corretta
Visual C#
um 114 di 202
visualizzazione degli elementi di una collezione, occorre impostare le proprietà seguenti.
ItemSource: permette d’impostare la collezione utilizzata come sorgente dati.
DisplayMemberPath: indica quale proprietà utilizzare per visualizzare il testo associato
all’elemento.
Benchè sia possibile usare un controllo di tipo ItemsControl, per la mancanza di uno stile
visuale è meglio utilizzare le classi che lo estendono, per esempio ListBox.
<StackPanel x:Name ="mainLayout">
<TextBlock Text="Lista autori"/>
<ListBox x:Name="authors" DisplayMemberPath=" Name">
<TextBlock Text="Nome"/>
<TextBox x:Name="name"/>
<TextBlock Text="Ruolo"/>
<TextBox x:Name="job"/>
<TextBlock Text="Sito"/>
<TextBox x:Name="site"/>
<Button Content="Inserisci" Click="Button_Click"/>
</StackPanel>
Lo StackPanel contiene un controllo ListBox e un numero di TextBox per collezionare i dati
necessari a valorizzare i campi di tipo Author, oltre ad un controllo Button con associato un
event handler per l’evento Click.
Quando si preme il pulsante, è creata una nuova istanza di Author che è aggiunta ad una
collezione ObservableCollection di Author.
Il tipo generico ObservableCollection rappresenta una collezione dinamica di dimensioni
variabili che notifica l’inserimento e la rimozione di nuovi elementi mediante
l’implementazione dell’interfaccia INotifyCollection.
public sealed partial class MainPage : Page {
private ObservableCollection<Author> _authorList;
public ObservableCollection<Author> AuthorList
{ Get {
if (_authorList == null)
_authorList = new ObservableCollection<Author>();
return _authorList;
}
}
public MainPage()
{ InitializeComponent();
authors.ItemsSource = AuthorList;
}
private void Button_Click(object sender, RoutedEventArgs e)
{ Author author = new Author();
AuthorDetails details = new AuthorDetails();
author.Name = name.Text;
details.Site = site.Text;
details.Job = job.Text;
author.Details = details;
AuthorList.Add(author);
}
}
Nel costruttore della pagina, si assegna la collezione alla proprietà ItemsSource del
controllo ListBox; inizialmente la collezione è vuota e quindi lo sarà anche la ListBox.
Visual C#
um 115 di 202
Alla pressione del pulsante si collezionano i dati immessi nell’UI, si valorizzano tutti i campi
di Author e si aggiungono alla collezione AuthorList che notifica alla ListBox l’inserimento
del nuovo elemento; in modo da visualizzare immediatamente il valore della proprietà
specificata da DisplayMemberPath.
Tutti i controlli che ereditano da ItemsControl espongono la proprietà ItemTemplate, di tipo
DataTemplate che permette la personalizzazione delle informazioni visualizzate in ogni
elemento della collezione.
<ListBox x:Name="authors">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Name}"
Grid.ColumnSpan="2" Grid.Column="0" Grid.Row="0" />
<TextBlock Text="{Binding Details.Job}"
Grid.Column="0" Grid.Row="1" />
<TextBlock Text="{Binding Details.Site}"
Grid.Column="1" Grid.Row="1" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Esempio, data una struttura d’informazioni fatta di categorie e relativi prodotti, caricare i
dati separando l’UI dal codice, in pratica evitare che quest’ultimo faccia riferimenti diretti a
elementi.
Scegliere, tra i controlli disponibili, quello che più rispecchia le funzionalità necessarire
perchè, indipendentemente da quello scelto, il modo di procedere è autonomo.
Per esempio, usare l’oggetto list con la proprietà DataContext della finestra o della pagina
in fase di caricamento iniziale.
File MAINWINDOW.XAML.CS
using System.Windows;
using System.Windows.Markup;
namespace WpfApplication1 {
public partial class MainWindow : Window {
public MainWindow()
{ InitializeComponent(); }
private void Window_Loaded(object sender, System.Windows.RoutedEventArgs e)
{ this.Language = XmlLanguage.GetLanguage("it-IT");
Category[] list = {
new Category {
Products = new Product[] {
new Product {Description = "Categoria: computer - Desktop 1", Id = 1},
new Product {Description = "Categoria: tablet
- Tablet 2", Id = 2}},
Description = "Categoria computer",
Visual C#
um 116 di 202
Id = 1
},
new Category {
Products = new Product[] {
new Product {Description = "Categoria: tablet - Tablet 3", Id = 1},
new Product {Description = "Categoria: tablet - Tablet 4", Id = 2}},
Description = "Categoria 2",
Id = 2
}
};
/* impostare la lista di categorie come contesto dati dell'elemento radice
* da questo momento l'elemento stesso e figli possono accedere a tale
* oggetto e leggerne le proprietà */
this.DataContext = list;
}
}
public class Category
{ public int Id
{ get; set; }
public string Description
{ get; set; }
public Product[] Products
{ get; set; }
}
public class Product
{ public int Id
{ get; set; }
public string Description
{ get; set; }
}
}
Tutti gli ItemsControl, come i controlli ListBox, DataGrid, GridView hanno la proprietà
ItemSource che può essere valorizzata sia da codice sia da data binding.
La markup extension {Binding Path=.} legge la proprietà dal contesto dati attuale, indicata
con la proprietà Path.
Nell’esempio, il valore della proprietà Path è un (.), indica al motore di data binding di
caricare l’oggetto stesso, in pratica la lista di categorie.
File MAINWINDOW.XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Data Binding" Height="480" Width="640"
WindowStartupLocation="CenterScreen"
Icon=" \max.ico"
Loaded="Window_Loaded">
<Window.Resources>
<DataTemplate x:Key="CategoryTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
Visual C#
um 117 di 202
<Rectangle Fill="Red"
Width="10"
Height="10"
DockPanel.Dock="Left" />
<TextBlock Grid.Column="1" Text="{Binding Path=Description}" />
</Grid>
</DataTemplate>
</Window.Resources>
<StackPanel>
<TextBlock>Categorie</TextBlock>
<ListBox ItemsSource="{Binding Path=.}"
x:Name="categories"
ItemTemplate="{StaticResource CategoryTemplate}"
Margin="4">
</ListBox>
<TextBlock>Prodotti</TextBlock>
<ListBox ItemsSource="{Binding
Path=SelectedItem.Products,ElementName=categories}"
DisplayMemberPath="Description"
Margin="4">
</ListBox>
</StackPanel>
</Window>
Con il DataTemplate si definisce l’aspetto da dare a ogni elemento della lista, nell’esempio
si mette un rettangolo, all’interno del template c’è poi il contesto dati: questo è il singolo
elemento della lista, nell’esempio è l’oggetto Category.
In questo modo è possibile effettuare il data binding facendo riferimento alla proprietà
Description.
Il template è poi referenziato con la proprietà ItemTemplate della ListBox.
a proprietà DisplayMemberPath indica quale proprietà usare per la descrizione.
Il programmatore non deve preoccuparsi dei tempi e della creazione dei ListBoxItem
necessari a popolare la ListBox.
Master/Detail
É possibile usare anche scenari Master/Detail in cui la selezione su una prima lista,
determina il caricamento di un dettaglio o di una seconda lista, a cascata.
Creare una seconda ListBox che deve caricare i prodotti della categoria selezionata sulla
prima ListBox.
La proprietà Path può contenere al posto del (.), la navigazione sotto proprietà, quindi con
SelectedItem si ottiene la Category selezionata e al suo interno leggere le proprietà.
Nell’esempio si deve specificare su quale oggetto interrogare la proprietà SelectedItem, la
prima ListBox di nome categories, si carica poi la lista dei prodotti nella seconda ListBox.
In questo modo, selezionando la categoria, la lista dei prodotti cambierà automaticamente.
Visual C#
um 118 di 202
Fonti dati
Data una casella di testo e un’etichetta, per inserire il testo immesso dall’utente, basta
dare un nome al primo controllo e referenziarlo nell’espressione di data binding.
Per fare questo si usa la proprietà ElementName dell’oggetto Binding per interrogare,
sempre con la proprietà Path, le proprietà di un elemento.
File MAINWINDOW.XAML.CS
Uguale all’esempio precedente.
File MAINWINDOW.XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfApplication1"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="Data Binding" Height="480" Width="640"
WindowStartupLocation="CenterScreen"
Icon="\max.ico"
Loaded="Window_Loaded">
<Window.Resources>
<l:Product x:Key="myProduct"
Id="1"
Description="Prodotto 1" />
<l:DateToColorConverter x:Key="converter" />
</Window.Resources>
<StackPanel>
<TextBlock>TextBox:</TextBlock>
<TextBox x:Name="myText"
Margin="4" />
<TextBlock>TextBlock:</TextBlock>
<TextBlock Text="{Binding Path=Text,ElementName=myText}"
Foreground="Red"
Margin="4" />
<TextBlock Text="{Binding Path=Description,Source={StaticResource myProduct}}"
Margin="4" />
<TextBlock Text="{Binding StringFormat='Oggi è \{0:D\}',Source={x:Static
sys:DateTime.Now}}"
Foreground="{Binding Source={x:Static
sys:DateTime.Now},Converter={StaticResource converter}}"
Margin="4" />
</StackPanel>
</Window>
La markup extension {Binding Path=Text,ElementName=myText} permette di separare
ogni proprietà con la (,) e di valorizzarla con un’assegnazione.
Nell’esempio, si carica la proprietà Text dell’oggetto TextBox di nome myText.
Inoltre, dato che questa proprietà può variare perchè è una DependencyProperty, il motore
l’aggancia e fa sì che ogni modifica che si esegue alla casella di testo, si rifletta
automaticamente sull’etichetta sottostante.
La proprietà ElementName è specifica per sorgenti di tipo elemento e non vi sono limiti sul
tipo di sorgente di dati.
La proprietà Source consente di valorizzarla con qualsiasi istanza e tipo.
S’istanzia al classe Product come nell’esempio precedente e si carica, con la proprietà
Visual C#
um 119 di 202
Source e la markup extension {StaticResource}.
Nell’esempio si usa una markup extension all’interno di un’altra "{Binding Path =
Description, Source={StaticResource myProduct}}".
Dato che la sorgente dati è il prodotto, legge la descrizione con la proprietà Path.
Formattazione
Non sempre è possibile visualizzare le informazioni così come sono memorizzate perché
potrebbe essere necessario formattare o cambiare il tipo, per esempio convertire un
numero in una stringa.
Il motore di data binding permette di usare le funzionalità di formattazione delle stringhe
con la proprietà StringFormat.
Nell’esempio, si visualizza il giorno corrente a video ="{Binding StringFormat = 'Oggi è
\{0:D\} passando come valore una data.
L’uso degli (' ') è obbligatorio a causa degli spazi che sarebbero interpretati dal parser,
mentre il carattere (\) da mettere davanti alle ({ }), serve a distinguerle da quelle usate per
identificare una markup extension.
La proprietà Path non è specificata perchè, se omessa, equivale a indicare l’oggetto
stesso della sorgente, in altre parole la data ed è equivalente al punto.
La proprietà Converter specifica un convertitore di valori: una classe che implementa
l’interface IValueConverter.
Un convertitore vuole due membri.
1. Convert: richiamato quando il motore vuole prendere il valore originale e convertirlo nel
valore finale.
2. ConvertBack: richiamato quando il motore deve riversare i cambiamenti effettuati sul
destinatario di un’espressione di binding sulla sorgente dati, binding TwoWay.
File DATETOCOLORCONVERTER.CS
using System;
using System.Windows.Data;
using System.Windows.Media;
namespace WpfApplication1 {
public class DateToColorConverter : IValueConverter {
public static readonly DateToColorConverter Instance = new
DateToColorConverter();
public object Convert(object value, System.Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{ System.DateTime d = (System.DateTime)value;
if (d.Day % 2 == 0)
// cambia il colore della data in funzione del giorno pari (verde) dispari (rosso)
return Brushes.Green;
else
return Brushes.Red;
}
public object ConvertBack(object value, System.Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{ throw new NotImplementedException(); }
}
}
In MAINWINDOW.XAML si deve valorizzare la proprietà Foreground del testo, sempre con
l’espressione Binding, indicando come Source la data attuale e come convertitore quello
progetato in DATETOCOLORCONVERTER.CS.
Il motore leggerà la data, la passerà al convertitore che ritorna un Brush e sarà poi
assegnato alla proprietà Foreground del testo.
Visual C#
um 120 di 202
EVENTI
Non c’è un legame tra un evento di un particolare oggetto e il codice che deve gestirlo: è
possibile gestire più eventi con un solo metodo, purchè la firma di questi sia omogenea.
Un routed event consente di propagare un evento su e giù per l’albero dei controlli, così
che anche un controllo diverso da quello che ha scatenato l’evento possa intercettarlo.
Gli eventi e i relativi gestori possono essere dichiarati come attributi XAML o implementati
in Visual C#, in XAML spesso non sono necessari, perchè le logiche dei controlli sono
sufficienti a coprire la maggior parte delle esigenze.
In ogni caso, tutti gli elementi visuali hanno un set di eventi base per ogni aspetto legato
all’input da parte dell’utente.
 Gestione tastiera: KeyDown, KeyUp.
 Gestione mouse: MouseDown, MouseUp.
 Gestione Drag&Drop: DragEnter, Drop.
 Gestione penna: StylusDown, StylusUp.
 Gestione touch: TouchDown, TouchUp.
Per intercettare questi eventi, bisogna dichiarare l’evento come attributo sull’elemento e
specificare il nome del metodo da chiamare.
Esempio, intercettare l’evento Loaded sulla finestra.
XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Eventi" Height="480" Width="640" WindowStartupLocation="CenterScreen"
Loaded ="Window_Loaded" >
Il metodo Window_Loaded è poi definito nel code behind, per essere richiamato non
appena la finestra ha completato l’operazione di caricamento.
Visual C#
private void Window_Loaded(object sender, RoutedEventArgs e) { }
Gli eventi non sono tutti dello stesso tipo, per esempio Loaded è di tipo diretto, in pratica è
invocato solo sull’elemento su cui è stato scatenato.
Ci sono eventi di tipo bubble, in altre parole si propagano dall’elemento che li ha generati e
risalgono tutto l’albero degli elementi, fino ad arrivare a quello radice, per esempio
MouseUp è intercettato abbracciando più elementi contemporaneamente.
Esempio, intercettare a livello di finestra e di rettangolo l’evento di pressione del mouse di
tipo bubble.
Per provare le funzionalità di bubbing, inibire i clic effettuati sul rettangolo e notificare il
Visual C#
um 121 di 202
nome dell’oggetto che ha generato l’evento.
XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Eventi" Height="480" Width="640" WindowStartupLocation="CenterScreen"
MouseUp="Window_MouseUp">
<Grid Background="LightGray">
<Rectangle Width="100" Height="100" MouseUp="Rectangle_MouseUp" Fill="Red"/>
</Grid>
</Window>
Visual C#
private void Window_MouseUp(object sender,
System.Windows.Input.MouseButtonEventArgs e)
{ MessageBox.Show("Pulsante premuto su " + e.OriginalSource.GetType().Name); }
private void Rectangle_MouseUp(object sender, MouseButtonEventArgs e)
{ e.Handled = true; }
Eseguire l’applicazione, solo facendo clic sulla Grid si ottiene il messaggio.
L’evento MouseUp è sollevato a prescindere dall’area in cui si fa clic mentre la proprietà
Handled inibisce l’evento sul rettangolo.
Grazie a questa caratteristica, controlli come Button possono esporre l’evento Click,
indipendentemente dal loro template.
In questo modo, tutti gli eventi MouseUp e KeyUp generati dagli elementi che danno
l’aspetto al pulsante, sono intercettati in modo da fornire un unico evento che rappresenta
la funzionalità logica del pulsante: il clic.
Gli eventi e i relativi gestori possono essere dichiarati come attributi XAML o implementati
in Visual C#, per esempio creare un pulsante con relativo gestore dell’evento Click.
XAML
<!-- inserisce un pulsante nella finestra -->
<Button Click="cmdCalcola_Click" Background="Green" Content="Invia"/>
Visual C#
Button btnprova = new Button();
btnprova.Background = Brushes.Green;
btnprova.Text = " Invia";
btnprova.Click += new System.EventHandler(cmdCalcola_Click);
Visual C#
um 122 di 202
È possibile generare gli event handler anche dal designer del codice XAML digitando
Click= per poi premere il tasto TAB che, come suggerisce l’intellisense, consente di creare
tutto il necessario per agganciare l’evento con il metodo che si scrive nel code behind.
INPUT
Gestione dell’input di testo da parte dell’utente.
TextBox
Permette all’utente di digitare testo all’interno di una casella su una singola linea.
La proprietà TextWrapping gestisce caselle di testo multi linea, il supporto al controllo
ortografico è dato dalla proprietà IsSpellCheckEnabled che fornisce all’utente anche il
suggerimento della parola che sta digitando, evitando così di scriverla per intero.
La proprietà InputScope con valore TelephoneNumber visualizza a schermo una tastiera
virtuale numerica.
PasswordBox
Usata per le password perché non visualizza i caratteri digitati, possiede un pulsante sul
bordo destro per visualizzare temporaneamente i caratteri inseriti.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="200" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<TextBlock Margin="10" >Singola linea</TextBlock>
<TextBlock Grid.Row="1" Margin="10" >Password</TextBlock>
<TextBlock Grid.Row="2" Margin="10" >Multilinea con dizionario</TextBlock>
<TextBlock Grid.Row="3" Margin="10" >Numero di telefono</TextBlock>
<TextBox Grid.Column="1" Margin="10" VerticalAlignment="Top" />
<PasswordBox Grid.Column="1" Grid.Row="1" Margin="10" VerticalAlignment="Top"/>
<TextBox Grid.Column="1" Grid.Row="2" Margin="10" VerticalAlignment="Stretch"
TextWrapping="Wrap"/>
<TextBox Grid.Column="1" Grid.Row="3" Margin="10" VerticalAlignment="Top"
InputScope="TelephoneNumber" />
<StackPanel Orientation="Horizontal" Grid.Row="4">
<Slider x:Name="slider" Width="200" />
<TextBox Text="{Binding ElementName=slider, Path=Value, Mode=TwoWay}"
Width="200" />
Visual C#
um 123 di 202
</StackPanel>
</Grid>
OUTPUT
Sono oggetti che producono un output visivo sulle pagine.
TextBlock
Per assegnare testo si usa la proprietà Text ma è possibile formattare il testo e
visualizzare contenuti tramite uno o più Run intervallati da LineBreak.
<StackPanel Margin="10,0,356,0">
<TextBlock Text="Un testo di esempio" />
<TextBlock>
<Run FontWeight="Bold">Un testo bold</Run>
<LineBreak />
<Run FontStyle="Italic">Un testo italic</Run>
<LineBreak />
<Run Foreground="Red">Un testo in rosso</Run>
</TextBlock>
<TextBlock > Un testo di esempio con un <Bold>Bold</Bold> al suo interno
</TextBlock>
</StackPanel>
Visual C#
um 124 di 202
Button
È in grado d’interpretare e gestire l’input dell’utente.
Per specificare il contenuto da visualizzare si usa la proprietà Content che è di tipo Object
e quindi accetta qualsiasi tipo di oggetto, in pratica non ci sono limiti a ciò che un pulsante
è in grado di visualizzare, per esempio testo e immagini.
<Grid>
<Button Margin="10,10,507,325" >
<StackPanel >
<Image Source="figura.png " Height="60" Width="60"/>
<TextBlock HorizontalAlignment=" Center">
<Run Text="Ciao, mondo!"/>
</TextBlock>
</StackPanel>
</Button>
</Grid>
CheckBox
RadioButton
Permette la selezione di eventi mutuamente esclusivi.
OPERAZIONI IN CORSO
Per tutte le operazioni che comportano tempi di attesa occorre notificarle in modo visuale
all’utente.
ProgressBar
È possibile specificare un Value all’interno di un range minimo e massimo per mostrare
all’utente l’effettivo progresso dell’operazione.
Esempio.
XAML
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="53*"/>
<ColumnDefinition Width="263*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="100" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
Visual C#
um 125 di 202
<ProgressBar Value="77" Grid.ColumnSpan="2" Margin="0,10,0,91"
Grid.RowSpan="2" />
<ProgressBar Grid.Row="1" IsIndeterminate="True" Grid.ColumnSpan="2"
Margin="0,10,0,91" Grid.RowSpan="2" />
</Grid>
Visual C#
try {
ring.IsActive = true;
// operazione lunga
}
finally
{ ring.IsActive = true; }
GLOBALIZZAZIONE
Consiste nella progettazione e nello sviluppo di applicazioni eseguibili a livello
internazionale, supporta, ad esempio, UI localizzate e dati internazionali per utenti che
utilizzano impostazioni cultura diverse.
WPF fornisce funzionalità di progettazione globalizzate, inclusi il layout automatico, gli
assembly satellite, nonché attributi e commenti localizzati.
LOCALIZZAZIONE
È possibile distribuire l’applicazione nella lingua dell’utente.
Consiste nella traduzione delle risorse dell’applicazione in versioni appositamente
localizzate per le impostazioni cultura supportate dall’applicazione.
La localizzazione in WPF richiede l’utilizzo delle API nello spazio dei nomi
System.Windows.Markup.Localizer.
Esistono diverse opzioni utilizzabili per localizzare un’applicazione WPF.
Ad esempio, è possibile associare le risorse localizzabili nell’applicazione ad un file XML,
archiviare il testo localizzabile in tabelle RESX o utilizzare file XAML.
Qui è descritto un flusso di lavoro di localizzazione che utilizza BAML (Binary XAML), il
quale fornisce diversi vantaggi.
 È possibile effettuare la localizzazione dopo la compilazione.
 È possibile effettuare l’aggiornamento ad una versione più recente di BAML con
localizzazioni da una versione precedente di BAML, il che significa sviluppare e
localizzare contemporaneamente.
 È possibile convalidare elementi di origine e semantica originali in fase di
compilazione, poiché BAML è la forma compilata di XAML.
Il processo di compilazione per la localizzazione è il seguente, il programamtore crea e
Visual C#
um 126 di 202
globalizza l’applicazione WPF.
Nel file di progetto, impostare <UICulture>en-US</UICulture> in modo tale che, quando
l’applicazione è compilata, sia generato un assembly principale indipendente dalla lingua.
Questo assembly possiede un file RESOURCES.DLL satellite contenente tutte le risorse
localizzabili.
Quando il file è compilato nella build, XAML è convertito in BAML.
Il file MYDIALOG.EXE indipendente dalle impostazioni cultura e il file
MYDIALOG.RESOURCES.DLL dipendente dalle impostazioni cultura (inglese) sono
distribuiti ai clienti di lingua inglese.
Il processo di localizzazione ha inizio una volta terminata la compilazione del file
MYDIALOG.RESOURCES.DLL non localizzato.
Gli elementi e le proprietà dell’interfaccia utente nel codice XAML originale sono estratti da
BAML
in
coppie
chiave/valore
utilizzando
le
API
contenute
in
System.Windows.Markup.Localizer.
I localizzatori utilizzano le coppie chiave/valore per localizzare l’applicazione.
Una volta completata la localizzazione, sarà possibile generare un nuovo file
RESOURCE.DLL a partire dai nuovi valori.
Le chiavi delle coppie chiave-valore sono valori x:Uid inseriti dal programmatore nel codice
XAML originale.
Questi valori x:Uid consentono all’API di tenere traccia e di unire le modifiche eseguite dal
programmatore e il localizzatore durante la localizzazione.
Ad esempio, se il programmatore modifica l’interfaccia utente quando il localizzatore ha
già avviato il processo di localizzazione, è possibile unire la modifica apportata durante lo
sviluppo con la versione di localizzazione già completata, così che il lavoro di traduzione
effettuato andrà perso soltanto in minima parte.
Il grafico presuppone che il programamtore scriva l’applicazione in inglese, quindi crea e
globalizza l’applicazione WPF.
Visual C#
um 127 di 202
In WINDOW1.XAML digitare.
<Window SizeToContent="WidthAndHeight">
La proprietà Window ridimensiona automaticamente la finestra in base alla dimensione del
contenuto e impedisce alla finestra di troncare il contenuto le cui dimensioni aumentano in
seguito alla localizzazione.
Rimuove, inoltre, lo spazio non necessario nel caso in cui le dimensioni del contenuto
diminuiscano in seguito alla localizzazione.
<Grid x:Uid="Grid_1">
Le proprietà Uid sono necessarie per il corretto funzionamento delle API di localizzazione
per tenere traccia delle modifiche tra lo sviluppo e la localizzazione dell’UI.
Consentono di unire una versione più recente dell’UI ad una localizzazione meno recente.
Xml:lang="en-US"
È collocato nell’elemento radice dell’UI, questa proprietà descrive le impostazioni cultura di
un dato elemento e dei relativi elementi figlio.
Questo valore è utilizzato da diverse funzionalità e dev’essere modificato in maniera
adeguata durante la localizzazione, modifica ad esempio il dizionario utilizzato per la
sillabazione e il controllo ortografico delle parole.
Influisce anche sulla visualizzazione delle cifre e sul modo in cui il sistema di fallback dei
tipi di carattere seleziona il tipo di carattere da utilizzare.
Infine, la proprietà influisce sulla modalità di visualizzazione dei numeri e dei testi scritti in
lingue con alfabeti non latini.
Il valore predefinito è en-US.
Tutte le risorse, lingua e fattori di scala, devono essere incluse nel file PRI (Package
Resource Index).
Per localizzare l’applicazione bisogna aggiungere al progetto il file RESOURCE1.RESX.
È un file XML che non si edita, si utilizza un editor che permette di aggiungere, in formato
stringa, tuttte le risorse dell’app.
Esempio, aggiungere al markup un controllo seguente.
<TextBlock x:Uid="Header"/>
La proprietà x:Uid identifica in modo univoco la “base” della chiave corrispondente
dichiarata nel file di risorse.
La convenzione sui nomi impone che la chiave nella risorsa, dichiarata nel file RESW, sia
composta dal nome indicato dalla proprietà x:Uid più la proprietà da impostare, separate
da un punto; nell’esempio Text è la proprietà da inserire e Valore è la stringa da
visualizzare.
Visual C#
um 128 di 202
Se si volesse diminuire la dimensione del font, basta inserire nella colonna Nome
Header.FontSize.
Per specificare un file per ogni lingua, si deve creare una cartella con il nome della lingua,
per esempio “fr-FR” e aggiungere al suo interno il file RESW.
Esempio, recuperare il valore di una risorsa.
var resourceLoader = new ResourceLoader();
var header = resourceLoader.GetString("Header");
La classe ResourceLoader espone solo due metodi.
1. GetString.
2. GetStringForReference.
Metodo Dispatcher.Invoke
Chiama il delegate indicato con la priorità specificata.
Questo si rende necessario in WPF poiché un thread secondario non può accedere
direttamente al thread dell’UI.
Nel caso in cui il thread secondario deve aggiungere degli item ad una ListBox, deve
delegare l’operazione all’oggetto Dispatcher associato al thread dell’UI.
Command pattern
Permette d’isolare la porzione di codice che effettua un’azione dal codice che ne richiede
l’esecuzione.
Tale pattern assicura che ogni oggetto riceve i propri comandi e fornisce un
disaccoppiamento tra mittente e destinatario.
Il mittente è un oggetto che invoca un’operazione e il ricevitore è un oggetto che prende la
richiesta e agisce su di esso.
L’azione è incapsulata nell’oggetto Command.
Visual C#
um 129 di 202
APPLICAZIONI DESKTOP
INTRODUZIONE
L’implementazione di una finestra include sia l’aspetto sia il comportamento.
L’aspetto definisce le caratteristiche visive della finestra: implementato tramite XAML.
Il comportamento definisce il funzionamento nel momento in cui gli utenti interagiscono
con essa: implementato tramite code behind.
Per creare un nuovo progetto, fare clic su File/Nuovo/Progetto… (CTRL+N).
Nella finestra di dialogo Nuovo Progetto selezionare Altri linguaggi/Visual C#, quindi
selezionare il tipo di applicazione, Applicazione WPF, immettere il nome del progetto nel
campo Nome: WpfApplication1, quindi per creare il progetto, premere OK.
Visual C# crea due file XAML con i relativi file di code behind.
File APP.XAML.CS
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace WpfApplication1 {
// logica d'interazione per App.xaml
public partial class App : Application
{ }
}
File APP.XAML
A prescindere dalla tipologia di applicazione da realizzare, l’infrastruttura di base è fornita
dalla classe Application, ogni applicazione è un’istanza di Application e Window contenute
nel namespace System.Windows.
La prima costituisce l’applicazione da eseguire, mentre la seconda rappresenta una
finestra nel mondo WPF.
Visual C#
um 130 di 202
Questa classe accomuna tutte le piattaforme che utilizzano XAML come linguaggio
di markup per la realizzazione delle UI.
La classe Application ha lo scopo di fornire l’infrastruttura necessaria a gestire il
ciclo di vita dell’applicazione, le risorse, l’UI e il sistema di navigazione.
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
Il metodo Main non c’è, perché il punto d’ingresso dell’applicazione è incapsulato dalla
classe Application e la proprietà StartupUri indica quale finestra utilizzare come interfaccia
iniziale dell’applicazione, in questo caso MainWindow.xaml.
Il fatto che il file APP.XAML e APP.XAML.CS costituiscono il punto di partenza è indicato
al compilatore tramite la finestra delle Proprietà.
Dopo aver selezionato il file APP.XAML, la proprietà Operazione di compilazione è
impostata nel seguente modo.
Ciò fa in modo che dietro le quinte è generato il codice Visual C# seguente.
public static void Main()
{ WinAppWPF.App app = new WinAppWPF.App();
app.InitializeComponent();
app.Run();
}
Il metodo Run della classe Application fa partire l’applicazione utilizzando come finestra
MainWindow.xaml.
File MAINWINDOW.XAML.CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
Visual C#
um 131 di 202
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1 {
// logica d'interazione per MainWindow.xaml
public partial class MainWindow : Window {
public MainWindow()
{ InitializeComponent(); }
}
}
File MAINWINDOW.XAML
Contiene la definizione di una finestra, istanza in WPF della classe Window.
Tutte le caratteristiche dell’UI possono essere create in XAML, mentre le funzionalità di
gestione nel code behind.
Nella finestra di Progettazione è visualizzato il form in alto e il codice XAML in basso.
Ogni finestra deve avere un contenitore in cui posizionare gli elementi d’interfaccia.
Visual Studio incoraggia l’utilizzo della classe Grid, per la flessibilità di posizionamento.
La proprietà Operazione di compilazione è impostata al valore Page e la finestra quindi
può essere utilizzata dall’Application come finestra principale per mezzo del suo URI
(Uniform Resource Identifier).
Visual Studio consente di editare manualmente il codice XAML, oppure di creare e
modificare l’interfaccia grafica per mezzo del designer, grazie ai controlli della Casella
degli strumenti.
Visual C#
um 132 di 202
Esempio, aggiungere un pulsante alla finestra.
Basta creare all’interno dell’elemento Grid un sotto elemento Button.
Gli oggetti sono inseriti in maniera dichiarativa all’interno del file XAML e poi il motore
WPF linterpreta e li traduce, in fase di run-time, in corrispondenti classi .NET.
Si crea un’istanza della classe Window che rappresenta la finestra e poi un’istanza di
Button che rappresenta il pulsante, aggiungendolo alla finestra.
In pratica, un TAG rappresenta una classe, mentre ogni attributo una proprietà di essa.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Pulsante" Height="80" Width="670" WindowStartupLocation="CenterScreen"
Icon="max.ico">
<Grid>
<Button Width="100" Height="30" Click="Button_Click_1">Cliccami</Button>
</Grid>
</Window>
Fare doppio clic sul pulsante nella finestra di Progettazione, si apre il file
MAINWINDOW.XAML.CS di code behind per implementare il gestore Button_Click.
private void Button_Click(object sender, RoutedEventArgs e)
{ MessageBox.Show("Ciao, mondo!"); }
Eseguire l’applicazione, si vede la finestra desktop che si può ingrandire, minimizzare o
ridimensionare.
Il pulsante dispone dello skin di sistema (in questo esempio Windows 7), poichè WPF
dispone di molteplici stili e template già preconfezionati che sono scelti a seconda del SO
e del tema scelto.
In pratica, non si usa il rendering Win32 ma si emula
l’interfaccia del sistema con l’engine di WPF.
Struttura dell’applicazione
Per utilizzare appieno le potenzialità vettoriali di WPF, non s’indicheranno delle dimensioni
fisse alla figura ma queste saranno relative al ridimensionamento del form da parte
dell’utente in modo da apprezzare che, in qualsiasi risoluzione o qualsiasi tipo di schermo,
il disegno non assume aspetti diversi.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
Visual C#
um 133 di 202
<!-- qui sono definite le risorse necessarie all'applicazione -->
</Window.Resources>
<Viewbox>
<!-- è un controllo che permette l’adattamento automatico del contenuto
rispetto al contenitore, per esempio una figura (contenuto) è
automaticamente ridimensionato al ridimensionamento
della Window (contenitore) -->
<Canvas Height="480" Width="640">
<!-- permette il posizionamento assoluto dei controlli al suo interno -->
<Button Width="300" Height="250">Cliccami</Button>
</Canvas>
</Viewbox>
</Window>
GESTIONE DELLE FINESTRE
La finestra è quel componente sul quale è possibile agire solo con le proprietà che poi
agiscono sul SO.
È possibile cambiarne però le caratteristiche tipiche: l’icona, il titolo e il dimensionamento.
Esempio, progettare una finestra trasparente, senza bordi, non rettangolare che non lasci
tracce sulla barre delle applicazioni.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:esempio"
Title="Finestra"
SizeToContent="WidthAndHeight"
WindowStyle="None" Background="Transparent" AllowsTransparency="True"
ShowInTaskbar="false"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
Icon="F:\Esercizi\C#\WPF\WpfApplication1\WpfApplication1\max.ico">
</Window>
La proprietà WindowStyle permette di scegliere il tipo di cromatura della finestra, per
esempio ToolWindow adatta la finestra a essere un pannello di strumenti agganciato alla
finestra principale, None nasconde l’intera cromatura.
In genere si rende visibile solo la finestra principale e si nascondono tutte le finestre figlie.
Una caratteristica del mondo desktop, infatti, è quella di gestire l’UI con altre finestre.
La finestra può essere di due tipi.
1. Non Modale (Show) l’utente può usare altre finestre, mentre la finestra principale è
visualizzata; le istruzioni successive alla chiamata del metodo sono eseguite
immediatamente dopo la visualizzazione della finestra.
2. Modale (ShowDialog) tutte le altre finestre dell’applicazione sono inattive; il codice
successivo non è eseguito finché la finestra non è chiusa; l’input dalla tastiera o dal
mouse è valido solo relativamente agli oggetti presenti sulla finestra, per effettuare
input da un’altra finestra, la finestra modale dev’essere scaricata.
Visual C#
um 134 di 202
Esempio.
File MAINWINDOW.XAML.CS
using System.Windows;
namespace WpfApplication1 {
public partial class MainWindow : Window {
public MainWindow()
{ InitializeComponent(); }
private void btnModale_Click(object sender, RoutedEventArgs e)
{ /* istanzio la classe che la finestra rappresenta, questo permette,
* tramite costruttore, di passare parametri alla finestra che si va ad aprire */
Window1 finestraM = new Window1();
// finestra modale
bool r = (bool)finestraM.ShowDialog();
/* r dice se la finestra è stata chiusa premendo X o se, dal punto di vista logico
* della finestra, è stata chiusa perchè le operazioni sono terminate */
}
private void btnNonModale_Click(object sender, RoutedEventArgs e)
{ Window2 finestraNM = new Window2();
// finestra NON modale
finestraNM.Show();
}
}
}
Visual C#
um 135 di 202
Esempio, progettare una finestra di dialogo che mostri un messaggio.
File MAINWINDOW.XAML.CS
using System.Windows;
namespace Finestra {
public partial class MainWindow : Window {
Window1 ib = new Window1();
public MainWindow()
{ InitializeComponent(); }
private void Button_Click_1(object sender, RoutedEventArgs e)
{ ib.Show(); }
}
}
File MAINWINDOW.XAML
<Window x:Class="Finestra.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Finestra di dialogo" Height="350" Width="525"
WindowStartupLocation="CenterScreen" Icon="max.ico">
<Grid>
<Button Content="Premi" HorizontalAlignment="Left" Height="37" Margin="30,31,0,0"
VerticalAlignment="Top" Width="135" Click="Button_Click_1"/>
</Grid>
</Window>
File WINDOW1.XAML.CS
using System.Windows;
using System.Windows.Input;
namespace Finestra {
public partial class Window1 : Window {
public Window1() : this("Finestra di dialogo") { }
public string ItemName {
get { return InputItemName.Text; }
set { InputItemName.Text = value; }
}
public Window1(string title) {
InitializeComponent();
InputItemName.Focus();
InputItemName.Text = "Ciao, mondo!";
}
Visual C#
um 136 di 202
private void Button_Click(object sender, RoutedEventArgs e)
{ Close();
}
private void InputItemName_KeyDown(object sender, KeyEventArgs e) {
if (e.Key == Key.Enter) {
e.Handled = true;
DialogResult = true;
}
else
if (e.Key == Key.Escape) {
InputItemName.Text = null;
e.Handled = true;
DialogResult = true;
}
}
}
}
File WINDOW1.XAML
<Window x:Class="Finestra.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowStyle="None"
ShowInTaskbar="False"
Height="120"
Width="400">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="5*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Grid.Column="0" Source="max.ico" Width="22" Height="24"
></Image>
<TextBlock x:Name="WindowTitle" Grid.Row="0" Grid.Column="1"
VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold"
FontSize="15" />
<TextBlock Text="Avviso" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center"
Margin="5" TextAlignment="Right" />
<TextBox x:Name="InputItemName" VerticalAlignment="Center" Grid.Row="1"
Grid.Column="1" Width="230" KeyDown="InputItemName_KeyDown"/>
<Button Click="Button_Click" Width="80" Content="OK" Grid.Row="1"
Grid.Column="2" VerticalAlignment="Center" />
</Grid>
</Window>
Visual C#
um 137 di 202
Progettare un’applicazione che visualizza gli argomenti della riga di comando in una
ListBox.
File MAINWINDOW.XAML.CS
private void Window_Loaded(object sender, RoutedEventArgs e)
{ foreach (string arg in Environment.GetCommandLineArgs())
argsListBox.Items.Add(arg);
}
File MAINWINDOW.XAML
<Grid>
<ListBox HorizontalAlignment="Left" Height="Auto" VerticalAlignment="Top"
Width="Auto" Name="argsListBox"/>
</Grid>
Progettare un’applicazione che calcola il BMI (Body Mass Index).
È un dato biometrico, espresso come rapporto tra il peso e l’altezza di un individuo ed è
utilizzato come un indicatore dello stato di peso forma.
L’indice di massa corporea è definito con la formula seguente.
BMI = peso / altezza2
Il peso è espresso in chilogrammi e l’altezza in metri.
L’indice di massa corporea dimensionalmente è una densità di superficie ed è misurata in
Kg/m2: la superficie del corpo è approssimata come un quadrato di lato pari all'altezza.
Esempio, altezza 1.7 metri, peso corporeo 68 Kg.
BMI = peso / altezza 2 = 68 Kg / (1.7 m)2 = 68 / 2.89 = 23.53 Kg/m2
L’indice di massa corporea consigliato dipende maggiormente da età e sesso ma anche
da fattori genetici, alimentazione, condizioni di vita e condizioni sanitarie.
L’OMS (Organizzazione Mondiale della Sanità), la medicina nutrizionale convenzionale,
l’opinione pubblica e in parte anche la medicina generica usano delle tabelle come la
seguente per definire termini da “magrezza” fino a “obesità”.
Visual C#
um 138 di 202
Peso
Obesità di classe III
Obesità di classe II
Obesità di classe I
Sovrappeso
Normopeso
Sottopeso
Grave magrezza
Visual C#
Min
>= 40.00
35.00
30.00
25.00
18.50
16.00
Max
39.99
34.99
29.99
24.99
18.49
16.00
um 139 di 202
APPLICAZIONI BROWSER
INTRODUZIONE
Chiamate anche XBAP (XAML Browser APplication), sono pagine in cui è possibile
spostarsi utilizzando i collegamenti ipertestuali.
Non è HTML e tutto questo funziona solo su Windows, sono disponibili due opzioni.
1. Frame per ospitare contenuto esplorabile in pagine o finestre.
2. NavigationWindow per ospitare contenuto esplorabile in un’intera finestra.
Per creare un nuovo progetto, fare clic su File/Nuovo/Progetto… (CTRL+N).
Nella finestra di dialogo Nuovo Progetto selezionare Altri linguaggi/Visual C#, quindi
selezionare il tipo di applicazione, Applicazione browser WPF.
Immettere il nome del progetto nel campo Nome: WpfBrowserApplication1, quindi per
creare il progetto, premere OK.
Visual C# crea due file XAML con i relativi file di code behind.
File APP.XAML.CS
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Navigation;
namespace WpfBrowserApplication1 {
// logica d'interazione per App.xaml
public partial class App : Application
{ }
}
File APP.XAML
<Application x:Class="WpfBrowserApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Page1.xaml">
<Application.Resources>
</Application.Resources>
Visual C#
um 140 di 202
</Application>
File PAGE1.XAML.CS
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfBrowserApplication1 {
// logica d'interazione per Page1.xaml
public partial class Page1 : Page {
public Page1()
{ InitializeComponent(); }
}
}
File PAGE1.XAML
<Page x:Class="WpfBrowserApplication1.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="Page1">
<Grid>
</Grid>
</Page>
Non è creata una Window ma una Page, in quanto le applicazioni per browser sono
costituite da pagine piuttosto che da finestre: il funzionamento è identico.
PAGE1 rappresenta la prima pagina da visualizzare, ogni pagina nella quale si andrà a
navigare sarà rappresentata da un file XAML, avente come TAG root Page.
Aggiungere un pulsante alla finestra, creare all’interno di Grid un sotto elemento Button.
<Button Width="100" Height="30" Click="Button_Click_1">Cliccami</Button>
Fare doppio clic sul pulsante nella fiinestra di Progettazione e si apre il file
PAGE1.XAML.CS di code behind per implementare il gestore Button_Click.
private void Button_Click(object sender, RoutedEventArgs e)
{ MessageBox.Show("Ciao, mondo!"); }
In questo modo si uniranno le migliori caratteristiche del web con le applicazioni client.
Come una qualsiasi pagina web, infatti, anche questa tipologia di applicazioni può essere
distribuita tramite un server web.
Eseguire l’applicazione, si apre il browser con visualizzata la prima pagina.
La barra degli indirizzi punta ad un file con estensione XBAP, è un file XAML che contiene
le informazioni sul file EXE necessarie a stabilire chi è che distribuisce l’applicazione e la
lista dei file necessari al corretto funzionamento della stessa.
Visual C#
um 141 di 202
La compilazione di Visual Studio produce sempre un eseguibile con le relative DLL.
Il file XBAP permette di caricare il tutto su un sito Internet e tramite ClickOnce scaricare o
aggiornare l’eseguibile con tutte le dipendenze.
Esempio, aggiungere un’altra pagina al progetto.
Fare clic destro in Esplora soluzioni sul nome del progetto, dal menu contestuale
selezionare Aggiungi/Nuovo elemento… (CTRL+MAIUSC+A).
Alla voce Visual C# selezionare WPF/PaginaWPF.
Per spostarsi dalla Page1 alla Page2 come in un’applicazione web, si utilizza la classe
NavigationService e il suo metodo Navigate.
Nell’esempio, sostituire la MessageBox con il codice seguente.
File PAGE1.XAML.CS
private void Button_Click_1(object sender, RoutedEventArgs e)
{ NavigationService.Navigate("page2.xaml"); }
Oppure, da XAML, utilizzando il controllo Hyperlink, impostare la proprietà NavigateUri.
<Hyperlink NavigateUri="page2.xaml">Vai a pagina 2</Hyperlink>
C’è la possibilità di creare applicazioni ibride, sfruttando il concetto di navigazione anche in
una normale applicazione.
Basta utilizzare un oggetto NavigationWindow.
<NavigationWindow ... Source="Page2.xaml"/>
Visual C#
um 142 di 202
HOSTING DI CONTROLLI WPF IN WINDOWS FORM
Quando si dispone di una grande quantità di codice Windows Form, può essere opportuno
estendere l’applicazione Windows Form esistente con WPF anziché riscriverla dall’inizio.
Uno scenario comune è dato dalla situazione in cui si desidera incorporare una o più
pagine implementate con WPF all'interno dell'applicazione Windows Form.
L’applicazione host Windows Form utilizza un oggetto ElementHost, il cui scopo è di
ospitare un controllo WPF all’interno del “mondo” Windows Form.
Creare un nuovo progetto, fare clic su File/Nuovo/Progetto… (CTRL+N).
Nella finestra di dialogo Nuovo Progetto selezionare Altri linguaggi/Visual C#, quindi
selezionare il tipo di applicazione, per esempio Applicazione Windows Form.
Immettere
il
nome
del
progetto
nel
campo
Nome:,
per
esempio
WindowsFormsApplication, quindi per creare il progetto, premere OK.
Fare clic destro in Esplora soluzioni sul nome del progetto, dal menu contestuale
selezionare Aggiungi/Nuovo elemento… (CTRL+MAIUSC+A).
Alla voce Elementi di Visual C# selezionare WPF/Controllo utente WPF.
Inserire all’interno di Esplora soluzioni cartella Riferimenti le DLL seguenti.
WINDOWS.XAML.DLL e SYSTEM.WINDOWS.FORMS.INTEGRATION.DLL.
File FORM1.CS
Il metodo Form1_Load mostra la procedura generale per l’hosting di un controllo WPF.
using System;
using System.Windows.Forms;
using System.Windows.Forms.Integration;
namespace WindowsFormsApplication {
public partial class Form1 : Form {
public Form1()
{ InitializeComponent(); }
private void Form1_Load(object sender, EventArgs e)
{ // crea il controllo ElementHost per l'hosting dello user control WPF
ElementHost host = new ElementHost();
// impostare la proprietà Dock del controllo su DockStyle.Fill
host.Dock = DockStyle.Fill;
// crea lo user control WPF
WindowsFormsApplication.UserControl1 uc = new
WindowsFormsApplication.UserControl1();
// assegna lo user control WPF al controllo ElementHost
// proprietà Child
host.Child = uc;
// aggiunge il controllo ElementHost al form
// collezione di controlli child
this.Controls.Add(host);
Visual C#
um 143 di 202
}
}
}
File USERCONTROL1.XAML
<UserControl x:Class="WindowsFormsApplication.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Content="WPF
Impiegato" Margin="5" HorizontalAlignment="Center"/>
<Label Grid.Column="0" Grid.Row="1" Content="Cognome" Margin="5"/>
<Label Grid.Column="0" Grid.Row="2" Content="Nome" Margin="5"/>
<Label Grid.Column="0" Grid.Row="3" Content="Data di nascita" Margin="5"/>
<TextBox Grid.Column="1" Grid.Row="1" Margin="5" Width="180"
HorizontalAlignment="Left" VerticalAlignment="Top" />
<TextBox Grid.Column="1" Grid.Row="2" Margin="5" Width="180"
HorizontalAlignment="Left" VerticalAlignment="Top" />
<TextBox Grid.Column="1" Grid.Row="3" Margin="5" Width="100"
HorizontalAlignment="Left" VerticalAlignment="Top" />
</Grid>
</UserControl>
Visual C#
um 144 di 202
Eseguire l’applicazione.
Visual C#
um 145 di 202
HOSTING DI CONTROLLI WINDOWS FORM IN WPF
È possibile utilizzare i controlli Windows Form nelle pagine WPF.
Creare un nuovo progetto, fare clic su File/Nuovo/Progetto… (CTRL+N).
Nella finestra di dialogo Nuovo Progetto selezionare Altri linguaggi/Visual C#, quindi
selezionare il tipo di applicazione, per esempio Applicazione WPF.
Immettere il nome del progetto nel campo Nome:, per esempio WpfApplication1, quindi
per creare il progetto, premere OK.
Inserire all’interno di Esplora soluzioni cartella Riferimenti le DLL seguenti.
WINDOWSFORMSINTEGRATION.DLL e SYSTEM.WINDOWS.FORMS.DLL.
File MAINWINDOWS.XAML
<Window x:Class="Hosting_Form_in_WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wf="clrnamespace:System.Windows.Forms;assembly=System.Windows.Forms"
Title="MainWindow" Height="350" Width="525">
<!-- per utilizzare i controlli personalizzati e i controlli di terze parti in XAML, è
necessario importare spazi dei nomi e assembly di riferimento, in visualizzazione
XAML, nel TAG di apertura per Window, aggiungere una nuova riga dopo il secondo
mapping xmlns -->
<Grid>
<WindowsFormsHost>
<!-- controllo Windows Form ospitato: MaskedTextBox -->
<wf:MaskedTextBox x:Name="mtbDate" Mask="00/00/0000"/>
</WindowsFormsHost>
</Grid>
</Window>
File MAINWINDOWS.XAML.CS
I controlli Windows Forms possono essere utilizzati in WPF, esiste la classe
WindowsFormsHost che contiene un controllo Windows Forms standard o custom.
Si può creare da code behind l’istanza host di WindowsFormsHost e aggiungerla alla Grid
denominata grid1.
<Grid Name="grid1"></Grid>
Il metodo WindowLoaded mostra la procedura generale per l’hosting di un controllo
Window Form.
using System.Windows;
using System.Windows.Forms;
using System.Windows.Forms.Integration;
namespace Hosting_Form_in_WPF {
public partial class MainWindow : Window {
public MainWindow()
{ InitializeComponent(); }
private void WindowLoaded(object sender, RoutedEventArgs e) {
// crea il controllo WindowsFormsHost per l'hosting del controllo Window Form
WindowsFormsHost host = new WindowsFormsHost();
// crea il controllo Windows Form: MaskedTextBox
MaskedTextBox mtbDate = new MaskedTextBox("00/00/0000");
// assegna il controllo MaskedTextBox al controllo WindowsFormsHost
Visual C#
um 146 di 202
// proprietà Child
host.Child = mtbDate;
// aggiunge il controllo WindowsFormsHost alla pagina WPF
// collezione di controlli child
this.grid1.Children.Add(host);
}
}
}
Visual C#
um 147 di 202
EQUAZIONE DI 1°
DISEGNO DELLA FINESTRA
L’UI è progettata utilizzando XAML, non solo scrivendo direttamente il codice XML ma
passando attraverso il designer dedicato ad esso.
File MAINWINDOW.XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Equazione 1°" Height="210" Width="310" WindowStartupLocation="CenterScreen"
Icon="max.ico">
<Viewbox>
<Canvas Height="200" Width="310">
<Label x:Name="lblPromptA" Content="Coefficiente A" Canvas.Left="30"
Canvas.Top="15" Width="87" FontFamily="Microsoft Sans Serif" FontSize="12"/>
<Label x:Name="lblPromptB" Content="Coefficiente B" Canvas.Left="200"
Canvas.Top="15" Width="86" RenderTransformOrigin="0.5,0.5" FontFamily="Microsoft
Sans Serif" FontSize="12">
<Label.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform AngleX="-2.197"/>
<RotateTransform/>
<TranslateTransform X="-0.499"/>
</TransformGroup>
</Label.RenderTransform>
</Label>
<TextBox x:Name="txtCoefficienteA" Height="20" Canvas.Left="30"
TextWrapping="Wrap" Canvas.Top="40" Width="87" FontFamily="Microsoft Sans Serif"
FontSize="12" TextChanged="txtCoefficienteA_TextChanged"/>
<TextBox x:Name="txtCoefficienteB" Height="20" Canvas.Left="200"
TextWrapping="Wrap" Canvas.Top="40" Width="87" RenderTransformOrigin="0.048,0.502" FontFamily="Microsoft Sans Serif" FontSize="12"
Visual C#
um 148 di 202
TextChanged="txtCoefficienteB_TextChanged"/>
<Button x:Name="btnCalcola" Content="Calcola" Height="34" Canvas.Left="100"
Canvas.Top="100" Width="75" FontSize="12" FontFamily="Microsoft Sans Serif"
Click="btnCalcola_Click" IsEnabled="False"/>
<Label x:Name="lblSoluzione" Content="x non calcolata" Canvas.Left="69"
Canvas.Top="150" Width="167" RenderTransformOrigin="0.499,0.934"
FontFamily="Microsoft Sans Serif" FontSize="12" Grid.IsSharedSizeScope="True"
Foreground="#FF1100FF"/>
</Canvas>
</Viewbox>
</Window>
File MAINWINDOW.XAML.CS
In grassetto sono evidenziate le istruzioni della business logic, le sole digitate.
using System;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1 {
// logica d'interazione per MainWindow.xaml
public partial class MainWindow : Window {
public MainWindow()
{ InitializeComponent(); }
private void btnCalcola_Click(object sender, RoutedEventArgs e) {
double x = 0.0;
// acquisisce i coefficienti dai due TextBox
double a = Convert.ToDouble(txtCoefficienteA.Text);
double b = Convert.ToDouble(txtCoefficienteB.Text);
if (a != 0.0) {
x = -b / a;
lblSoluzione.Content = "La soluzione è: " + x;
}
else {
if (b != 0.0)
lblSoluzione.Content = "Non esiste nessuna soluzione!";
else
lblSoluzione.Content = "Esistono infinite soluzioni.";
}
}
}
}
using System;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1 {
// logica d'interazione per MainWindow.xaml
public partial class MainWindow : Window {
public MainWindow()
{ InitializeComponent(); }
private void btnCalcola_Click(object sender, RoutedEventArgs e)
{ double a = 0.0, b = 0.0, x = 0.0;
// acquisisce i coefficienti dai due TextBox
a = Convert.ToDouble(txtCoefficienteA.Text);
Visual C#
um 149 di 202
b = Convert.ToDouble(txtCoefficienteB.Text);
if (a == 0.0) {
if (b == 0.0)
lblSoluzione.Content = "Equazione indeterminata!";
else
lblSoluzione.Content = "Equazione impossibile!";
}
else {
if (b == 0.0)
lblSoluzione.Content = "Radice = 0.0!";
else {
x = -b / a;
lblSoluzione.Content = "Radice = " + x;
}
}
}
}
}
Visual C#
um 150 di 202
GRAFICA 2D
INTRODUZIONE
Il sistema di coordinate di WPF è misurato con i numeri a virgola mobile a precisione
doppia anziché a precisione singola.
Anche i valori di opacità e delle trasformazioni sono espressi come precisione doppia, è,
inoltre, supportata un’ampia gamma di colori ed è disponibile il supporto integrato per la
gestione degli input da spazi colore diversi.
La programmazione grafica è semplificata, grazie alla gestione automatica delle scene di
animazione, infatti, non occorre preoccuparsi dell’elaborazione delle scene, dei cicli di
rendering e dell’interpolazione bilineare.
Inoltre, è fornito il supporto per hit test e composizione alfa completa.
Il sistema grafico di WPF sfrutta i vantaggi dei componenti H/W grafici per ridurre al
minimo l’utilizzo della CPU.
Il primo passo verso lo sviluppo di un’applicazione 2D, passa attraverso la definizione di
un oggetto che si aggira per il monitor in un videogame: lo sprite.
Uno sprite è un’immagine bidimensionale, fissa o animata, all’interno di una scena.
Bitmap: incapsula la bitmap GDI+ costituita dai pixel dell’immagine creata dinamicamente.
Brush: usata per riempire le parti interne del poligono tracciato.
Pen: definisce l’oggetto per disegnare le linee e le curve.
SolidBrush: definisce i pennelli che sono utilizzati per il riempimento di forme grafiche,
come rettangoli, ellissi e poligoni.
Color: definisce un colore nel formato ARGB (Alpha Red Green Blue).
Point: rappresenta una coppia ordinata di coordinate x e y intere che definisce un punto
nel piano 2D.
Size: rappresenta una coppia di valori che specifica la grandezza di un oggetto.
LinearGradientBrush: incapsula una classe Brush per la gestione delle sfumature a
gradiente lineare.
Gli oggetti grafici ereditano da FrameworkElement, quindi possono essere inseriti
direttamente all’interno di Panel o controlli come Button o ListBox.
Tutti questi elementi che ereditano dalla classe base Shape, contenuta nel namespace
System.Windows.Shapes, espongono le seguenti proprietà.
 Fill: di tipo Brush, rappresenta il riempimento della forma.
 Stroke: di tipo Brush, rappresenta il riempimento del bordo della forma.
 StrokeThickness: di tipo Shape, specifica lo spessore del bordo della forma.
Utilizzando i tipi derivati da Shape, è possibile disegnare: Ellipse (ellisse o cerchio), Line
(linea), Path (figura complessa), Polygon (poligono), Polyline (polilinea) e Rectangle
(rettangolo).
Visual C#
um 151 di 202
Per colorare gli oggetti si usano le classi derivate dal tipo Brush.
 SolidColorBrush: è il riempimento più semplice, rappresentato da un riempimento
pieno di un colore uniforme che può essere impostato attraverso la proprietà Color.
 LinearGradientBrush: rappresenta una sfumatura che parte dal punto iniziale,
StartPoint e si modifica lungo una linea retta sino al punto finale, Endpoint e tra questi
due punti i colori che costituiscono la sfumatura possono essere illimitati e possono
essere impostati con GradientStops.
 RadialGradientBrush: è simile a LinearGradientBrush ma la sfumatura è radiale parte
dal punto iniziale e si modifica lungo il raggio di un cerchio sino al punto finale, in altre
parole il gradiente forma un cerchio il cui centro è il punto iniziale mentre il bordo
esterno è quello finale e s’imposta l’aspetto con le proprietà RadiusX e RadiusY.
 ImageBrush: il riempimento non è più un colore ma un’immagine per coprire
completamente l’oggetto al quale è applicata, per esempio immagini semitrasparenti di
tipo PNG (Portable Network Graphics), in modo da creare motivi personalizzati.
FORME 2D
Non sono utilizzate solo per la visualizzazione ma implementano molte delle funzionalità
fornite dai controlli, incluso l’input della tastiera e del mouse.
Esempio, evento MouseUp di un oggetto Ellipse gestito.
XAML
<Grid>
<Ellipse Name="clickableEllipse" Fill="Blue" MouseUp="clickableEllipse_MouseUp" />
</Grid>
Visual C#
private void clickableEllipse_MouseUp(object sender, MouseButtonEventArgs e)
{ MessageBox.Show("Hai fatto clic!"); }
Esempio, disegnare un rettangolo, una linea e un ellisse.
<Canvas Height="480" Width="640">
<Line X1="100" Y1="50" X2="400" Y2="100" Stroke="Blue"/>
<Ellipse Width="100" Height="70" Stroke="Red" Fill="Red"/>
<Rectangle Width="200" Height="110" Stroke="Green" Canvas.Left="50"
Canvas.Top="80"/>
</Canvas>
Visual C#
um 152 di 202
Esempio, definire come colore di riempimento di un oggetto e un gradiente che parte dal
giallo e finisce al bianco.
<Canvas Height="480" Width="640">
<Rectangle Width="320" Height="240" Stroke="Blue">
<Rectangle.Fill>
<LinearGradientBrush>
<LinearGradientBrush.GradientStops>
<GradientStop Color="Yellow" Offset="0" />
<GradientStop Color="White" Offset="1" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Canvas>
Esempio, inserire all’interno di un pulsante una serie di forme, per ognuna specificare il
colore di riempimento e le proprietà per aggiustarne la posizione.
<Button>
<Grid>
<Rectangle Fill="Red" Width=" 45.6" Height=" 48.8" HorizontalAlignment="Left"
VerticalAlignment="Top"/>
<Ellipse Fill="#FF3694FF" Margin =" 157.4,0,45.6,0" Width=" 70.4" />
<Path Fill="#FF7F7F7F" Stretch =" Fill" HorizontalAlignment="Left" Margin ="
72.3,6.5,0,3.7" Width=" 58.9"
Data=" M72.299999,30.2 L86.999997,6.5000006 130.2,32.1000001
98.199997,44.100001 z"/>
</Grid>
</Button>
Visual C#
um 153 di 202
Esempio, disegnare un ellisse utilizzando Path.
<Canvas Height="480" Width="640">
<Path Fill="Red" Stroke="Red">
<Path.Data>
<EllipseGeometry Center="320,240" RadiusX="180" RadiusY="90" />
</Path.Data>
</Path>
</Canvas>
Esempio, disegnare un cerchio e colorarlo mediante un gradiente composto da tre colori.
<Ellipse Width="160" Height="120" >
<Ellipse.Fill >
<LinearGradientBrush EndPoint=" 0.5,1" StartPoint=" 0.5,0">
<GradientStop Color=" #FFEA0F0F" Offset=" 0"/>
<GradientStop Color="Black" Offset=" 1"/>
<GradientStop Color="White" Offset=" 0.477"/>
</LinearGradientBrush>
</Ellipse.Fill >
</Ellipse>
Visual C#
um 154 di 202
GEOMETRIE 2D
Le forme 2D coprono il set standard di forme di base, può essere necessario creare forme
personalizzate per la progettazione di un’UI, per questo scopo, sono fornite le geometrie.
Il sistema di coordinate per la grafica 2D individua l’origine nella parte superiore sinistra
dell’area di rendering, in genere lo schermo; i valori positivi dell’asse x procedono verso
destra, mentre i valori positivi dell’asse y procedono in direzione discendente.
Geometrie semplici
 LineGeometry: è definito specificando il punto iniziale della linea e il punto finale.
 RectangleGeometry: la posizione e le dimensioni del rettangolo sono definite da
struttura Rect che specifica la posizione relativa, l’altezza e la larghezza, è possibile
creare un rettangolo arrotondato impostando le proprietà RadiusX e RadiusY.
 EllipseGeometry: è definito da un punto centrale, un raggio x e un raggio y.
Queste forme, oltre a forme più complesse, possono essere create mediante un oggetto
PathGeometry o combinando oggetti Geometry ma queste classi forniscono un mezzo più
semplice di creare queste forme geometriche di base.
Esempio, creare ed eseguire il rendering di LineGeometry, è impossibile che un oggetto
Geometry disegni se stesso, pertanto si utilizza una forma Path per il rendering della linea;
dato che una linea non ha un’area, l’impostazione della proprietà Fill dell’oggetto Path non
avrebbe alcun effetto; al contrario sono specificate solo le proprietà Stroke e
StrokeThickness.
<Grid>
<Path Stroke="Black" StrokeThickness="1" >
<Path.Data>
<LineGeometry StartPoint="10,20" EndPoint="100,130" />
</Path.Data>
</Path>
</Grid>
Esempio, creare ed eseguire il rendering di RectangleGeometry, la posizione è 50, 50 e
l’altezza e la larghezza sono entrambi 25, si crea un quadrato.
Visual C#
um 155 di 202
<Grid>
<Path Fill="LemonChiffon" Stroke="Black" StrokeThickness="1">
<Path.Data>
<RectangleGeometry Rect="50,50,25,25" />
</Path.Data>
</Path>
</Grid>
Esempio, creare ed eseguire il rendering di un oggetto EllipseGeometry, l’oggetto Center è
impostato sul punto 50, 50 e il raggio x e il raggio y sono entrambi impostati su 50, per
creare un cerchio con un diametro pari a 100; la parte interna dell’ellisse è disegnata
assegnando un valore alla proprietà Fill dell’elemento Path, in questo caso Gold.
<Grid>
<Path Fill="Gold" Stroke="Black" StrokeThickness="1">
<Path.Data>
<EllipseGeometry Center="50,50" RadiusX="50" RadiusY="50" />
</Path.Data>
</Path>
</Grid>
Geometrie di percorso
La classe PathGeometry fornisce il mezzo per descrivere figure più complesse costituite
da archi, curve e linee.
I segmenti all’interno di una classe PathFigure sono combinati in una singola forma
geometrica che utilizza il punto finale di ciascun segmento come punto iniziale del
segmento successivo.
La proprietà StartPoint specifica il punto da cui è tracciato il primo segmento.
Ogni segmento successivo inizia dal punto finale di quello precedente, ad esempio una
linea verticale da 10, 50 a 10, 150 può essere definita impostando la proprietà StartPoint
su 10, 50 e creando un oggetto LineSegment con la proprietà Point impostata su 10,150.
La sintassi utilizzata per PathGeometry è molto più dettagliata di quella utilizzata per la
classe LineGeometry e consente aree geometriche complesse.
Esempio, crearea una classe PathGeometry semplice costituita da un singolo oggetto
PathFigure con un oggetto LineSegment, è visualizzata mediante un elemento Path.
Visual C#
um 156 di 202
L’oggetto StartPoint dell’oggetto PathFigure è impostato su 10, 20 e LineSegment è
definito con un punto finale di 100, 130.
<Grid>
<Path Stroke="Black" StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="10,20">
<PathFigure.Segments>
<LineSegment Point="100,130"/>
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
Esempio, utilizzare le classi BezierSegment, LineSegment e ArcSegment per creare una
curva di Bezier cubica definendo quattro punti, un punto iniziale che rappresenta il punto
finale del segmento precedente, un punto finale e due punti di controllo che si comportano
come magneti, ovvero attraggono determinate parti della linea che sarebbe altrimenti una
linea retta producendo una curva.
Il primo punto di controllo, Point1, influisce sulla parte iniziale della curva, mentre il
secondo punto di controllo, Point2, interessa la parte finale della curva.
Quindi aggiungere LineSegment che è tracciato tra il punto finale dell’oggetto
BezierSegment precedente e il punto specificato dalla proprietà LineSegment.
Quindi aggiungere ArcSegment che è tracciato dal punto finale dell’oggetto LineSegment
precedente al punto specificato dalla proprietà Point, inoltre, specificati i raggi x e y
dell’arco, un angolo di rotazione, un flag che indica l’ampiezza dell’angolo dell’arco
risultante e un valore che indica in quale direzione è tracciato l’arco.
<Grid>
<Path Stroke="Black" StrokeThickness="1" >
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="10,50">
<PathFigure.Segments>
<BezierSegment
Point1="100,0"
Point2="200,200"
Visual C#
um 157 di 202
Point3="300,100"/>
<LineSegment Point="400,100" />
<ArcSegment
Size="50,50" RotationAngle="45"
IsLargeArc="True" SweepDirection="Clockwise"
Point="200,100"/>
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
EFFETTI 2D
Sono effetti visivi, quali sfumature, bitmap, disegni, disegni con video, rotazione,
ridimensionamento e inclinazione.
Tutti gli elementi visibili sullo schermo possono essere visualizzati poiché sono stati
disegnati con un pennello, ad esempio per descrivere lo sfondo di un pulsante, il primo
piano di un testo e il riempimento di una forma.
I pennelli consentono di disegnare oggetti dell’UI utilizzando colori semplici e a tinta unita
ma anche set complessi di modelli e immagini.
Brush “disegna” un’area con un output specifico, tipi differenti di pennelli generano tipi di
output diversi, alcuni pennelli disegnano un’area con un colore a tinta unita, altri con una
sfumatura, un modello, un’immagine o un disegno.
Esempio, utilizzare DrawingBrush per disegnare l’oggetto Fill di un oggetto Rectangle.
<Grid>
<Rectangle Width="75" Height="75">
<Rectangle.Fill>
<DrawingBrush Viewport="0,0,0.25,0.25" TileMode="Tile">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="White">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,100,100" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
Visual C#
um 158 di 202
<GeometryDrawing>
<GeometryDrawing.Geometry>
<GeometryGroup>
<RectangleGeometry Rect="0,0,50,50" />
<RectangleGeometry Rect="50,50,50,50" />
</GeometryGroup>
</GeometryDrawing.Geometry>
<GeometryDrawing.Brush>
<LinearGradientBrush>
<GradientStop Offset="0.0" Color="Black" />
<GradientStop Offset="1.0" Color="Gray" />
</LinearGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
VisualBrush disegna un’area con Visual, Button, Page e MediaElement rappresentano
esempi di oggetti Visual, consente, inoltre, di proiettare il contenuto da una parte
dell’applicazione in un’altra area; questa funzione è molto utile per la creazione di effetti
reflection e per l’ingrandimento di parti dello schermo.
Esempio, utilizzare VisualBrush per disegnare l’oggetto Fill di un oggetto Rectangle.
<Grid>
<Rectangle Width="90" Height="90">
<Rectangle.Fill>
<VisualBrush TileMode="Tile">
<VisualBrush.Visual>
<StackPanel>
<StackPanel.Background>
<DrawingBrush>
<DrawingBrush.Drawing>
<GeometryDrawing>
<GeometryDrawing.Brush>
<RadialGradientBrush>
<GradientStop Color="MediumBlue" Offset="0.0" />
<GradientStop Color="White" Offset="1.0" />
</RadialGradientBrush>
</GeometryDrawing.Brush>
<GeometryDrawing.Geometry>
<GeometryGroup>
Visual C#
um 159 di 202
<RectangleGeometry Rect="0,0,50,50" />
<RectangleGeometry Rect="50,50,50,50" />
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</StackPanel.Background>
<TextBlock FontSize="12pt" Margin="10" FontFamily="Arial Black">Ciao,
mondo!</TextBlock>
</StackPanel>
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
TRASFORMAZIONI
È possibile applicare trasformazioni, come la scala non uniforme, la rotazione o la
traslazione, basta impostare la proprietà RenderTransform esposta da qualsiasi tipo erediti
direttamente o indirettamente dal UIElement.
Le trasformazioni applicate con RenderTransform non influenzano altri elementi se non
quello che imposta la proprietà.
La proprietà RenderTransform accetta una sola istanza del tipo Transform, impedendo, di
fatto, la possibilità di applicare una collezione di trasformazioni.
Per evitare questo problema, si usa il tipo TransformGroup che eredita dal tipo Transform,
applicando così più trasformazioni, aggiungendole alla collezione Children.
Prestare attenzione all’ordine nel quale sono dichiarate le trasformazioni all’interno della
collezione, in quanto l’ordine può produrre effetti differenti se, per esempio, prima di
ruotare un oggetto, si è precedentemente spostato.
Tutte le trasformazioni ereditano dal tipo Transform e ognuna di esse conferisce un effetto
differente all’elemento al quale è applicata.
 TranslateTransform: trasporta l’elemento al quale è applicata, specificando le
coordinate x e y con le proprietà X e Y.
 SkewTransform: applica una trasformazione di slittamento, impostando le proprietà
AngleX e AngleY; per esempio, un rettangolo cui è applicata una trasformazione di
questo tipo può diventare un trapezio.
 ScaleTransform: applica una trasformazione di scalatura, controllabile con le proprietà
ScaleX e ScaleY.
 RotateTransform: applica una trasformazione di rotazione, della quale è possibile
controllare l’angolo con la proprietà Angle e il centro con CenterX e CenterY.
La proprietà LayoutTransform modifica anche il layout degli elementi circostanti; per
esempio, se s’ingrandisce un elemento, quelli nelle vicinanze si sposteranno di
Visual C#
um 160 di 202
conseguenza.
Esempio, ruotare di 45 gradi un’immagine.
<Canvas Height="480" Width="640">
<Image Canvas.Top="72" Opacity="0.5" Source="immagini/freccia.gif"/>
<Image Canvas.Top="35" Source="immagini/freccia.gif" Canvas.Left="35">
<Image.RenderTransform>
<RotateTransform Angle="45" CenterX=" 40.5"/>
</Image.RenderTransform>
</Image>
</Canvas>
Il controllo Image dispone della proprietà OpacityBrush con la quale è possibile applicare
effetti di trasparenza a precise porzioni.
Esempio, applicare ogni volta una trasformazione differente ad un rettangolo.
<StackPanel Margin="0,38,0,114" >
<Rectangle Fill=" #FFFE1F1F" Width="50 " Height=" 50">
<Rectangle.RenderTransform>
<RotateTransform Angle=" -42"/>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Fill=" #FFFE1F1F" Width="50 " Height=" 50" Margin="291,0,287,0" >
<Rectangle.RenderTransform>
<ScaleTransform ScaleX=" 0.6"/>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Fill=" #FFFE1F1F" Width="50 " Height=" 50" Grid.Row =" 1" Grid.Column
=" 1">
<Rectangle.RenderTransform>
<TranslateTransform X=" 42" Y =" 35"/>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Fill=" #FFFE1F1F" Width="50 " Height=" 50" Margin ="291,41,287,41">
<Rectangle.RenderTransform>
<SkewTransform AngleX=" -31"/>
</Rectangle.RenderTransform>
</Rectangle>
</StackPanel>
Visual C#
um 161 di 202
ANIMAZIONI
Alla base dell’animazione ci sono due aspetti fondamentali.
1. Il tempo.
2. La variazione di una proprietà nel tempo.
È possibile integrare le applicazioni con le animazioni predefinite, con i seguenti vantaggi:
le animazioni sono allineate allo stile di Windows 8.x, sono rapide e non invadenti.
Questi tipi di animazioni sono già inclusi nei controlli ListView, FlipView, Flayout e AppBar.
XAML
Si animano gli elementi dell’UI alterandone le proprietà, per esempio modificando la
proprietà Opacity per far apparire dal nulla un oggetto.
Sono necessari i seguenti requisiti: la proprietà dev’essere una DependencyProperty
mentre l’oggetto che la espone deve ereditare da DependencyObject.
Le classi per l’animazione sono divise in due gruppi.
1. <Type>Animation
2. <Type>AnimationUsingKeyFrames
Dove <Type> rappresenta il tipo della proprietà da animare.
Se, ad esempio, si volesse applicare un’animazione in modo che sia modificata la
proprietà Width di un pulsante che è di tipo Double, si utilizza DoubleAnimation.
Una serie di oggetti Animation sono in grado di variare ogni tipo managed in funzione della
durata, delle accelerazioni o decelerazioni, su un preciso evento o all’infinito.
Si tratta di classi che mutano i tipi Double, Integer, String, Color, Matrix, Point e Vector.
L’animazione dev’essere racchiusa in una timeline che rappresenta un intervallo di tempo,
all’interno del quale si possono animare contemporaneamente più proprietà e affinché
questo sia possibile si devono racchiudere le animazioni in un contenitore di
timelines/animazioni rappresentato dall’oggetto StoryBoard che le coordina e che si avvia
o si ferma in funzione della TriggerAction alla quale appartiene.
La caratteristica principale di Storyboard è di essere time based: ogni animazione è
definita per durata e per intervalli di tempo e indipendentemente dalle capacità del PC,
ogni animazione è portata a termine nel tempo impostato, semplificando la
sincronizzazione di più animazioni.
Una volta definita l’animazione va specificato come questa deve partire, ecco perché le
animazioni sono normalmente contenute all'interno di un EventTrigger anche se possono
Visual C#
um 162 di 202
essere contenute anche in uno Style.
Negli Style la proprietà Triggers contiene un oggetto Trigger per dipendere da una
proprietà ma come sia anche disponibile un EventTrigger che si aggancia ad un evento e
può contenere i TriggerAction che avviano, sospendono, fermano una Storyboard.
Esempio, variare la dimensione di un pulsante quando ci si posiziona sopra con il mouse.
Si deve intercettare l’evento MouseEnter, avviare un’animazione che ne modifichi la
proprietà Width del Button.
<Viewbox>
<Canvas Height="480" Width="640">
<StackPanel Canvas.Left="261" Canvas.Top="10">
<TextBlock HorizontalAlignment="Center">Esempio di animazione</TextBlock>
<Button Content="Cliccami" FontSize="20" HorizontalAlignment="Center"
Canvas.Left="100" Canvas.Top="50" Width="120">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Button.Width)"
AutoReverse="true" RepeatBehavior="Forever" To="160" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Button.MouseLeave">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Button.Width)"
To="200" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
</StackPanel>
</Canvas>
</Viewbox>
L’oggetto DoubleAnimation varia la proprietà Width del pulsante, indicata tramite l’attributo
TargetProperty.
Tramite l’attributo To s’indica il punto finale dell’animazione, mentre con Duration la sua
durata e con AutoReverse s’inverte la marcia una volta arrivati a 200.
RepeatBehavior fa sì che l’animazione si ripeta all’infinito.
Visual C#
um 163 di 202
Gestione del tempo
Le animazioni sono basate sul tempo, ogni proprietà o metodo che lavora con tale misura
utilizza oggetti di tipo TimeSpan.
Vista l’importanza, il plugin contiene molti strumenti per la conversione dei dati numerici
verso un valore temporale, per esempio TimeSpan.FromMilliseconds converte un Double
nei corrispondi millisecondi.
Animazioni non lineari
Ci sono oggetti che permettono la realizzazione di animazioni non lineari.
In esse il valore della proprietà in animazione cambia il proprio stato seguendo un
andamento non rettilineo e personalizzabile con i vari oggetti di tipo IEasingFunction.
Blend
Inserire l’oggetto da animare, per esempio un pulsante.
Creare una Nuova… Storyboard dal pannello Oggetti e sequenza temporale.
Posizionare la testina di riproduzione (la riga gialla) e cambiare i valori delle proprietà che
si vogliono animare.
Trascinare il pulsante sulla finestra, Blend registrerà gli spostamenti per l’animazione.
Trascinare il rombo sulla timeline.
Le animazioni possono essere lineari, in altre parole con un andamento costante nel
tempo, variando una proprietà solo da un valore all’altro ma possono usare anche
keyframe, ovvero fotogrammi chiave nei quali una determinata proprietà deve raggiungere
un determinato valore.
Visual C#
um 164 di 202
GRAFICA 3D
INTRODUZIONE
È lo studio e il metodo di proiezione di una rappresentazione matematica di oggetti 3D
tramite un’immagine 2D, attraverso l’uso di tecniche come la prospettiva e
l’ombreggiatura per simulare la percezione di questi oggetti da parte dell’occhio umano.
Ogni sistema 3D deve fornire due elementi.
1. Un metodo di descrizione del sistema 3D stesso, scena: è l’insieme degli oggetti, detti
modelli e nella maggior parte dei casi si tratta di mesh che si desidera visualizzare e
include sia gli oggetti veri e propri sia le luci.
2. Un meccanismo di produzione di un’immagine 2D dalla scena, detto renderer.
In pratica, lo scopo della grafica 3D è quello di riuscire a rappresentare un modello
tridimensionale su uno schermo di PC che è bidimensionale.
In grafica 3D sono utilizzate le coordinate cartesiane tridimensionali, con gli assi x, y, z.
Il sistema rappresentato è una generalizzazione del piano cartesiano a due dimensioni, è
formato da tre rette orientate perpendicolari tra loro e incidenti in un punto O, chiamato
origine degli assi che è il centro dell’area di rendering.
I tre assi x, y e z identificano tre piani nello spazio xy, xz e yz che dividono lo spazio in otto
ottanti, concetto analogo ai quattro quadranti formati dagli assi cartesiani bidimensionali.
Ogni punto nelle spazio 3D è identificato da tre coordinate che rappresentano ognuna la
distanza del punto dal piano formato dagli altri due.
I valori positivi dell’asse x crescono verso destra, i valori positivi dell’asse y crescono verso
l’alto e i valori positivi dell’asse z crescono verso l’esterno dell’origine, in direzione
dell’osservatore.
Lo spazio definito da questi assi è il frame di riferimento fisso per oggetti 3D.
PUNTI
L’oggetto fondamentale di una scena 3D è il punto.
Tramite i punti è possibile definire qualsiasi oggetto di una scena 3D.
L’unione di più punti, infatti, permette di creare oggetti primitivi che, uniti a loro volta,
danno vita a modelli più sofisticati.
Visual C#
um 165 di 202
In Visual C# la struttura Point3D permette di creare e memorizzare le coordinate x, y e z.
Point3D point = new Point3D(1.2, 3.4, -5.6);
Point3D origine = new Point3D(0, 0, 0);
In XAML, è possibile rappresentate un Point3D con una stringa contenente i tre valori delle
coordinate separate da virgola o da uno spazio.
"2.33, 1.5, -2"
"0 0 0"
Per rappresentare complessi oggetti 3D, formati da un insieme di Point3D, si usa un
oggetto della classe Point3Dcollection, è possibile aggiungere punti mediante il metodo
Add e rimuoverli con Remove o RemoveAt o ancora accedere ai punti mediante la
notazione [].
Point3DCollection coll = new Point3DCollection();
coll.Add(new Point3D(1, 1.5, 2.0));
coll.Add(new Point3D(0, 0, 0));
Point3D pt1 = coll[0];
In XAML è possibile definire una collezione di punti 3D elencandoli nel modo seguente.
"2.55 1.5 -2, 0 2.5 7, 1 1 -3"
La struttura Size3D rappresenta le dimensioni di un oggetto tridimensionale, la struttura
Rect3D rappresenta un rettangolo tridimensionale, quindi un cubo oppure un
parallelepipedo.
Un Rect3D è costruito a partire da un punto che rappresenta la posizione nello spazio e
dalle sue dimensioni.
Point3D origine = new Point3D(0, 0, 0);
Size3D size = new Size3D(5, 5, 5);
Rect3D cubo = new Rect3D(origine, size);
TRIANGOLI
Nella programmazione grafica 3D non esistono linee, rettangoli, ellissi o curve di Bezier.
Le superfici di un qualsiasi oggetto sono formate da insiemi di poligoni, a loro volta
risultanti dall’unione di punti nello spazio.
La rappresentazione di tale superficie è chiamata mesh: è una “maglia” poligonale, in altre
parole una superficie che si estende nelle tre dimensioni, costituita da un insieme di
poligoni messi insieme.
Figure con superfici piatte sono rappresentabili con pochi poligoni, quando invece si cerca
di formare una superficie con curvature, per approssimarle si ha bisogno di un alto numero
di poligoni e ciò implica una proporzionale potenza di calcolo richiesta al PC.
Il poligono più semplice è il triangolo, quindi è il poligono più utilizzato per la
rappresentazione di oggetti tridimensionali.
Ogni singolo triangolo definisce sempre una superficie piatta, tuttavia insiemi di triangoli
opportunamente predisposti possono rappresentare un oggetto solido e perfino tracciare
una superficie curva come anche delle sfere.
Per esempio, per creare un quadrato occorrono due triangoli ma per ogni triangolo
Visual C#
um 166 di 202
servono tre punti su un piano non coincidenti, quindi in tutto sono necessari sei punti ma,
di fatto, i punti distinti sono solamente quattro.
Per esempio, per creare un cubo occorrono sei quadrati uno per ogni faccia.
L’ordine di aggiunta dei punti è molto importante, poiché permette di determinare quali
sono le superfici visibili, create in senso antiorario, fronte e quali sono quelle nascoste,
create in senso orario, retro.
Se si pensa ad un triangolo come ad un foglio di carta con scritto davanti “Fronte” e dietro
“Retro” ordinando i punti in senso antiorario sarà visibile all’osservatore la faccia “Fronte” e
nascosta quella con su scritto “Retro”.
Viceversa creando il triangolo in senso orario, la faccia con la scritta “Retro” è quella
frontale e quella con la scritta “Fronte” è quella sul retro.
Esempio, costruire, posizionare e osservare un triangolo nel mondo 3D.
Occorre specificare i tre vertici, impostando i tre Point3D, uno per ogni vertice, all’interno
della collezione Positions, i tre punti A, B, C sono descritti nel modo seguente.
<MeshGeometry3D Positions="0 3 0, 0 0 -3, 0 0 3">
Le dimensioni non hanno importanza assoluta, dato che la grandezza degli oggetti
dipenderà dalla distanza del punto di posizionamento della Camera, è la videocamera
virtuale attraverso la quale l’utente vede la scena.
Un’altra proprietà di MeshGeometry3D permette di definire l’indice dei punti del triangolo.
La proprietà TriangleIndices, indica l’ordine di costruzione del triangolo in senso iorario,
visto di fronte, in funzione della posizione assunta in Positions.
Con l’ordine in senso antiorario si definiscono iI lati visibili del triangolo.
Per esempio, l’ordine ABC indicherà che il fronte è quello rosso, mentre indicando gli indici
come BAC si vede come fronte il lato opposto, quello nascosto.
<MeshGeometry3D Positions="0 3 0, 0 0 -3, 0 0 3" TriangleIndices="0 1 2" />
Visual C#
um 167 di 202
La collezione TriangleIndices definisce il primo, secondo e terzo punto della collezione
Positions.
Come si fa a distinguere il retro del triangolo dal suo fronte?
Basta colorare le due facce in maniera differente, mediante l’oggetto Material.
È una classe astratta, si divide in DiffuseMaterial che permette di colorare un oggetto con
un colore “a tinta unita”, la proprietà della classe è Brush; EmissiveMateriale e
SpecularMaterial.
Il tipo di materiale incide sul calcolo della luce.
Una figura 3D può essere “ricoperta” con una sorta di superficie colorata, riempita con
colori sfumati, disegni, immagini, foto o anche video.
Applicare una semplice colorazione per il fronte Blue e per il retro Red.
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<DiffuseMaterial Brush="Blue"/>
</GeometryModel3D.Material>
<GeometryModel3D.BackMaterial>
<DiffuseMaterial Brush="Red"/>
</GeometryModel3D.BackMaterial>
L’oggetto GeometryModel3D contiene due oggetti: Material, materiale che costituisce il
davanti dell’oggetto e BackMaterial per il retro.
Per esempio, nel cubo le superfici interne restano invisibili e quindi, per tracciare il retro
dei triangoli, si può omettere l’impiego della proprietà Back Material con vantaggi in termini
di prestazioni.
SCENA
Per visualizzare una scena 3D occorre un contenitore.
WPF fornisce l’oggetto Viewport3D che definisce la superficie di disegno e contiene tutti gli
elementi visuali.
Può far parte di una struttura più complessa di definizione del layout di un’applicazione e,
inoltre, servirà anche a trattare l’input di mouse e tastiera.
Il sistema grafico considera Viewport3D come un elemento visivo bidimensionale e
funziona come una finestra o un riquadro di visualizzazione, in una scena 3D.
Più precisamente, si tratta di una superficie sulla quale è proiettata una scena 3D.
Benché sia possibile utilizzare Viewport3D con altri oggetti disegno 2D nello stesso
grafico, non è possibile inserire oggetti 2D e 3D all’interno di un oggetto Viewport3D.
Definisce la proprietà Children, di tipo Visual3DCollection che rappresenta una collezione
di oggetti Visual3D, in pratica di oggetti tridimensionali che saranno rappresentati sulla
scena 3D.
Visual3D è una classe astratta, con un’unica classe derivata, ModelVisual3D.
Questa classe rappresenta quindi l’oggetto da rappresentare che però contiene un
modello, in altre parole la descrizione di ciò che dovrà essere disegnato.
In particolare, tale modello sarà definito all’interno della proprietà Content, di tipo
Model3D.
Anche la classe Model3D è astratta e ha due figli.
1. GeometryModel3D è un modello 3D formato dalla combinazione di un oggetto
MeshGeometry3D e un oggetto Material che rappresenta il materiale di cui è composto
l’oggetto e quindi lo si utilizza per definirne il colore.
2. Light da cui derivano le classi che rappresentano le diverse sorgenti di luci con cui
illuminare gli oggetti.
Geometry3D è una classe astratta di base per definire figure geometriche.
Visual C#
um 168 di 202
Esempio, struttura generica di un ViewPort3D e degli oggetti in esso contenuti.
<Viewport3D>
<ModelVisual3D>
<ModelVisual3D.Content>
<GeometryModel3D>
...
</GeometryModel3D>
</ModelVisual3D.Content>
</ModelVisual3D>
<ModelVisual3D>
<ModelVisual3D.Content>
<AmbientLight ... />
</ModelVisual3D.Content>
</ModelVisual3D>
<Viewport3D.Camera>
<PerspectiveCamera ... />
</Viewport3D.Camera>
</Viewport3D>
Creata la scena che può essere vista a tutti gli effetti come una rappresentazione teatrale,
non resta che riempirla di “attori” che in WPF saranno modelli 3D.
Gli attori in genere saranno vestiti e indosseranno degli abiti di scena.
In WPF gli abiti prendono il nome di texture, il materiale serve a determinare l’aspetto che
un preciso oggetto avrà tenendo conto anche della riflessione della luce.
Inoltre, ci vorrà un pubblico che seduto sulle poltrone, osservi la rappresentazione teatrale
che sarà percepita diversamente dagli spettatori in base alla posizione e alla direzione
dalla quale guardano.
In WPF il pubblico si traduce in Camera, telecamere virtuali che osservano la scena da
una posizione precisa, proprietà Position e verso una direzione ben definita, proprietà
LookDirection.
Come in una rappresentazione teatrale, quando inizia lo spettacolo si devono accendere
le luci di scena: una o più fonti per illuminare gli attori e la scenografia.
LUCI
Il triangolo è già posizionato nel mondo 3D ma non è possibile ancora vederlo: è al buio.
Una scena 3D richiede che ci sia una fonte di luce che la inquadri, altrimenti, anche se le
figure all’interno della scena fossero visibili, non saremmo in grado di distinguere i colori
ma vedremmo solo una figura scura o nera su uno sfondo bianco.
WPF permette d’inserire in una scena diverse fonti di luce, come se fossero dei fari o delle
lampadine di diversa intensità o colore.
Gli oggetti luce creano una varietà di luci e di effetti di ombreggiatura e sono modellati in
base al comportamento di varie luci reali.
È necessario includere almeno una luce nella scena, altrimenti nessun modello sarà
visibile; le luci derivano dalla classe Light.
AmbientLight
È una luce molto naturale che permette di ottenere un’illuminazione uniforme
indipendentemente dalla posizione o dall’orientamento e, proprio per questa sua
caratteristica è la luce più performante e più utilizzata.
Visual C#
um 169 di 202
DirectionalLight
È una luce diretta come la luce solare e serve ad illuminare lungo una direzione
specificata da una struttura Vector3D, ha una proprietà Direction ma nessuna posizione.
PointLight
Rappresenta una sorgente di luce che ha una posizione specificata nello spazio e proietta
la luce in tutte le direzioni, illumina come una sorgente di luce vicina.
Gli oggetti hanno una posizione dalla quale proiettano la luce e nella scena sono illuminati
a seconda della posizione e della distanza rispetto alla luce.
PointLightBase espone una proprietà Range che determina una distanza oltre la quale i
modelli non saranno illuminati dalla luce, espone anche proprietà di attenuazione che
determinano la modalità in cui l’intensità della luce diminuisce a distanza.
È possibile specificare interpolazioni costanti, lineari o quadratiche per l’attenuazione della
luce.
SpotLight
Eredita da PointLight, gli oggetti Spotlight illuminano come gli oggetti PointLight e
dispongono di posizione e direzione, proiettano luce in un’area a forma di cono impostata
dalle proprietà InnerConeAngle e OuterConeAngle, specificate in gradi.
Le luci sono oggetti Model3D, pertanto è possibile trasformarne e animarne le proprietà,
tra cui posizione, colore, direzione e intervallo.
Aggiungere una luce uniforme e colorata di bianco, si usa la classe AmbientLight,
impostando la proprietà Color.
<AmbientLight Color="White" />
VIDEOCAMERA
La scena è pronta, bisogna istanziare una videocamera e osservare la scena.
Quando si compilano modelli in questo spazio e si creano luci e fotocamere per
visualizzarli, è consigliabile distinguere il frame di riferimento fisso o spazio globale, dal
frame di riferimento locale creato per ciascun modello quando vi si applicano
trasformazioni.
Gli oggetti dello spazio globale possono avere un aspetto completamente diverso o non
essere visibile a tutti, a seconda delle impostazioni di luce e fotocamera ma la posizione
della fotocamera non modifica la posizione degli oggetti nello spazio globale.
I programmatori che lavorano in 2D posizionano le primitive del disegno su uno schermo
bidimensionale.
Nella creazione di una scena 3D, è importante tenere presente che in realtà si sta creando
una rappresentazione 2D di oggetti 3D.
Poiché una scena 3D ha un aspetto diverso a seconda del punto di vista dello spettatore,
è necessario specificare tale punto di vista.
La classe Viewport3D definisce la proprietà Camera che consente di specificare il punto di
di osservazione utilizzato per mappare la scena 3D in una visualizzazione 2D.
Per comprendere la modalità di rappresentazione di una scena 3D su una superficie 2D è,
inoltre, possibile descrivere la scena come proiezione sulla superficie di visualizzazione.
ProjectionCamera consente di specificare proiezioni diverse e le relative proprietà per
modificare la modalità di visualizzazione dei modelli 3D da parte dello spettatore.
Un oggetto PerspectiveCamera specifica una proiezione che rappresenta uno scorcio
della scena, è una telecamera che inquadra gli oggetti come l’occhio umano: in
prospettiva, più lontano è l’oggetto e più piccolo apparira nello schermo.
In altre parole, PerspectiveCamera offre la prospettiva del punto di fuga.
Visual C#
um 170 di 202
È possibile specificare la posizione della fotocamera nello spazio delle coordinate della
scena, la direzione e il campo visivo per la fotocamera e un vettore che definisce la
direzione verso l’alto nella scena.
Esempio, di proiezione dell’oggetto PerspectiveCamera.
Le proprietà NearPlaneDistance e FarPlaneDistance di ProjectionCamera limitano
l’intervallo della proiezione della fotocamera.
Poiché le fotocamere si possono trovare in qualunque punto della scena, è possibile che
la fotocamera sia posizionata all’interno di un modello o accanto a esso, rendendo difficile
distinguere correttamente gli oggetti.
NearPlaneDistance consente di specificare una distanza minima dalla fotocamera oltre la
quale non saranno disegnati oggetti.
FarPlaneDistance consente di specificare una distanza dalla fotocamera oltre la quale non
saranno disegnati oggetti per garantire che non siano inclusi nella scena oggetti troppo
lontani per essere riconoscibili.
OrthographicCamera specifica una proiezione ortogonale di un modello 3D su una
superficie visiva 2D, analogamente ad altre fotocamere, specifica una posizione, una
direzione di visualizzazione e una direzione verso l’alto.
A differenza di PerspectiveCamera, descrive una proiezione che non include lo scorcio
prospettico, in altri termini descrive un riquadro di visualizzazione con i lati paralleli,
anziché un riquadro i cui lati s’incontrano in un punto in corrispondenza della fotocamera.
Esempio, lo stesso modello visualizzato con PerspectiveCamera e OrthographicCamera.
Utilizzare PerspectiveCamera, per posizionare la telecamera in un punto dello spazio si
usa la proprietà Position che è di tipo Point3D nel punto "5 1 0", così s’inquadra il triangolo
da dietro e dovrà apparire rosso, in altre parole mostra la posizione in cui si trova
l’osservatore.
La posizione non è sufficiente, perché la telecamera dovrà puntare verso il triangolo, in
altre parole è necessario indicare la direzione verso cui sta guardando l’osservatore, verso
quale direzione puntare l’obiettivo.
Visual C#
um 171 di 202
La proprietà da usare è LookDirection che è un oggetto di tipo Vector3D.
Per puntare verso le ascisse negative e parallelamente all’asse x, basterà quindi
impostare come valore "-1 0 0".
Non è necessario “normalizzare” il vettore, in quanto sarà considerata solo la direzione e
non le grandezze.
Scrivere in pratica "-1 0 0" oppure "-100 0 0" avrà lo stesso identico effetto.
Un’altra impostazione permette di definire l’orientamento della telecamera, mediante la
proprietà UpDirection che è ancora di tipo Vector3D.
Il valore di default è "0 1 0", il che significa che l’alto della telecamera è verso i valori
crescenti dell’asse y.
La quarta proprietà dell’oggetto PerspectiveCamera si chiama FieldOfView imposta un
valore che rappresenta il campo visivo orizzontale della telecamera, variabile tra 0 e 180,
in pratica è l’angolo in gradi che la telecamera riesce a inquadrare
Il valore di default è 45 gradi ma con questo valore, data la vicinanza dal triangolo, non si
riesce a inquadrare l’oggetto intero, bisogna aumentare quindi il valore a 90.
<Viewport3D.Camera>
<PerspectiveCamera Position="5 1 0"
LookDirection="-1 0 0"
UpDirection="0,1,0"
FieldOfView="90" />
</Viewport3D.Camera>
Compilare ed eseguire l’applicazione.
Model3D è la classe base astratta che rappresenta un oggetto 3D generico.
Per compilare una scena 3D sono necessari alcuni oggetti da visualizzare e gli oggetti che
compongono il grafico della scena derivano da Model3D.
WPF supporta le geometrie di modellazione con GeometryModel3D, la proprietà
Geometry di questo modello accetta una primitiva mesh.
Per compilare un modello, iniziare dalla compilazione di una primitiva o mesh.
Una primitiva 3D è un insieme di vertici che costituiscono una sola entità 3D.
La maggior parte dei sistemi 3D offre primitive modellate sulla figura chiusa più semplice,
ovvero un triangolo definito da tre vertici.
Poiché i tre punti di un triangolo sono complanari, è possibile continuare ad aggiungere
triangoli per modellare forme più complesse, dette mesh.
Nel sistema WPF è disponibile la classe MeshGeometry3D che consente di specificare
qualsiasi geometria.
Nell'elenco Positions sono specificati otto vertici per definire una mesh a forma di cubo.
Per eseguire il rendering della superficie del modello, è necessario che il sistema grafico
disponga delle informazioni per stabilire in quale direzione è rivolta la superficie in
corrispondenza di tutti i triangoli specificati.
Tali informazioni sono utilizzate dal sistema per eseguire calcoli d’illuminazione per il
modello, con le superfici rivolte direttamente verso una sorgente di luce più brillanti rispetto
a quelle angolate rispetto alla luce.
Visual C#
um 172 di 202
La proprietà TextureCoordinates specifica un insieme di Point 2D che mappano a
posizioni 3D, in pratica fornisce al sistema grafico la modalità di mapping delle coordinate
che determinano in che modo è disegnata una trama sui vertici della mesh.
Le coordinate rispecchiano il sistema di riferimento 2D della grafica raster in cui l’origine
coincide con l’angolo in alto a sinistra.
TextureCoordinates è specificata come valore compreso tra 0 e 1, inclusi.
Analogamente a quanto avviene con la proprietà Normals, le coordinate di trama
predefinite possono essere calcolate dal sistema grafico ma è possibile decidere di
impostare coordinate di trama diverse per controllare, ad esempio, il mapping di una trama
che include parte di un pattern ripetitivo.
Affinché una mesh assuma l’aspetto di oggetto tridimensionale, è necessario che ad essa
sia applicata una trama per coprire la superficie definita dai relativi vertici e triangoli, in
modo che possa essere illuminata e proiettata dalla fotocamera.
In 2D, utilizzare la classe Brush per applicare colori, modelli, sfumature o altro contenuto
visivo alle aree dello schermo.
L’aspetto degli oggetti 3D, tuttavia, è una funzione del modello d’illuminazione, non solo
del colore o del pattern applicato.
Gli oggetti reali riflettono la luce in modo diverso a seconda della qualità delle superfici.
Le superfici lucide e brillanti non hanno, infatti, lo stesso aspetto delle superfici grezze o
opache.
Alcuni oggetti, inoltre, sembrano assorbire la luce, mentre altri la riflettono.
È possibile applicare agli oggetti 3D gli stessi pennelli applicabili agli oggetti 2D ma non
direttamente.
PENNELLI
La grafica 3D e la grafica 2D si sovrappongono nell’area dei pennelli, infatti, è sempre
coperta la superficie di una visuale 3D con un pennello 2D.
Il pennello più semplice da utilizzare è un pennello a colore solido ma è possibili impiegare
anche pennelli composti così come anche immagini.
L’uso di pennelli composti pone il problema di mappare le immagini 2D, le texture, sulle
superfici 3D.
Un pennello permette di tracciare il colore di background, dei bordi e dei primi piani di
elementi grafici.
Per definire le caratteristiche di superficie di un modello, è utilizzata la classe astratta
Material, le sottoclassi concrete determinano alcune delle caratteristiche di aspetto della
superficie del modello e ognuna fornisce anche una proprietà Brush alla quale è possibile
passare I seguenti oggetti.
SolidColorBrush
Permette di dipingere con un colore a tinta unita.
LinearGradientBrush
Permette un cambio graduale di colore in un’area con una sfumatura lineare.
RadialGradientBrush
Disegna un’area con una sfumatura radiale, un punto focale definisce l’inizio della
sfumatura e un cerchio ne descrive il punto finale.
DrawingBrush
Consente di definire un disegno specifico che è tracciato dal pennello attraverso un
elemento GeometryDrawing che può includere forme, testo, video, immagini o altri disegni.
Visual C#
um 173 di 202
ImageBrush
Carica un’immagine in un pennello, con questo elemento è visualizzata l’immagine definita
dalla proprietà ImageSource.
VisualBrush
Consente di utilizzare altri elementi in un pennello, per esempio è possibile aggiungere
anche testo e altri controlli in modo similare ai materiali.
DiffuseMaterial
Specifica che il pennello è applicato al modello come se tale modello fosse illuminato in
modo diffuso, le superfici dei modelli non riflettono luce come se fossero brillanti.
SpecularMaterial
Specifica che il pennello è applicato al modello come se la superficie del modello fosse
dura o brillante, in grado di riflettere le evidenziazioni, è possibile impostare il grado di
qualità riflettente o luminosità, della trama specificando un valore per la proprietà
SpecularPower.
EmissiveMaterial
Consente di specificare che la trama è applicata come se il modello emettesse una luce
uguale al colore del pennello, in questo modo, il modello non è trasformato in luce ma
partecipa in modo diverso allo shadowing rispetto a quanto accade nel caso di trama
applicata con DiffuseMaterial o SpecularMaterial.
Per prestazioni ottimali, le facce posteriori di un oggetto GeometryModel3D, ovvero le
facce esterne alla visualizzazione poiché si trovano sul lato opposto del modello rispetto
alla fotocamera, sono rimosse dalla scena.
Per specificare un oggetto Material da applicare alla faccia posteriore di un modello come
un piano, impostare la proprietà BackMaterial del modello.
Per ottenere alcune qualità della superficie, come l’effetto alone o riflettente, è necessario
applicare in successione ad un modello più pennelli diversi.
È possibile applicare e riutilizzare più materiali tramite la classe MaterialGroup, gli
elementi figlio sono applicati dal primo all’ultimo in più passaggi di rendering.
ANIMAZIONI
Quando gli oggetti sono creati nello spazio 3D, assumono nella scena una ben
determinata posizione in funzione dei parametri d’inizializzazione.
Le trasformazioni consentono di riposizionare, ridimensionare e orientare nuovamente gli
oggetti senza modificare i valori di base che li definiscono.
 Trasformazioni di rotazione.
 Trasformazioni di traslazione.
 Trasformazioni di scala.
Le trasformazioni 3D ereditano dalla classe base astratta Transform3D e includono le
classi di trasformazione seguenti.
RotateTransform3D
Consente di ruotare l’oggetto definito da un angolo nelle direzioni x, y e z.
TranslateTransform3D
Consente di spostare tutti i punti nel Model3D in direzione del vettore offset che è
specificato con le proprietà OffsetX, OffsetY e OffsetZ.
Visual C#
um 174 di 202
ScaleTransform3D
Consente di modificare la grandezza dell’oggetto con un vettore di ridimensionamento con
riferimento ad un punto centrale.
MatrixTransform3D
Transform3Dgroup
Consente di combinare più trasformazioni in una sola.
Per gestire le animazioni tramite code behind, ogni oggetto di trasformazione avrà
associato un nome tramite l’attributo x:Name specificato.
Le animazioni sono regolate da un timer inizializzato nel costruttore della window, grazie
alla classe Timer: System.Windows.Threading.DispatcherTimer.
La classe DispatcherTimer non fa altro che eseguire un metodo allo scadere di un
intervallo di tempo, come la classe Timer, con la differenza che questa implementazione
utilizza l’oggetto System.Windows.Threading.Dispatcher che permette di gestire una coda
di operazioni con priorità per un singolo thread, molto utile quando si deve accedere da un
thread secondario a quello che gestisce l’interfaccia utente.
Usando il costruttore di default è utilizzato il dispatcher di default con priorità normale.
Con le animazioni è possibile realizzare una transizione morbida, utilizzando elementi che
si muovono, cambi di colore e trasformazioni.
È possibile animare qualsiasi valore di una proprietà dipendente, il cambiamento nel corso
del tempo è definito da una timeline.
Per animare una proprietà double, come ad esempio la posizione lungo l’asse x, si può
usare la classe DoubleAmination.
Le proprietà più importanti che è possibile impostare in una timeline sono le seguenti.
AutoReverse
Permette alla proprietà animata di ritornare automaticamente al valore iniziale al termine
della stessa.
AccellerationRatio/DecellerationRatio
In un’animazione è possibile modificare i valori in modo non lineare, per definire l’impatto
dell’accelerazione e della decelerazione sul movimento.
RepeatBehavior
Permette di definire quante volte e quanto a lungo ripetere l’animazione.
In funzione del tipo della timeline possono essere definiti anche i parametri From/To per
specificare i valori di partenza e di fine dell’animazione.
Visual C#
um 175 di 202
GUIDA IN LINEA
ESEMPIO
Il .NET Framework fornisce le classi necessarie ad implementare sistemi di guida per le
applicazioni Windows.
Scrivere la documentazione e i file della guida di un’applicazione è spesso un compito
sottovalutato o addirittura tralasciato.
Esistono diverse tecniche per aggiungere una funzionalità di guida ad un’applicazione, la
maggior parte sono diventate uno standard de facto sia nel mondo Windows sia Linux.
Ad esempio, ogni applicazione ha un menu help ?, il tasto F1 attiva un help contestuale
sulla funzione che si sta utilizzando, fumetti o tooltip che appaiono posizionando il mouse
su un controllo e spiegando la sua funzione.
Progettare un’applicazione Windows costituita da una singola finestra e da qualche
controllo, per esempio pulsanti e caselle di testo, per inserire i dati personali.
Tooltip
L’utente si chiede, ad esempio, cosa serve il pulsante Carica, utilizzando un tooltip
quando si posiziona il puntatore del mouse su di esso si può far apparire una spiegazione.
Fare clic sulla Casella degli strumenti e trascinare il componente ToolTip sul form per
fare in modo che ogni controllo sia dotato di una nuova proprietà, Tooltip on Nometooltip.
Impostando una stringa essa sarà utilizzata come ToolTip per il controllo.
La stessa cosa si può ottenere via codice, per creare un’istanza del ToolTip è necessario
invocare il costruttore di default e poi utilizzare il metodo SetTooltip specificando il
controllo e la stringa da mostrare.
Visual C#
um 176 di 202
ToolTip mioTooltip = new ToolTip();
mioTooltip.SetToolTip(btCarica, "Carica la foto di questo cliente");
mioTooltip.SetToolTip(btClose, "Chiudi la finestra");
Una sola istanza di ToolTip può essere utilizzata con tutti i controlli presenti sul form.
Posizionando il puntatore del mouse sul pulsante si ottiene la seguente spiegazione.
Barra di stato
Un altro sistema per mostrare informazioni su ciò che sta accadendo su una finestra
dell’applicazione, è quello di mostrare una stringa sulla barra di stato che varia ad esempio
spostando il focus da un controllo all’altro, mostrando in tempo reale una descrizione
dell’informazione data dal controllo.
Fare clic sulla Casella degli strumenti e trascinare il componente StatusStrip sul form.
StatusStrip crea una ToolStripStatusLabel al suo interno, sarà necessario gestire l’evento
MouseMove dei controlli interessati e dell’intero form, impostando la proprietà Text della
ToolStripStatusLabel.
private void txtNome_MouseMove(object sender, MouseEventArgs e)
{ this.toolStripStatusLabel.Text = "Il nome del cliente"; }
private void txtCognome_MouseMove(object sender, MouseEventArgs e)
{ this.toolStripStatusLabel.Text = "Il cognome del cliente"; }
private void dateTimePicker1_MouseMove(object sender, MouseEventArgs e)
{ this.toolStripStatusLabel.Text = "La data di nascita del cliente"; }
private void listBox1_MouseMove(object sender, MouseEventArgs e)
{ this.toolStripStatusLabel.Text = "Il titolo di studio del cliente"; }
private void txtIndirizzo_MouseMove(object sender, MouseEventArgs e)
{ this.toolStripStatusLabel.Text = "L’indirizzo del cliente"; }
private void txtCittà_MouseMove(object sender, MouseEventArgs e)
{ this.toolStripStatusLabel.Text = "La città di resdienza del cliente"; }
private void cboCAP_MouseMove(object sender, MouseEventArgs e)
{ this.toolStripStatusLabel.Text = "Il CAP di residenza del cliente"; }
private void pictureBoxFoto_MouseMove(object sender, MouseEventArgs e)
{ this.toolStripStatusLabel.Text = "La foto del del cliente"; }
private void Form1_MouseMove(object sender, MouseEventArgs e)
{ this.toolStripStatusLabel.Text = ""; }
Nel caso dell’evento MouseMove relativo al form, si resetta il contenuto con una stringa
vuota.
Classe HelpProvider
L’help contestuale consente di fornire all’utente aiuto ed informazioni su un particolare
controllo, semplicemente selezionandolo e utilizzando i tasti di scorciatoia opportuni.
Per far ciò, è possibile in .NET utilizzare la classe HelpProvider.
Fare clic sulla Casella degli strumenti e trascinare il componente HelpProvider sul form,
è possibile impostare la stringa di help per ogni controllo che si vuole, ad esempio, per la
Visual C#
um 177 di 202
TextBox txtNome si può scrivere il codice seguente.
HelpProvider helpProvider = new HelpProvider();
helpProvider.SetHelpString(txtNome, "Il nome del cliente");
helpProvider.SetHelpString(txtCognome, "Il cognome del cliente");
helpProvider.SetHelpString(txtIndirizzo, "L’indirizzo del cliente");
helpProvider.SetHelpString(cboCAP, "Il CAP di residenza del cliente");
helpProvider.SetHelpString(txtCittà, "La città di residenza del cliente");
helpProvider.SetHelpString(lstTitoloDiStudio, "Il titolo di studio del cliente");
helpProvider.SetHelpString(pictureBoxFoto, "La foto del cliente");
helpProvider.SetHelpString(btCarica, "Carica un file immagine che rappresenta la foto del
cliente");
helpProvider.SetHelpString(btClose, "Chiude la finestra");
Cliccando sul ? del form, il puntatore visualizzerà un punto interrogativo e ogni controllo
per cui è stata impostata una stringa di help visualizzerà al clic su di esso tale stringa in
una sorta di tooltip.
La stessa cosa avverrà, senza alcuna aggiunta di codice, cliccando il tasto F1 dopo aver
impostato il focus su un controllo.
Per visualizzare un pulsante di help sulla finestra, è necessario impostare a true la
proprietà HelpButton del form e quindi impostare a false le proprietà MaximizeBox e
MinimizeBox.
Ciò visualizzerà sulla barra del titolo della finestra un pulsante con un punto interrogativo
che una volta cliccato attiverà l’help contestuale, situazione verificabile dal fatto che anche
il puntatore del mouse cambierà.
Dopo aver aggiunto il componente HelpProvider, ogni controllo sarà dotato di una nuova
proprietà HelpString on NomeHelpProvider nella finestra delle proprietà, da impostare con
la stringa di Help per il controllo stesso.
Componenti provider
Esistono dei componenti che pur non essendo visibili permettono di dotare i controlli grafici
di nuove proprietà, sono gli extender provider: Tooltip, ErrorProvider e HelpProvider e i
programmatori possono creare il provider implementando l’interfaccia IExtenderProvider.
La classe HelpProvider permette di aprire un file di guida esterno, sia HTML sia CHM.
Per l’applicazione si usa un file CHM qualunque, per esempio il file di guida di Blocco note,
NOTEPAD.CHM.
Se la proprietà HelpNamespace è stata impostata ad un percorso di un file di guida,
quest’ultimo sarà visualizzato usando i parametri che devono essere impostati mediante il
metodo SetHelpNavigator ad uno dei valori dell’enumerazione HelpNavigator.
 AssociateIndex: apre la guida alla prima lettera dell’argomento specificato.
Visual C#
um 178 di 202
 Find: apre la guida alla pagina di ricerca.
 Index: apre la guida alla pagina dell’indice.
 KeywordIndex: apre la guida all’argomento dell’indice specificato se esiste, oppure a
quello più vicino.
 TableOfContents: apre il sommario la guida.
 Topic: apre lo specifico argomento se esiste.
 TopicId: apre l’argomento identificato dal numero specificato.
Per esempio, alla pressione del tasto F1 con il focus su un dato controllo, aprire il file
NOTEPAD.CHM, posizionandosi alla prima parola che inizia con una determinata lettera.
È necessario in questo caso impostare il valore HelpNavigator.Index e quindi la lettera che
interessa.
HelpProvider chmHelp = new HelpProvider();
chmHelp.HelpNamespace = "notepad.chm";
chmHelp.SetHelpNavigator(btHelpIndex, HelpNavigator.Index);
chmHelp.SetHelpKeyword(btHelpIndex, "a");
Se si vuol aprire la guida posizionandosi ad un determinato sotto argomento, bisogna
fornire il percorso dell’argomento nel file, si può ottenere cliccando con il tasto destro, nella
pagina dell’argomento stesso e poi cliccando su proprietà.
Quindi bisogna utilizzare come HelpNavigator il parametro Topic.
chmHelp.SetHelpNavigator(btHelpIndex, HelpNavigator.Topic);
Visual C#
um 179 di 202
chmHelp.SetHelpKeyword(btHelpIndex, "notepad.chm::/win_notepad_font.htm");
Per impostare il file HTM da aprire, basta impostare la proprietà HelpNamespace
utilizzando il percorso del file e invocare il metodo SetShowHelp per indicare che l’help è
attivo su un determinato controllo.
HelpProvider htmlHelpProvider = new HelpProvider();
htmlHelpProvider.SetShowHelp(btReadMe, true);
htmlHelpProvider.HelpNamespace = "Leggimi.htm";
Se è impostata la proprietà HelpNamespace allora la HelpString sarà ignorata e non sarà
più visualizzata come tooltip premendo il tasto F1 o utilizzando l’HelpButton del form ma al
suo posto sarà aperto il file di guida stesso.
La HelpString sarà comunque accessibile per ogni evenienza invocando il metodo
GetHelpString.
Menu
La classe HelpProvider utilizza internamente i metodi della classe Help che fornisce al
programmatore solo metodi statici pubblici, quindi è utilizzabile direttamente nel codice.
Utilizzarla, per esempio, per creare il classico menu di help, ?, con le voci Sommario,
Indice e Cerca e aprire il file guida posizionandolo proprio ad una delle tre voci.
Invocare il metodo ShowHelp, con l’adeguato HelpNavigator ed eventualmente con una
chiave di ricerca.
private void sommarioToolStripMenuItem_Click(object sender, EventArgs e)
{ Help.ShowHelp(this, "notepad.chm", HelpNavigator.TableOfContents); }
Visual C#
um 180 di 202
private void indiceToolStripMenuItem_Click(object sender, EventArgs e)
{ Help.ShowHelp(this, "notepad.chm", HelpNavigator.Index); }
private void cercaToolStripMenuItem_Click(object sender, EventArgs e)
{ Help.ShowHelp(this, "notepad.chm", HelpNavigator.Find,"keyword"); }
L’attivazione di un menu può essere fatta con un clic del mouse oppure dalla tastiera con
un tasto di scelta, in altre parole un tasto che, premuto insieme al tasto ALT, consente di
attivare l’azione associata al menu.
Per assegnare un tasto di scelta ad un menu occorre inserire (&) prima del carattere che
deve diventare il tasto di scelta: il carattere è sottolineato.
Visual C#
um 181 di 202
MODULO 9
XAMARIN
Introduzione
Mono for Android
DroidDraw
Mono for iOS
Visual C#
um 182 di 202
INTRODUZIONE
GENERALITÀ
Ci sono tre piattaforme con un SO e un modello di sviluppo totalmente diverso l’uno
dall’altro.
1. Android: Eclipse come IDE (Integrated Development Environment) e Java come
linguaggio di programmazione.
2. iOS: XCode come IDE e Objective-C come linguaggio di programmazione.
3. Windows Phone: Visual Studio come IDE e Visual C# (Visual Basic) come linguaggio
di programmazione.
Xamarin permette di sviluppare sulle tre piattaforme cercando di riutilizzare il più possibile
il know how di un programmatore .NET.
Xamarin non inventa un nuovo sistema di sviluppo ma si sostituisce solo per la stesura del
codice che orchestra gli elementi e interagisce nelle view.
La struttura del pacchetto, il layout e tutto il motore di rendering sono invariati rispetto allo
sviluppo tradizionale, bisogna imparare per le varie piattaforme l’ambiente di disegno, i
controlli messi a disposizione e le guideline di ogni rispettivo sistema.
Xamarin ha progettato un modello a oggetti che rispecchia quello in Objective-C di iOS e
quello in Java di Android ed effettua il binding sull’API di ogni piattaforma.
Non si sostituisce ma aggiunge uno strato superiore, questo significa che è presente un
overlay tra le chiamate.
Questo aspetto di binding riguarda solo l’UI, l’IO e l’uso dei sensori.
Quando si usa la BCL (Base Class Library) per manipolare stringhe e numeri, invece, si
sta usando codice managed sviluppato da Xamarin stesso, con il progetto Mono.
Android e iOS si differenziano nel modo seguente.
 Su Android è eseguita una VM (Virtual Machine) Mono che esegue il codice managed.
 Su iOS il codice managed è compilato in linguaggio macchina direttamente in fase di
creazione del pacchetto.
Xamarin permette di sfruttare quanto si conosce di .NET Framework.
Visual Studio: come ambiente di sviluppo.
Visual C#: come linguaggio di programmazione.
INSTALLAZIONE
1. JDK (Java Development Kit).
2. Android SDK (Software Development Kit).
3. Android NDK (Android Native Development).
4. GTK (GIMP [GNU Image Manipulation Program] ToolKit)
È un insieme di strumenti per la creazione di GUI, sviluppato in C, supporta nativamente
Visual C#
um 183 di 202
l’ambiente grafico X Window System e Microsoft Windows ed è S/W libero, parte del
progetto GNU (GNU is Not Unix), secondo la licenza LGPL (Lesser General Public
License).
5. Xamarin Studio.
L’IDE, successore di MonoDevelop, su Windows.
Visual C#
um 184 di 202
6. Xamarin.Android.
Integrazione con Visual Studio.
7. Xamarin.iOS.
Integrazione con Visual Studio.
Visual C#
um 185 di 202
LIBRERIA DI CLASSI (PORTABILE)
Strutturando il progetto in più assembly e stratificando in vari componenti, si può
riutilizzare lo stesso codice tra le piattaforme, riducendosi solo alla scrittura del codice
necessario per interagire con l’UI.
Visual C#
um 186 di 202
MONO FOR ANDROID
INTRODUZIONE
Fare clic su File/Nuovo/Progetto… (CTRL+MAIUSC+N).
Nella finestra di dialogo, selezionare Modelli/Visual C#/Android quindi selezionare il tipo
di applicazione.
Immettere il nome del progetto nel campo Nome: quindi per creare il progetto, premere
OK.
Visual C#
um 187 di 202
La soluzione generata contiene le seguenti cartelle.
 Riferimenti.
 Componenti.
 Assets: contiene le risorse, qualsiasi file diverso da immagini perchè hanno una
cartella dedicata.
 Resources: contiene le risorse utilizzate dall’app, icone, i file di layout rappresentati
dai file AXML e le stringhe, usate per la localizzazione.
File ACTIVITY1.CS
È la view automaticamente creata dal template, è rappresentata da un Activity che a sua
volta può facoltativamente appoggiarsi ad un file di layout AXML per la definizione dell’UI.
[Activity(Label = "Ciao, mondo!", MainLauncher = true, Icon = "@drawable/icon")]
public class Activity1 : Activity {
int count = 1;
protected override void OnCreate(Bundle bundle)
{ base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
// Get our button from the layout resource,and attach an event to it
Button button = FindViewById<button>
(Resource.Id.MyButton);
button.Click += delegate { button.Text = string.Format("{0} clicks!", count++); };
}
}
Il metodo OnCreate è sovrascritto per creare la view che è creata con SetContentView.
Non vi è quindi un sistema automatico ma è il programmatore ad indicare quale file di
layout caricare.
Xamarin genera automaticamente una classe Resource che permette di fare riferimento
alle risorse.
File MAIN.AXML
Il designer mantiene e modifica un struttura XML che, in modo molto simile a XAML,
definisce la struttura e gli elementi.
<?xml version="1.0" encoding="utf-8" ?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<button android:id="@+id/MyButton"
Visual C#
um 188 di 202
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/Hello" />
</linearlayout>
Il markup descrive un pulsante inserito in un layout allineato verticalmente che deve
riempire orizzontalmente tutto lo spazio a disposizione, mentre verticalmente solo quello
necessario al contenuto.
Gli attributi valorizzati con @ stanno ad indicare un riferimento alle risorse.
Visual C#
um 189 di 202
DROIDDRAW
INTRODUZIONE
È possibile scaricarlo collegandosi all’indirizzo:http://code.google.com/p/droiddraw.
Eseguire l’applicazione.
Selezionare il Root Layout:.
Selezionare lo Screen Size:.
Visual C#
um 190 di 202
Widgets contiene l’elenco dei componenti che possono essere inseriti all’interno del form.
Le altre schede permettono d’impostare le proprietà del controllo selezionato.
Visual C#
um 191 di 202
Fare clic sul pulsante Generate.
Visual C#
um 192 di 202
MONO FOR IOS
INTRODUZIONE
Fare clic su File/Nuovo/Progetto… (CTRL+MAIUSC+N).
Nella finestra di dialogo, selezionare Modelli/Visual C#/iOS quindi selezionare il tipo di
template, per esempio Universal, immettere il nome del progetto nel campo Nome: quindi
per creare il progetto, premere OK.
I template a disposizione sono tre, ognuno dei quali con una UI di partenza.
1. iPad.
2. iPhone.
3. Universal: soluzioni universali per entrambi.
Visual C#
um 193 di 202
File MAIN.CS
using MonoTouch.Foundation;
using MonoTouch.UIKit;
namespace HelloWorld1 {
public class Application {
// entry point dell'applicazione
static void Main(string[] args)
{ // se vuoi una classe diversa da AppDelegate, scriverla di seguito
UIApplication.Main(args, null, "AppDelegate");
}
}
}
File MYVIEWCONTROLLER.CS
Qui si scrive il codice che interagisce con la view.
Il metodo ViewDidLoad permette d’inserire tutto il codice di setup, per esempio intercettare
il clic sul pulsante per la visualizzazione di un messaggio.
TouchUpInside rappresenta il tap sul pulsante che s’intercetta tramite delegate.
Sull’evento si usa l’oggetto UIAlertView per visualizzare un messaggio, specificando titolo,
testo e etichetta per il pulsante di chiusura.
using System;
using MonoTouch.UIKit;
using System.Drawing;
namespace HelloWorld1 {
public class MyViewController : UIViewController {
UIButton button;
int numClicks = 0;
float buttonWidth = 200;
float buttonHeight = 50;
public MyViewController() { }
public override void ViewDidLoad()
{ base.ViewDidLoad();
// setup addizionale dopo avere caricato la view tipicamente da un nib
View.Frame = UIScreen.MainScreen.Bounds;
View.BackgroundColor = UIColor.White;
View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth |
UIViewAutoresizing.FlexibleHeight;
button = UIButton.FromType(UIButtonType.RoundedRect);
button.Frame = new RectangleF(View.Frame.Width / 2 - buttonWidth /
2,View.Frame.Height / 2 - buttonHeight / 2,buttonWidth,buttonHeight);
button.SetTitle("Click me", UIControlState.Normal);
button.TouchUpInside += (object sender, EventArgs e) =>
{ button.SetTitle(String.Format("clicked {0} times", numClicks++),
UIControlState.Normal); };
button.AutoresizingMask = UIViewAutoresizing.FlexibleWidth |
UIViewAutoresizing.FlexibleTopMargin | UIViewAutoresizing.FlexibleBottomMargin;
View.AddSubview(button);
}
}
}
Visual C#
um 194 di 202
MODULO 10
VIDEOGIOCHI
Unity
Construct 2
Visual C#
um 195 di 202
UNITY
INTRODUZIONE
Unity è un motore per lo sviluppo di videogiochi che permette di creare videogame ed
esportarli per più piattaforme, desktop (Mac, Windows e Linux), web e per diversi
marketplace e device (Windows Store, Windows Phone, iOS, Android, Blackberry, Wii U,
PlayStation e XBOX).
È un IDE per progettare giochi in Visual C#, JavaScript e/o Boo, disponibile in due
versioni: gratuita e Pro.
È consigliabile iniziare con la versione gratuita, anche perché è già completa di tutto ciò
che serve per creare giochi ed esportarli per ogni piattaforma.
La versione Pro include alcuni strumenti avanzati e tool per le performance.
Per creare un nuovo progetto, fare clic su File/New project.
Fare clic sul pulsante Create.
Fare clic su Window/Layout/Wide per uniformare la finestra dell’applicazione, per
Visual C#
um 196 di 202
utilizzare la modalità 2D.
VSTU (VISUAL STUDIO TOOLS FOR UNITY)
Unity prevede l’esportazione del progetto come soluzione per Visual Studio che genererà i
pacchetti per il Windows Store e per il Windows Phone Store.
In Unity si definiscono gli oggetti che faranno parte del gioco: i GameObject.
I comportamenti che ciascun oggetto, in pratica gli eventi, sono descritti tramite codice.
Fare clic su Edit/Preferences/External Tools.
Inserire il percorso in cui trovare l’eseguibile di Visual Studio sul PC.
Visual C#
um 197 di 202
C:\PROGRAM FILES (X86)\MICROSOFT VISUAL STUDIO 12.0\COMMON7\IDE\
DEVENV.EXE
Installare il pacchetto.
Visual C#
um 198 di 202
CONSTRUCT 2
INTRODUZIONE
Fare clic su File/Export project… .
Visual C#
um 199 di 202
Visual C#
um 200 di 202
Aprire il pacchetto in Visual Studio.
Visual C#
um 201 di 202
Visual C#
um 202 di 202
UBERTINI MASSIMO
http://www.ubertini.it
[email protected]
Dip. Informatica Industriale
I.T.I.S. "Giacomo Fauser"
Via Ricci, 14
28100 Novara Italy
tel. +39 0321482411
fax +39 0321482444
http://www.fauser.edu
[email protected]