|
Premessa
Visto e considerato che la maggior parte di coloro a cui è destinata questa serie di articoli sono Programmatori e c'è una regola non scritta che dice come qualsiasi bravo programmatore è un pessimo grafico, lasciamo perdere XAML per il momento ed impariamo ad usare il codice per manipolare gli oggetti WPF, in seguito, capito come funziona il modello ad oggetti, come funzionano gli eventi e tutte le nuove funzionalità fornite da WPF, saremo in grado di usare anche XAML senza shock da linguaggio di Markup per coloro che vengono dalla programmazione windows.
Argomenti trattati
L'oggetto Application
Una Window Application WPF, per funzionare deve generare un oggetto (ed uno soltanto) di tipo Application o di una classe ereditata da Application. Questo oggetto, una volta istanziato deve chiamare il suo metodo Run per avviarsi, dopo di ciò il termine dell'applicazione dipende dalla configurazione della stessa, usualmente, visto che una Window Application instanzia almeno una finestra, il termine dell'applicazione è determinato dalla chiusura dell'ultima finestra aperta oppure dalla chiusura di quella definita come finestra principale.
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
namespace Dnw
{
class Program
{
[STAThread]
public static void Main()
{
DnwApp app = new DnwApp();
app.Run();
}
}
}
imports System
imports System.Collections.Generic
imports System.Text
imports System.Windows
Public Class Program
_
public shared sub Main
dim app as new DnwApp ()
app.Run()
End Sub
End Class
L'oggetto Application è in grado di intercettare una decina di eventi, due sono quelli che per primi dovremmo prendere in considerazione:
OnStartup - Evento scatenato all'esecuzione del metodo Run che può essere utilizzato per istanziare gli oggetti di base che comporranno la nostra applicazione.
OnSessionEnding - Evento scatenato alla chiusura della sessione di windows, sia esso dovuto ad un Logoff dell'utente oppure alla richiesta di spegnimento del sistema. In Questo evento possiamo provvedere a verificare se vi sono dati da salvare, aggiornare e memorizzare lo stato dell'applicazione ed eventualmente, richiedere l'intervento dell'utente bloccando la chiusura della sessione quando necessario.
Aggiungiamo un terzo evento che può tornare utile in alcuni casi:
OnExit - Evento scatenato all'esecuzione dell'uscita dall'applicazione. L'evento viene scatenato dopo la chiusura di tutte le finestre eventualmente aperte, pertanto i controlli sulle transazioni, sui dati modificati ma non salvati ecc. Andrebbero gestiti sull'evento Closing di ciascuna delle Window dell'applicazione.
Vediamo un esempio dell'uso degli eventi dell'applicazione:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
namespace Dnw
{
///
/// Classe Derivata da Application implementata per gestire l'applicazione
/// che andiamo a gestire secondo le nostre esigenze.
///
///
///
///
class DnwApp : Application
{
///
/// Override del metodo della classe application che gestisce l'evento OnStartup,
/// che permette di utilizzare questo evento per leggere parametri di configurazione
/// verificare connessioni a database, inizializzare dati di tipo statico e caricare
/// dati e strutture, oltre ovviamente a poter avviare la finestra principale della
/// applicazione, così come abbiamo fatto in questo esempio
///
/// Struttura contenente i dati dell'evento
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Window win = new Window();
win.Show();
}
///
/// Override del metodo della classe application che gestisce l'evento
/// OnSessionEnding, questa funzione, intercetta l'evento di sistema
/// e agisce di conseguenza, permettendo all'utente di bloccarlo.
/// Intercettare questo evento permette di inserire codice che
/// salva dati, oppure salva su disco lo stato o la configurazione
/// di una applicazione aperta, oppure, richiede all'utente come
/// comportarsi ad esempio se vi fossero transazioni aperte.
///
/// classe contenente i dati dell'evento
protected override void OnSessionEnding(SessionEndingCancelEventArgs e)
{
base.OnSessionEnding(e);
MessageBoxResult result =
MessageBox.Show("Sto chiudendo la sessione che faccio Continuo?",
MainWindow.Title, MessageBoxButton.YesNoCancel, MessageBoxImage.Question,
MessageBoxResult.Yes);
e.Cancel = (result != MessageBoxResult.Yes);
}
///
/// Override del metodo della classe Application che gestisce l'evento
/// OnExit, all'uscita dell'applicazione, permette di verificare se vi sono
/// dati da salvare, memorizzare lo stato dell'applicazione ed eventuali operazioni
/// di controllo su transazioni aperte ecc.
///
/// Classe contenente i dati dell'evento
protected override void OnExit(ExitEventArgs e)
{
base.OnExit(e);
MessageBoxResult result =
MessageBox.Show("Sto chiudendo l'applicazione, Verifico e salvo ciò che necessario... ",
MainWindow.Title, MessageBoxButton.OK, MessageBoxImage.Information);
}
}
}
imports System
imports System.Collections.Generic
imports System.Text
imports System.Windows
imports System.IO
Public Class DnwApp
Inherits Application
Protected Overrides Sub OnStartup(ByVal e As System.Windows.StartupEventArgs)
MyBase.OnStartup(e)
dim win as Window = new Window()
win.Title = "Test Evento"
win.Height = 150
win.Width = 150
win.Show()
End Sub
Protected Overrides Sub OnSessionEnding(ByVal e As System.Windows.SessionEndingCancelEventArgs)
MyBase.OnSessionEnding(e)
dim result as MessageBoxResult = _
MessageBox.Show("Sto chiudendo la sessione che faccio Continuo?", _
MainWindow.Title, MessageBoxButton.YesNoCancel, MessageBoxImage.Question, _
MessageBoxResult.Yes)
e.Cancel = (result <> MessageBoxResult.Yes)
End Sub
Protected Overrides Sub OnExit(ByVal e As System.Windows.ExitEventArgs)
MyBase.OnExit(e)
MessageBox.Show("Sto chiudendo l'applicazione, Verifico e salvo ciò che necessario... ", _
"Exit", MessageBoxButton.OK, MessageBoxImage.Information)
End Sub
End Class
In questo caso, utilizziamo OnStartup per istanziare la finestra di partenza dell'applicazione e gestiamo un ipotetico salvataggio dati allo Shutdown e all'uscita dall'applicazione.
L'oggetto Window
In una Windows Application, l'oggetto Window è ovviamente la colonna portante, infatti per definirsi tale una Windows Application deve avere almeno una finestra con cui interagisce con l'utente. L'oggetto Window di WPF è però molto diverso da una Form, anche se l'aspetto può sembrare uguale.
Abbiamo potuto vedere già nella nostra prima minuscola applicazione, nella prima parte di questa serie, come Window sia dotata di un Titolo, e sia dotata di un oggetto Content, ma già qui, possiamo vedere come Content sia un oggetto singolo, non una collezione di oggetti. E già iniziano i dubbi, ma se contiene una cosa sola, come faccio a creare una form con delle textbox e altri controlli a noi familiari?
Vi rimando alla traduzione dell'articolo MSDN L'Albero degli elementi, che ci mostra quali siano le strutture Visuale e Logica di una applicazione WPF. Sono basate su un albero, e Window è il ramo che si attacca alla radice, ovvero Application, ad esso poi è attaccato il contenuto, che poi si ramifica e si segmenta. Per il primo test sulla finestra, abbiamo scritto del testo nel Content, ed abbiamo visto che possiamo cambiarne la dimensione, ora, proviamo a vedere se Window risponde a degli eventi, la risposta è ovviamente Si, e facciamo un primo test con gli eventi del Mouse: MouseDown, MouseUp, MouseWheel, MouseEnter, Mouseleave.
///
/// Override del metodo della classe application che gestisce l'evento OnStartup,
/// che permette di utilizzare questo evento per leggere parametri di configurazione
/// verificare connessioni a database, inizializzare dati di tipo statico e caricare
/// dati e strutture, oltre ovviamente a poter avviare la finestra principale della
/// applicazione, così come abbiamo fatto in questo esempio
///
/// Argomenti dell'evento
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Window win = new Window();
win.Title = "Test Evento";
win.Height = 150;
win.Width = 150;
win.WindowStartupLocation = WindowStartupLocation.CenterScreen;
win.MouseDown += new System.Windows.Input.MouseButtonEventHandler(win_MouseDown);
win.MouseEnter += new System.Windows.Input.MouseEventHandler(win_MouseEnter);
win.MouseLeave += new System.Windows.Input.MouseEventHandler(win_MouseLeave);
win.MouseUp += new System.Windows.Input.MouseButtonEventHandler(win_MouseUp);
win.MouseWheel += new System.Windows.Input.MouseWheelEventHandler(win_MouseWheel);
win.Closed += new EventHandler(win_Closed);
win.Show();
}
///
/// Salviamo il log creato al termine della sessione per
/// guardarci dentro
///
///
///
void win_Closed(object sender, EventArgs e)
{
string logFile =
Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.Personal),
"DnwAppTest01.log");
File.WriteAllText( logFile, mContentText.ToString());
}
///
/// Evento che viene generato quando l'utente usa la rotella del mouse sulla finestra
///
/// Oggetto che ha scatenato l'evento
/// Argomenti relativi all'evento
void win_MouseWheel(object sender,
System.Windows.Input.MouseWheelEventArgs e)
{
Window win = sender as Window;
mContentText.AppendFormat("Evento MouseWheel - Delta: {0}", e.Delta);
mContentText.AppendLine();
win.Content = null;
win.Content = mContentText;
}
///
/// Evento che viene generato quando un tasto del mouse viene rilasciato
///
/// Oggetto che ha scatenato l'evento
/// Argomenti relativi all'evento
void win_MouseUp(object sender,
System.Windows.Input.MouseButtonEventArgs e)
{
Window win = sender as Window;
mContentText.AppendFormat("Evento mouseUp - ChangedButton: {0}", e.ChangedButton);
mContentText.AppendLine();
win.Content = null;
win.Content = mContentText;
}
///
/// Evento che viene generato quando il mouse esce dalla finestra
///
/// Oggetto che ha scatenato l'evento
/// Argomenti relativi all'evento
void win_MouseLeave(object sender,
System.Windows.Input.MouseEventArgs e)
{
Window win = sender as Window;
mContentText.AppendFormat("Evento mouseLeave - Position: {0}:{1}",
e.GetPosition(win).X, e.GetPosition(win).Y);
mContentText.AppendLine();
win.Content = null;
win.Content = mContentText;
}
///
/// Evento che viene generato quando il mouse entra nella finestra
///
/// Oggetto che ha scatenato l'evento
/// Argomenti relativi all'evento
void win_MouseEnter(object sender,
System.Windows.Input.MouseEventArgs e)
{
Window win = sender as Window;
mContentText.AppendFormat("Evento mouseEnter - Position: {0}:{1}",
e.GetPosition(win).X, e.GetPosition(win).Y);
mContentText.AppendLine();
win.Content = null;
win.Content = mContentText;
}
///
/// Evento che viene generato quando un tasto del mouse viene premuto
///
/// Oggetto che ha scatenato l'evento
/// Argomenti relativi all'evento
void win_MouseDown(object sender,
System.Windows.Input.MouseButtonEventArgs e)
{
Window win = sender as Window;
mContentText.AppendFormat("Evento mouseDown - ChangedButton: {0}", e.ChangedButton);
mContentText.AppendLine();
win.Content = null;
win.Content = mContentText;
}
imports System
imports System.Collections.Generic
imports System.Text
imports System.Windows
imports System.IO
Public Class DnwApp
Inherits Application
private mContentText as new StringBuilder()
Protected Overrides Sub OnStartup(ByVal e As StartupEventArgs)
MyBase.OnStartup(e)
Dim win As New Window()
win.Title = "Test Evento"
win.Height = 150
win.Width = 150
win.WindowStartupLocation = WindowStartupLocation.CenterScreen
AddHandler win.MouseDown, AddressOf win_MouseDown
AddHandler win.MouseEnter, AddressOf win_MouseEnter
AddHandler win.MouseLeave, AddressOf win_MouseLeave
AddHandler win.MouseUp, AddressOf win_MouseUp
AddHandler win.MouseWheel, AddressOf win_MouseWheel
AddHandler win.Closed, AddressOf win_Closed
win.Show()
End Sub
Private Sub win_Closed(ByVal sender As Object, ByVal e As EventArgs)
Dim logFile As String = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Personal),
"DnwAppTest01.log")
File.WriteAllText(logFile, mContentText.ToString())
End Sub
Private Sub win_MouseWheel(ByVal sender As Object, ByVal e As System.Windows.Input.MouseWheelEventArgs)
Dim win As Window = TryCast(sender, Window)
mContentText.AppendFormat("Evento MouseWheel - Delta: {0}", e.Delta)
mContentText.AppendLine()
win.Content = Nothing
win.Content = mContentText
End Sub
Private Sub win_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
Dim win As Window = TryCast(sender, Window)
mContentText.AppendFormat("Evento mouseUp - ChangedButton: {0}", e.ChangedButton)
mContentText.AppendLine()
win.Content = Nothing
win.Content = mContentText
End Sub
Private Sub win_MouseLeave(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs)
Dim win As Window = TryCast(sender, Window)
mContentText.AppendFormat("Evento mouseLeave - Position: {0}:{1}"
, e.GetPosition(win).X, e.GetPosition(win).Y)
mContentText.AppendLine()
win.Content = Nothing
win.Content = mContentText
End Sub
Private Sub win_MouseEnter(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs)
Dim win As Window = TryCast(sender, Window)
mContentText.AppendFormat("Evento mouseEnter - Position: {0}:{1}",
e.GetPosition(win).X, e.GetPosition(win).Y)
mContentText.AppendLine()
win.Content = Nothing
win.Content = mContentText
End Sub
Private Sub win_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
Dim win As Window = TryCast(sender, Window)
mContentText.AppendFormat("Evento mouseDown - ChangedButton: {0}", e.ChangedButton)
mContentText.AppendLine()
win.Content = Nothing
win.Content = mContentText
End Sub
Protected Overrides Sub OnSessionEnding(ByVal e As System.Windows.SessionEndingCancelEventArgs)
MyBase.OnSessionEnding(e)
dim result as MessageBoxResult = _
MessageBox.Show("Sto chiudendo la sessione che faccio Continuo?", _
MainWindow.Title, MessageBoxButton.YesNoCancel, MessageBoxImage.Question, _
MessageBoxResult.Yes)
e.Cancel = (result <> MessageBoxResult.Yes)
End Sub
Protected Overrides Sub OnExit(ByVal e As System.Windows.ExitEventArgs)
MyBase.OnExit(e)
MessageBox.Show("Sto chiudendo l'applicazione, Verifico e salvo ciò che necessario... ", _
"Exit", MessageBoxButton.OK, MessageBoxImage.Information)
End Sub
End Class
Nel Codice esempio, vediamo come in risposta agli eventi, aggiungo del testo ad uno stringbuilder che inserisco nella finestra. Osservando il codice, potete notare come per visualizzare il contenuto io ponga win.Content a null prima di assegnargli di nuovo lo stringbuilder. Questo accade perché la variazione del contenuto dello stringbuilder visto che questo oggetto non ha nulla a che vedere con gli elementi visuali, non solleva alcun evento di tipo "Content Changed" quindi la Window non esegue automaticamente il Redraw, che, pure in modo grezzo, io ottengo invece cambiando l'oggetto della proprietà Content per cancellare la finestra. Eseguendo il codice la finestra visualizzata è la seguente:

la finestra aperta dall'applicazione, volutamente molto piccola, possiamo notare come il contenuto viene tagliato e non appaiono scrollbars. |

Ingrandendo la finestra, vediamo come il contenuto diviene visibile, ma non appaiono scrollbars. |
Questo piccolo esperimento ci serve per stabilire alcuni concetti:
- L'ordine in cui gli eventi del mouse si verificano che possiamo leggere dentro alla finestra.
- La proprietà Content può contenere qualsiasi cosa, se l'oggetto in essa predisposto non è un elemento visuale, viene rappresentato con la sua funzione ToString().
- La proprietà Content non rappresenta un controllo visuale e non ha alcuna proprietà o metodo per formattare in qualsivoglia modo, ciò che gli viene inserito all'interno.
- Ponendo nel Content un oggetto che non è in grado di segnalare una variazione del suo contenuto, come uno StringBuilder, la finestra non sarà in grado di aggiornare il suo contenuto al variare del contenuto dell'oggetto.
Completiamo la prima breve panoramica dell'oggetto Window provando ancora il sistema di input informazioni a tutt'oggi più utilizzato, la tastiera, e vediamo come anche per le Windows così come per le Forms a cui siamo abituati, il metodo più corretto per creare una finestra che faccia quello che a noi serve, è quello di creare una classe che Eredita dalla classe base ed implementa quanto necessario al suo uso.
#region Using directives
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows;
#endregion
namespace Dnw
{
///
/// Classe Derivata da Application implementata per gestire l'applicazione
/// che andiamo a gestire secondo le nostre esigenze.
///
///
///
///
class DnwApp : Application
{
///
/// Variabile a livello di classe per la gestione del contenuto
///
private StringBuilder mContentText = new StringBuilder();
///
/// Override del metodo della classe application che gestisce l'evento OnStartup,
/// che permette di utilizzare questo evento per leggere parametri di configurazione
/// verificare connessioni a database, inizializzare dati di tipo statico e caricare
/// dati e strutture, oltre ovviamente a poter avviare la finestra principale della
/// applicazione, così come abbiamo fatto in questo esempio
///
/// Argomenti dell'evento
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
WinMain win = new WinMain();
win.Title = "Test input da tastiera C#";
win.Height = 400;
win.Width = 340;
win.Show();
}
}
}
imports System
imports System.Collections.Generic
imports System.Text
imports System.Windows
imports System.IO
Public Class DnwApp
Inherits Application
private mContentText as new StringBuilder()
Protected Overrides Sub OnStartup(ByVal e As StartupEventArgs)
MyBase.OnStartup(e)
Dim win As New WinMain()
win.Title = "Test input da tastiera C#"
win.Height = 400
win.Width = 340
win.Show()
End Sub
End Class
Generiamo un nuovo progetto, contenente la classe Program.cs (Program.vb) identica a quella mostrata all'inizio di questo articolo. Una seconda classe, simile alla precedente DnwApp, e una terza classe, WinMain, derivata da Window.
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
namespace Dnw
{
///
/// Finestra principale test input da tastiera
///
///
///
///
class WinMain: Window
{
///
/// Stringa di input
///
string mTextInput = string.Empty;
///
/// Evento che viene generato all'input di un dato da tastiera
///
/// Oggetto che ha scatenato l'evento
/// Argomenti relativi all'evento
///
///
protected override void OnTextInput(System.Windows.Input.TextCompositionEventArgs e)
{
base.OnTextInput(e);
if (e.Text == "\b" && mTextInput.Length > 0)
{
mTextInput = mTextInput.Substring(0, mTextInput.Length - 1);
this.Content = mTextInput;
}
else
{
if (e.Text != "\n" && e.Text != "\r" && e.Text != "\t")
{
if (e.Text.Length > 0 &&
!Char.IsControl(e.Text[0]))
{
mTextInput += e.Text;
this.Content = mTextInput;
}
}
else
{
mTextInput += e.Text;
this.Content = mTextInput;
}
}
}
}
}
Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.Windows
Public Class Winmain
Inherits Window
Dim mTextInput As String = String.Empty
Protected Overloads Overrides Sub OnTextInput _
(ByVal e As System.Windows.Input.TextCompositionEventArgs)
MyBase.OnTextInput(e)
If e.Text = "" & Chr(8) & "" AndAlso mTextInput.Length > 0 Then
mTextInput = mTextInput.Substring(0, mTextInput.Length - 1)
Me.Content = mTextInput
Else
If e.Text <> "" & Chr(10) & ""
AndAlso e.Text <> "" & Chr(13) & "" AndAlso e.Text <> "" & Chr(9) & "" Then
If e.Text.Length > 0 AndAlso Not [Char].IsControl(e.Text(0)) Then
mTextInput += e.Text
Me.Content = mTextInput
End If
Else
mTextInput += e.Text
Me.Content = mTextInput
End If
End If
End Sub
End Class
In questa nostra classe finestra dimostriamo ancora una volta il funzionamento del Content, utilizziamo una stringa invece di uno StringBuilder come deposito per il testo visualizzato, attiviamo un paio di controlli che ci permettono di usare il tasto backspace per cancellare un dato sbagliato, e intercettiamo l'Accapo in modo da poter scrivere su più righe. Fatto questo facciamo le nostre prove e vediamo che il contenuto riporta i tasti che premiamo, perché la stringa sembra provocare l'update che lo stringbuilder non provoca? Per un motivo molto semplice, le stringhe in .NET sono immutabili, quindi ogni volta che sommiamo un carattere ad una stringa, viene generato un nuovo oggetto ed il vecchio oggetto va nel Garbage collector. Questo sono certa che è qualcosa che avete letto in tutti i libri che parlano delle basi dello sviluppo con .NET ove è scritto che le stringhe devono sempre essere usate come oggetti immutabili, perché a causa di questo tipo di funzionamento, concatenare stringhe o cambiarle provoca un notevole overhead sia di memoria che di tempo CPU.
Provando a scrivere sulla nostra tastiera, vedremo come i caratteri vengono aggiunti o cancellati, ma non c'è cursore, non esistono ctrl+c ctrl+v, non esiste selezione eccetera, ancora una volta, viene confermato come la finestra non sia un controllo ne il suo Content qualcosa di simile al pannello contenitore di una Form. però, la finestra ha ancora un sacco di proprietà, eventi e gadget in serbo, pertanto proviamo a osservarne un paio:
public WinMain()
{
//this.WindowStyle = WindowStyle.SingleBorderWindow;
//this.WindowStyle = WindowStyle.ThreeDBorderWindow;
this.WindowStyle = WindowStyle.ToolWindow;
//this.WindowStyle = WindowStyle.None;
}
Public Sub New()
'this.WindowStyle = WindowStyle.SingleBorderWindow;
'this.WindowStyle = WindowStyle.ThreeDBorderWindow;
'this.WindowStyle = WindowStyle.None;
Me.WindowStyle = WindowStyle.ToolWindow
End Sub
generiamo un costruttore per la finestra e proviamo uno per uno i quattro valori forniti dall'enumerazione dello stile finestra, che ci permettono di decidere come visualizzare ognuna delle finestre che costruiremo in base alle esigenze determinate dalla loro funzione.

WindowStyle.None |

WindowStyle.SingleBorderWindow |

WindowStyle.ThreeDBorderWindow |

WindowStyle.ToolWindow |
Vediamo come decidere se una finestra può essere ridimensionata, utilizzando la proprietà ResizeMode, e come cambiare molto semplicemente il colore di sfondo della nostra Window, tanto per iniziare a guardare un po' più a fondo in quello che si può fare con poche istruzioni.
public WinMain()
{
//this.WindowStyle = WindowStyle.SingleBorderWindow;
this.WindowStyle = WindowStyle.ThreeDBorderWindow;
//this.WindowStyle = WindowStyle.ToolWindow;
//this.WindowStyle = WindowStyle.None;
this.ResizeMode = System.Windows.ResizeMode.NoResize;
this.Background = Brushes.Crimson;
}
Public Sub New()
'Me.WindowStyle = WindowStyle.SingleBorderWindow
Me.WindowStyle = WindowStyle.ThreeDBorderWindow
'Me.WindowStyle = WindowStyle.None
'Me.WindowStyle = WindowStyle.ToolWindow
me.ResizeMode = Windows.ResizeMode.NoResize
me.Background = Brushes.Crimson
End Sub
Abbiamo modificato il costruttore per ottenere questo effetto:

Possiamo vedere come non vi siano i bottoni di ridimensionamento, e lo sfondo ha assunto un colore piuttosto vivace.
Conclusioni
Window è una specie di Canovaccio o se vogliamo una tela grezza pronta per essere dipinta, fornisce le fondamenta, la struttura di base su cui costruire l'interfaccia utente. Nel prossimo articolo, parleremo di Pennelli e Grafica di base applicata al nostro Canovaccio.
Per qualsiasi Feedback, Ulteriore domanda, Chiarimento, oppure se trovate qualche errore usate direttamente il form dei commenti in calce a questo articolo oppure scrivete all'autrice .
|