Finestre Wpf con immagine di sfondo e trasparenti
Windows Presentation Manager fra le molte frecce al suo arco ne offre diverse che permettono di modificare l’aspetto delle sue finestre, per default rettangolari. Qui ci occupiamo delle Window dotate di forma che dipende dall’immagine incorporata nel suo Background nonché caratterizzate da parziale trasparenza.
Il piccolo ma significativo esempio che varrà illustrato fa mostra di sé nella figura seguente:
dove al posto della solita finestra si ha la figura di un calciatore fluttuante sul desktop con due normali pulsanti (Button). Per la cronaca, tale immagine deriva da una vecchia raccolta ClipArt97, tradotta dal formato WMF in PNG, che molti di noi avranno conservato... Quanto alla trasparenza essa fa sì che dietro le parti più chiare della figura siano visibili il sottostante desktop di Windows e altri documenti eventualmente aperti in quella posizione.
Nota. Ovviamente i normali pulsantini tra cui la x di chiusura sono assenti. Per chiudere l’applicazione tutti se la cavano o con codice VB oppure, semplicemente, premendo Ctrl+F4.
Due parole in termini generali sull’argomento. La classe Window deriva da ContentControl e pertanto può racchiudere contenitori come StackPanel o Grid che comprendono oggetti vari (controlli in primo luogo) ed essere dotata di uno sfondo tramite la proprietà Background. Inoltre si può agire sulle proprietà BorderBrush e BorderThickness per modificare il bordo della finestra, in vari modi tra cui il più particolare consiste nel farle assumere contorni secondo una shape personalizzata (tipicamente con Path). In tal modo si possono ottenere finestre, per fare un solo esempio, in forma di nuvola, fumetto o altre più o meno fantasiose. Le mosse da compiere sono del tutto simili a quelle relative a un controllo (ne riparlerò, ma i più esperti possono cavarsela da soli, ragionando per analogia con i controlli di forma personalizzata).
Window con immagine di sfondo e trasparenti
Nel nostro esempio le proprietà della classe Window sono WindowsStyle e AllowsTransparency. La prima va posta a “None”, in modo da eliminare del tutto il bordo, mentre nessuno si sorprende scoprendo che la seconda va fissata a “True”.
Dopo di che si esamini il file primario XAML seguente, in cui le parti singolari sono state grassettate.
<Window x:Class="MainWindow" WindowStyle="None" AllowsTransparency="True"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Finestra Wpf con immagine" Height="565" Width="473">
<Window.Background>
<ImageBrush ImageSource="C:\Calcio5.png" />
</Window.Background>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button Margin="16,12,229,80" Content="Apri il modello" FontSize="24" />
<Button Grid.Row="2" Margin="10,44,179,49" Content="Chiudi" FontSize="24" />
</Grid>
</Window>
Nota. Al posto di “C:\Calcio5.png” va inserito l’URI relativo. Ricordo che esso si ottiene collocando l’immagine nella cartella interna Images del progetto, mediante operazioni che fissano la proprietà ImageSource. Facilitate in WPF 2010 (con un provvidenziale pulsante etichettato “...”), comunque sono ben descritte in un paio di Forum, cui rimando. L’eseguibile così ottenuto dopo compilazione di fatto incorpora le figure “assoldate”, ovunque esso venga collocato.
Dopo quanto detto non occorrano commenti: il bello della programmazione dichiarativa sta nella sua eloquenza, specie in questi casi.
Pulsanti in forma di frecce e ruotati in quattro direzioni
Nella maggior parte degli esempi i pulsanti (oggetti Button) si presentano magari colorati e dipinti in molti modi e con svariate sfumature, però la loro forma è rettangolare. Peraltro associare ad essi una shape non è difficile, anche se il convento WPF in prima battuta sembrerebbe che fornisca due sole figure geometriche, l’ellisse e – di nuovo! – il rettangolo, al più con angoli arrotondati.
Presto si scopre che, a dirla tutta, la classe Windows.Shapes è dotata di altri figli oltre a Rectangle ed Ellypse, tra cui Line, Path, Polyline e Polygon. Quest’ultima con un po’ di pazienza permette il tracciamento di una figura chiusa descritta mediante i successivi punti, rappresentati da coordinate cartesiane del contenitore, ove l’asse X procede da sinistra a destra mentre l’asse X va dall’alto in basso.
Definire una freccia (piena) orientata a destra
In pratica mi interessavano dei pulsanti-frecciati, di foggia simile a quella dei cartelli stradali ed efficaci per esempio in problemi di navigazione e simili.
Partiamo con una freccia piena . Aiutandomi alla buona con un foglio a quadretti ho ottenuto il codice XAML seguente:
<Polygon Stroke="Blue" Fill="Gold"
Points="20,60 80,60 80,70 100,55 80,40 80,50 20,50"
Margin="1,0,12,12" Grid.RowSpan="3" />
Che di fatto corrisponde alla freccia con riempimento dorato posta nella cella zero di una griglia di tre righe:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="110*" />
<RowDefinition Height="110*" />
<RowDefinition Height="110*" />
</Grid.RowDefinitions>
. . . eccetera . . .
</Grid>
Nota. Per indicare i punti del poligono- Points = “. . . “ ho qui utilizzato la sintassi che elenca ciascun punto con una coppia x,y.
Frecce rivolte nelle altre direzioni, usando uno stile ad hoc del Button
Per questi scopi ho subito respinto l’idea di ripetere il lavoro sulla carta quadrettata, perché alla pazienza c’è un limite. Analogamente ho scartato il ricalcolo delle coordinate, eventualmente a run-time, procedimento possibile ma tedioso (e per giunta poco chiaro). Presto comunque ho pensato che la cosa più semplice è quella di sfruttare la proprietà RotateTransform , a sua volta figlia di RenderTransform, con l’attributo Angle posto rispettivamente a 180, -90 e 90 ottenendo così la freccia rivolta a sinistra, in alto e in basso, rispettivamente.
Nota. Detto en passant, il presente post costituisce un esempio pratico, pedestre, di queste a altre proprietà consimili, che altrimenti potrebbero esser viste quasi come uno sfizio estetizzante.
Le operazioni appena detta si possono compiere manualmente, copiando il Button con freccia sinistra e incollandolo altrove, apportando poi al nuovo venuto la rotazione richiesta, impostata sempre a manina. Ma la classica lampadina si è accesa, nottetempo: diamine, è più semplice ed elegante utilizzare uno stile a sua volta comprendente un ControlTemplate nel quale stabilire la shape poligonale mostrata poc’anzi. Il codice XAML è subito visto:
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="380" Width="302">
<Window.Resources>
<Style x:Key="pulsFreccia" TargetType="Button">
<Setter Property="MinHeight" Value="100" />
<Setter Property="MinWidth" Value="100" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Polygon Stroke="Black" Fill="Blue
Points="20,60 80,60 80,70 100,55 80,40 80,50 20,50"
Height="249" Width="266" Margin="0,12,12,0" Grid.RowSpan="3" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
Tale stile denominato pulsFreccia (ricordarsene più avanti) comprende tra i suoi Setter uno con Property=”Template” che a sua volta incastona nella tag di valore (Setter.Value) il ControlTemplate di tipo Button che- infine! – definisce il nostro Polygon frecciato, di dimensioni e orientamento (destro), direi, default.
A questo punto esaminiamo le prime tag a valle delle Grid.RowDefinitions:
<Polygon Stroke="Blue" Fill="Gold"
Points="20,60 80,60 80,70 100,55 80,40 80,50 20,50"
Margin="1,0,12,12" Grid.RowSpan="3" />
<Rectangle Fill="Chartreuse" Name="Rectangle1"
Stroke="#FF190000" Width="84" Margin="173,57,23,13" />
<Rectangle Fill="Chocolate" Margin="105,12,98,59" Name="Rectangle2"
Stroke="#FF190000" />
<Button Name="FrecciaDxt" Style="{StaticResource pulsFreccia}" Grid.Row="1"
Margin="22,8,144,8"/>
Nota. Per carità, non si sottilizzi né ci si scandalizzi sui valori adottati come quelli relativi ai margini. Questo è solo uno studio preliminare, didattico...
Come è subito chiaro, le prime tre figure inserite nella prima cella della griglia sono il poligono già visto più due rettangoli (infilati lì tanto per confronto). Segue, nella seconda cella (Grid.Row=”1”), un Button poligonale dall’eloquente nome “FrecciaDxt”, che pesca i suoi caratteri dallo stile in questione, mediante l’attributo specifico (per la cui sintassi rimando ai manuali e agli esempi MSDN):
Style="{StaticResource pulsFreccia}"
Subito sotto troviamo le definizioni degli altri tre pulsanti freccia (e già che ci siamo includo anche le tag di chiusura Grid e Window):
<Button Name="FrecciaSin" Style="{StaticResource pulsFreccia}" Grid.Row="1"
Margin="277,113,-141,13">
<Button.RenderTransform>
<RotateTransform Angle="180" />
</Button.RenderTransform>
</Button>
<Button Name="FrecciaSu" Style="{StaticResource pulsFreccia}" Grid.Row="2"
Margin="6,119,159,-117">
<Button.RenderTransform>
<RotateTransform Angle="-90" />
</Button.RenderTransform>
</Button>
<Button Name="FrecciaGiu" Style="{StaticResource pulsFreccia}" Grid.Row="2"
Margin="280,1,-114,1">
<Button.RenderTransform>
<RotateTransform Angle="90" />
</Button.RenderTransform>
</Button>
</Grid>
</Window>
I nomi “FrecciaSin”, “FrecciaSu” e “FrecciaGiu” sono parlanti e chiare le rispettive collocazioni – il primo nella stessa cella della FrecciaDxt, gli altri e uno accanto all’altro a riga 2 – e per capire tutto basterà dunque esaminare le tag Button.RenderTransform che incapsulano delle RotateTransform con tre scontati attributi Angle.
Ultima osservazione. Per semplicità ho indicato nello stile impostazioni essenziali, ma il bello degli stili & template è che possono essere arricchiti a volontà (con sfumature e magari trigger che agiscono quando si sfiora un pulsante) ottenendo un flessibilità e un risparmio di codice che anche questo esempio ribadisce.
Tutto il listato XAML
Lo riporto qui sotto per comodità dei più pigri.
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="380" Width="302">
<Window.Resources>
<Style x:Key="pulsFreccia" TargetType="Button">
<Setter Property="MinHeight" Value="100" />
<Setter Property="MinWidth" Value="100" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Polygon Stroke="Black" Fill="Blue
Points="20,60 80,60 80,70 100,55 80,40 80,50 20,50"
Height="249" Width="266" Margin="0,12,12,0" Grid.RowSpan="3" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="110*" />
<RowDefinition Height="110*" />
<RowDefinition Height="110*" />
</Grid.RowDefinitions>
<Polygon Stroke="Blue" Fill="Gold"
Points="20,60 80,60 80,70 100,55 80,40 80,50 20,50"
Margin="1,0,12,12" Grid.RowSpan="3" />
<Rectangle Fill="Chartreuse" Name="Rectangle1"
Stroke="#FF190000" Width="84" Margin="173,57,23,13" />
<Rectangle Fill="Chocolate" Margin="105,12,98,59" Name="Rectangle2"
Stroke="#FF190000" />
<Button Name="FrecciaDxt" Style="{StaticResource pulsFreccia}" Grid.Row="1"
Margin="22,8,144,8"/>
<Button Name="FrecciaSin" Style="{StaticResource pulsFreccia}" Grid.Row="1"
Margin="277,113,-141,13" Grid.RowSpan="2">
<Button.RenderTransform>
<RotateTransform Angle="180" />
</Button.RenderTransform>
</Button>
<Button Name="FrecciaSu" Style="{StaticResource pulsFreccia}" Grid.Row="2"
Margin="6,119,159,-117">
<Button.RenderTransform>
<RotateTransform Angle="-90" />
</Button.RenderTransform>
</Button>
<Button Name="FrecciaGiu" Style="{StaticResource pulsFreccia}" Grid.Row="2"
Margin="280,1,-114,1">
<Button.RenderTransform>
<RotateTransform Angle="90" />
</Button.RenderTransform>
</Button>
</Grid>
</Window>
Spostare controlli di una griglia con Grid.RowProperty / Grid.ColumnProperty
Come si sa Grid.RowProperty e Grid.ColumnProperty sono attached property, nel senso che la loro definizione nella tag che in XAML fa riferimento non ad una proprietà specifica dell’oggetto sito in una cella del contenitore Grid (come per esempio Margin) bensì ad una caratteristica del “padre” Grid. Di qui la sintassi dotted, Grid. eccetera, nonché l’obbligo di utilizzare nel codice una sintassi GetValue o SetValue, come nell’esempio seguente:
Dim Riga = Oggetto.GetValue(Grid.RowProperty) ' Ci dà la riga dell’Oggetto
Mentre con Margin si può ricorrere, semplicemente, a Oggetto.Margin
Nota. Volendo, anche Oggetto.GetValue(Margin) è lecito. Analogamente con SetValue.
L’ideuzza che qui mi permetto di suggerire a quanti, forse, non ci hanno ancora pensato è quella di sfruttare le due proprietà in questione per effettuare spostamenti di oggetti da cella a cella della griglia. Vengo al punto senza ulteriori lungaggini con questo esempio (relativo per semplicità a due Button):
Scambio fra due pulsanti
Private Sub ScambiaPuls(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs)
' Scambia posizione dei pulsanti puls1 e puls2
Dim Riga1 = Puls1.GetValue(Grid.RowProperty)
Dim Colonna1 = Puls1.GetValue(Grid.ColumnProperty)
With Puls1
.SetValue(Grid.RowProperty, Puls2.GetValue(Grid.RowProperty))
.SetValue(Grid.ColumnProperty, Puls2.GetValue(Grid.ColumnProperty))
End With
With Puls2
.SetValue(Grid.RowProperty, Riga1)
.SetValue(Grid.ColumnProperty, Colonna1)
End With
End Sub
Mi astengo da commenti, in quanto lo snippet è eloquente, idem per la sua traduzione in un metodo del tipo Sub ScambiaPulsanti(Puls1 As..., Puls2 As...). Piuttosto faccio notare che
In questo come in altri esempi gli oggetti modificati cambiano proprietà “ereditate” (attached) mantenendo intatta la loro peculiare identità, a partire dal nome, proprio come una persona che si sposta in un’altra località.
Prima di proseguire propongo un altro brano non privo di interesse:
For Each Ctrl In miaGrid.Children
' Tratta tutte e sole le TextBox di miaGrid
If TypeOf Ctrl Is TextBox Then
Riga = Ctrl.GetValue(Grid.RowProperty)
Colonna = Ctrl.GetValue(Grid.ColumnProperty)
MessageBox.Show(Ctrl.ToString & vbLf & _
"Riga: " & Riga & " – " & _
"Colonna: " & Colonna)
Next
Come indica il commento iniziale, il ciclo itera i figlioli di una certa griglia di nome miaGrid (melius abundare quam deficere con i nomi in WPF...) prendendo in considerazione solo le TextBox, segnalandone la Riga e la Colonna della cella di appartenenza. Tale tecnica discriminatoria, adattabile a altri tipi come Button ecc., può essere utilmente sfruttata in vari contesti, e non soltanto per gli spostamenti di cui qui si parla.
Accumulare pulsanti nella prima cella
Private Sub TuttiPulsACella0_0(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs)
' righe/colonne dei pulsanti indicati in VettPuls
Dim VettPuls() As Button = {Puls3, Puls4, Puls5, Puls2}
Dim N = VettPuls.GetUpperBound(0)
Dim VettRighe(N) As Integer ' = {1, 2, 3} ' Meglio un codice più GENERALE
Dim VettCol(N) As Integer ' = {1, 2, 3} ' " " " " " " "
Dim i = 0
For Each puls In VettPuls
' MessageBox.Show(puls.Name) ' Servita per debug
VettRighe(i) = puls.GetValue(Grid.RowProperty)
VettCol(i) = puls.GetValue(Grid.ColumnProperty)
i += 1
' Sposta il controllo nella cella (0, 0)
puls.SetValue(Grid.RowProperty, 0)
puls.SetValue(Grid.ColumnProperty, 0)
Next
Dim Msg = "Ripristino le posizioni originarie?"
i = 0
If MessageBox.Show(Msg, "", MessageBoxButton.YesNo) _
= MessageBoxResult.Yes Then
For Each puls In VettPuls
puls.SetValue(Grid.RowProperty, VettRighe(i))
puls.SetValue(Grid.ColumnProperty, VettCol(i))
i += 1
Next
End If
End Sub
La precedente curiosa routine prevede una matrice VettPuls di oggetti Button, identificati – ecco un punto di un certo interesse, direi – con il rispettivo nome (proprietà Name) dopo di che il primo ciclo For Each puls In VettPuls ne carica i valori di riga e colonna nei VettRighe e, rispettivamente, VettCol quindi prosegue sistemando tutti nella cella di coordinate nulle.
Nota. En passant, un accumulo di pulsanti non è una stramberia, perché può servire in altri contesti. Per esempio se si desidera presentare all’utente un comando alla volta. La qual cosa può esser fatta anche in ambiente Windows Form, ma mi sembra che WPF anche qui si presenti più versatile.
Va poi da sé che il susseguente ciclo, se ADR l’utente risponde affermativamente, ripristina le posizioni originarie dei nostri Button salvate in precedenza in VettRighe e VettCol. Si dirà che sarebbe stato più elegante una struttura Stack. OK, touché, ma chi ci tiene può accomodarsi con facili modifiche, qual che conta essendo, a modesto mio parere, più la sostanza che la forma...
Piazzare un controllo in un “buco”
Sub SpostaPulsante(ByVal Puls As Button, RigaBuco As Integer, ColonnaBuco As Integer)
Dim Riga As Integer, Colonna As Integer
Riga = Puls.GetValue(Grid.RowProperty)
Colonna = Puls.GetValue(Grid.ColumnProperty)
If Riga = RigaBuco And System.Math.Abs(Colonna - ColonnaBuco) = 1 Then
Puls.SetValue(Grid.ColumnProperty, ColonnaBuco)
ColonnaBuco = Colonna
ElseIf Colonna = _
ColonnaBuco And System.Math.Abs(Riga - RigaBuco) = 1 Then
Puls.SetValue(Grid.RowProperty, RigaBuco)
RigaBuco = Riga
End If
End Sub
Quest’ultima routine proposta, stavolta con argomenti RigaBuco e ColonnaBuco, corrisponde di fatto alla prima che mi ha offerto lo spunto di impiegare le attached property in questione. Si è trattato del famoso Gioco del 15. Per il quale ho implementato una Window contenente una Grid 4 x 4, nelle cui celle ho collocato quindici Button, escludendo l’ultima in basso a destra – il BUCO - di coordinate (3, 3), ciascuno dei quali reca sul gobbo numeretti da 1 a 15 dislocati a caso. La tabellina seguente schematizza la situazione, con tutti i pulsanti dipinti di blu, salvo il buco:
|
10
|
6
|
14
|
13
|
|
12
|
15
|
5
|
7
|
|
4
|
9
|
11
|
1
|
|
3
|
8
|
2
|
|
Premesso che tutti i 15 pulsanti hanno in comune una Sub d’evento Click sostanzialmente conforme alla precedente SpostaPulsante, qui giunti l’analisi del come e perché solo il pulsante prossimo al buco vi può essere spostato viene lasciata per esercizio. Per non tediare e non per sadismo, giuro.
Gianni Giaccaglini
P.S. -Chi fosse interessato a usare il pur futile giochino del 15 a titolo di relax può chiedermelo per posta (giannigiac@tin.it).
Simulazione di finali di un campionato di calcio
Il procedimento di questo programmino, creato in occasione degli ultimi Mondiali sudafricani, ha origini molto lontane. Inizialmente l’ho sviluppato in un modello Excel dotato di macro VBA, più avanti ho sfruttato un controllo DataGridView inserito in una Window Form. In entrambi i casi ho ripreso il ben noto schema piramidale inserito nelle celle dell’uno o dell’altro.
Esaminando infine la griglia (Grid) di una finestra WPF mi sono infine detto che la si può direttamente utilizzare, mutatis mutandis, in modo più diretto e ottenendo una creatura meno pesante.
L’idea base si può grossomodo schematizzare con la tabella seguente:
|
|
|
Valori
|
|
|
|
|
|
|
|
Prima squadra
|
|
6
|
|
|
|
|
|
|
|
|
|
|
|
Semifinalista 1
|
|
|
|
|
|
Seconda squadra
|
|
6
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Finalista 1
|
|
|
|
Terza squadra
|
|
6
|
|
|
|
|
|
|
|
|
|
|
|
Semifinalista 2
|
|
|
|
|
|
Quarta squadra
|
|
6
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Campione
|
|
Quinta squadra
|
|
6
|
|
|
|
|
|
|
|
|
|
|
|
Semifinalista 3
|
|
|
|
|
|
Sesta squadra
|
|
6
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Finalista 2
|
|
|
|
Settima squadra
|
|
6
|
|
|
|
|
|
|
|
|
|
|
|
Semifinalista 4
|
|
|
|
|
|
Ottava squadra
|
|
6
|
|
|
|
|
|
|
Nota. Per semplicità e... pigrizia mi sono limitato ai quarti di finale (cosa tra l’altro tipica di altre finali, per esempio di atletica), ma chi ne ha voglia può ampliare il discorso agli ottavi, aggiungendo sulla sinistra una colonna con 16 finaliste.
Dalla tabella precedente non è arduo evincere di tradurre l’idea in una Window WPF al centro della quale si abbia un container Grid (ma come avete fatto a indovinare?), nelle cui celle si trovano delle TextBox, con aggiunta di uno o più pulsanti di comando.
Chi fosse interessato al programmino per vedere da vicino l'effetto che fa può chiedermelo direttamente tramite email: giannigiac@tin.it .
Al lancio si presenta sostanzialmente conforme alla tabella precedente, con aggiunta di un pulsante in alto.
Si noterà subito che i nomi delle 8 squadre in ballo sono generici, mentre i valori, che esprimono valutazioni (si suggeriscono voti da 1 a 10) su ciascuna di esse, sono allineati alla sufficienza per tutti.
Nota Non ho previsto, per brevità, nessun controllo sui valori che debbono essere rigorosamente numerici (ma utenti così idioti ce ne sono ancora in giro?).
Va da sé che prima di avviare la Simulazione col pulsante così etichettato, occorre sostituire “Prima squadra”, “Seconda squadra” ecc. coi nomi reali, idem per i voti uniformati sul 6. Dopo di che l’algoritmo introduce un fattore di (pseudo)casualità per tener conto della aleatorietà tipica delle competizioni calcistiche (la palla è tonda, come si sa). Tale procedimento verrà descritto insieme al codice, anticipo solo che esso nelle TextBox da “Semifinalista1” a “Semifinalista4” inserisce i rispettivi vincitori fra Prima e seconda squadra, Terza e quarta squadra ecc. Cosa accade nel resto della piramide degradante segue la stessa logica, ergo la faccenda si commenta da sola.
Il codice XAML
La struttura dei contenitori facenti parte della nostra Window1 (o, in WPF 4.0 , MainWindow) prevede un DockPanel che al suo interno incapsula due Grid, la prima di una sola riga e tre colonne, potendo così ospitare fino a tre pulsanti, di cui uno soltanto viene qui utilizzato (chi vuole ne può aggiungere altri) mentre la seconda corrisponde alla tabella su esposta.
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Finali Campionato mondiale" Height="520" Width="500">
<DockPanel LastChildFill="True" Background="LightGreen">
<Grid DockPanel.Dock="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="95.743*" />
<ColumnDefinition Width="95.743*" />
<ColumnDefinition Width="95.743*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20*" />
</Grid.RowDefinitions>
<Button Click="AvviaSimulaz" Grid.Column="1" Margin="10" Height="30">
Simulazione
</Button>
</Grid>
<Grid Name="GrigliaSquadre" Width="420.084" Height="400">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120*" />
<ColumnDefinition Width="6*" />
<ColumnDefinition Width="78.179*" />
<ColumnDefinition Width="2.858*" />
<ColumnDefinition Width="95.743*" />
<ColumnDefinition Width="2.858*" />
<ColumnDefinition Width="95.743*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="30*" />
<RowDefinition Height="62*" />
<RowDefinition Height="145.5*" />
</Grid.RowDefinitions>
<Label Content ="Valori:" Foreground ="Yellow" Background="Black" VerticalContentAlignment="Center" FontSize="14" Width="49.976" HorizontalAlignment="Right" />
<TextBox Name="Squad1" Grid.Row="1" Grid.Column="0" Background="LightBlue">
Prima squadra
</TextBox>
<Label Grid.Row="1" Grid.Column="1" Foreground ="DarkRed" FontWeight="Bold" Grid.ColumnSpan="4" Margin="32.657,0,2.648,0">SEMIFINALI</Label>
<Label Grid.Row="3" Grid.Column="4" Foreground ="DarkRed" FontWeight="Bold" Grid.ColumnSpan="2" Margin="2.848,0,0,0">FINALI</Label>
<Label Grid.Row="6" Grid.Column="5" Foreground ="DarkRed" FontWeight="Bold" Grid.ColumnSpan="2" Margin="0,25.5,2.848,2.003" Grid.RowSpan="2">CAMPIONE</Label>
<TextBox Name="Va11" Text="6" FontSize="9" Grid.Row="1" BorderThickness="4"
HorizontalAlignment="Right" Width="32.756" TextAlignment="Center" />
<TextBox Name="Squad2" Grid.Row="3" Grid.Column="0" Background="LightBlue">
Seconda squadra
</TextBox>
<TextBox Name="Va12" Text="6" FontSize="9" Grid.Row="3" BorderThickness="4"
HorizontalAlignment="Right" Width="32.756" TextAlignment="Center" />
<TextBox Name="Squad3" Grid.Row="5" Grid.Column="0" Background="LightBlue">
Terza squadra
</TextBox>
<TextBox Name="Va13" Text="6" FontSize="9" Grid.Row="5" BorderThickness="4"
HorizontalAlignment="Right" Width="32.756" TextAlignment="Center" />
<TextBox Name="Squad4" Grid.Row="7" Grid.Column="0" Background="LightBlue">
Quarta squadra
</TextBox>
<TextBox Name="Va14" Text="6" FontSize="9" Grid.Row="7" BorderThickness="4"
HorizontalAlignment="Right" Width="32.756" TextAlignment="Center" />
<TextBox Name="Squad5" Grid.Row="9" Grid.Column="0" Background="LightBlue">
Quinta squadra
</TextBox>
<TextBox Name="Va15" Text="6" FontSize="9" Grid.Row="9" BorderThickness="4"
HorizontalAlignment="Right" Width="32.756" TextAlignment="Center"/>
<TextBox Name="Squad6" Grid.Row="11" Background="LightBlue">
Sesta squadra
</TextBox>
<TextBox Name="Val6" Text="6" FontSize="9" Grid.Row="11" BorderThickness="4"
HorizontalAlignment="Right" Width="32.756" TextAlignment="Center" />
<TextBox Name="Squad7" Grid.Row="13" Grid.Column="0" Background="LightBlue">
Settima squadra
</TextBox>
<TextBox Name="Va17" Text="6" FontSize="9" Grid.Row="13" BorderThickness="4"
HorizontalAlignment="Right" Width="32.756" TextAlignment="Center" />
<TextBox Name="Squad8" Grid.Row="15" Grid.Column="0" Background="LightBlue">
Ottava squadra
</TextBox>
<TextBox Name="Va18" Text="6" FontSize="9" Grid.Row="15" BorderThickness="4"
HorizontalAlignment="Right" Width="32.756" TextAlignment="Center"/>
<TextBox Name="Semfin1" Grid.Row="2" Grid.Column="2" Background="Aquamarine" Grid.ColumnSpan="2" Margin="0,0,2.648,0">
Semifinalista 1
</TextBox>
<TextBox Name="Semfin2" Grid.Row="6" Grid.Column="2" Background="Aquamarine" Grid.ColumnSpan="2" Margin="0,0,2.648,0">
Semifinalista 2
</TextBox>
<TextBox Name="Semfin3" Grid.Row="10" Grid.Column="2" Background="Aquamarine" Grid.ColumnSpan="2" Margin="0,0,2.648,0">
Semifinalista 3
</TextBox>
<TextBox Name="Semfin4" Grid.Row="14" Grid.Column="2" Background="Aquamarine" Grid.ColumnSpan="2" Margin="0,0,2.648,0">
Semifinalista 4
</TextBox>
<TextBox Name="Fin1" Grid.Row="4" Grid.Column="4" Background="LightBlue">
Finalista 1
</TextBox>
<TextBox Name="Fin2" Grid.Row="12" Grid.Column="4" Background="LightBlue">
Finalista 2
</TextBox>
<TextBox Name="Campione" Grid.Row="8" Grid.Column="6" Foreground="White" Background="DarkBlue" >
Campione
</TextBox>
</Grid>
</DockPanel>
</Window>
L’analisi dello schema XAML è affidato al commento autonomo del lettore, faccio solo notare i nomi parlanti affibbiati alle varie TextBox: Squad1, Squad2, ..., Squad8 (le 8 squadre ovviamente), Val1, Val2, ..., Val8 (le rispettive valutazioni) e via di seguito con Semfin1, ..., Semfin4 poi Fin1 e Fin2 fino al Campione.
Tutto il codice VB, routine per routine
Premesso che al Button etichettato Simulazione, anonimo perché oltretutto è solo soletto, viene associata con Click="AvviaSimulaz" l’omonima routine, secondo una sintassi più consona all’ambiente WPF, qui sotto riporto l’intero listato.
Class Window1
Dim SquadreValori As New Dictionary(Of String, Double)
Dim SquadValCaricate = False
Sub CaricaSquadreValori()
SquadreValori.Add(Squad1.Text, CType(Va11.Text, Double))
SquadreValori.Add(Squad2.Text, CType(Va12.Text, Double))
SquadreValori.Add(Squad3.Text, CType(Va13.Text, Double))
SquadreValori.Add(Squad4.Text, CType(Va14.Text, Double))
SquadreValori.Add(Squad5.Text, CType(Va15.Text, Double))
SquadreValori.Add(Squad6.Text, CType(Val6.Text, Double))
SquadreValori.Add(Squad7.Text, CType(Va17.Text, Double))
SquadreValori.Add(Squad8.Text, CType(Va18.Text, Double))
SquadValCaricate = True
End Sub
Function Vincitore(ByVal Rivale1 As String, ByVal Rivale2 As String) As String
If SquadreValori(Rivale1) * (1 + 1.5 * Rnd()) > _
SquadreValori(Rivale2) * (1 + 1.5 * Rnd()) Then
Return Rivale1
Else
Return Rivale2
End If
End Function
Sub AggiornaValori()
SquadValCaricate = False
SquadreValori.Clear()
CaricaSquadreValori()
End Sub
Private Sub AvviaSimulaz(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
If Not SquadValCaricate Then
CaricaSquadreValori()
End If
AggiornaValori()
' Ottavi (o come diavolo si chiamano)
Randomize()
Semfin1.Text = Vincitore(Squad1.Text, Squad2.Text)
Semfin2.Text = Vincitore(Squad3.Text, Squad4.Text)
Semfin3.Text = Vincitore(Squad5.Text, Squad6.Text)
Semfin4.Text = Vincitore(Squad7.Text, Squad8.Text)
' Semifinali
Fin1.Text = Vincitore(Semfin1.Text, Semfin2.Text)
Fin2.Text = Vincitore(Semfin3.Text, Semfin4.Text)
' Finali
Campione.Text = Vincitore(Fin1.Text, Fin2.Text)
End Sub
End Class
Commenti tacitiani.
Una serie di SquadreValori.Add(Squad1.Text, CType(Va11.Text, Double)) e seguenti carica nella Dictionary SquadreValori, definita a livello Dichiarazioni, i testi delle squadre reali – “Brasile”, “Germania”... - che l’utente avrà la bontà d’inserire al posto dei generici “Prima squadra”, “Seconda squadra, ... in veste di chiave, mentre i corrispondenti valori sono attinti dai vari Val1.Text, Val2.Text, ... previo cast nel formato Double anche a beneficio dei pignoli che volessero assegnare voti tipo 8,45.
Il clou si ha nella funzione Vincitore, di cui riporto nuovamente le istruzioni qui sotto per comodità di chi legge:
If SquadreValori(Rivale1) * (1 + 1.5 * Rnd()) > _
SquadreValori(Rivale2) * (1 + 1.5 * Rnd()) Then
Return Rivale1
Else
Return Rivale2
End If
Nota. Chi vuole può sostituire la pur vetusta ma sintetica Rnd con la più moderna System.Random.Next...
L’algoritmo pesca dalla dictionary SquadreValori la valutazione pertinente alle chiavi Rivale1 e Rivale2 cui si aggiunge un pizzico di aleatorietà variabile da 0 a 1,5. Dal confronto emerge come Vincitore una delle due rivali.
Compreso questo meccanismo, il mestiere della routine d’evento AvviaSimulaz appare chiaro, pertanto non voglio tediare la gente con ulteriori chiose.
Una variante più stringata (ma cervellotica)
Potrebbe piovere qualche critica da chi ama la compattezza, per il fatto che tutte le caselle di testo vengono chiamate in gioco per nome, ad una ad una. Ma tale scelta a mio parere era inevitabile, dato il carattere sparso delle caselle di testo nella nostra griglia. Comunque a scopo di dibattito ecco una routine avente lo scopo di esplorare e segnalare con delle MessageBox tutte e sole le TextBox delle prime 8 squadre affiancandole ai rispettivi valori:
Sub EsploraSquadEtValori
Static swPrimogiro As Boolean = False
Dim DepCtrl
Dim DepNomeCtrl = ""
For Each Ctrl In GrigliaSquadre.Children
If TypeOf Ctrl Is TextBox Then
Dim NomeCtrl = Ctrl.Name.ToString
If NomeCtrl <> "" And Ctrl.Getvalue(Grid.ColumnProperty) = 0 Then
If Not swPrimogiro Then
DepCtrl = Ctrl
DepNomeCtrl = NomeCtrl
Else
MessageBox.Show(DepNomeCtrl & " - " & NomeCtrl)
End If
swPrimogiro = Not swPrimogiro
End If
End If
Next
End Sub
Il punto è che GrigliaSquadre.Children fornisce tutti i controlli che tale griglia incorpora, di qui la necessità di discriminare quelli di tipo TextBox (TipeOf Ctrl Is TextBox) nonché quelli che giacciono in colonna 0.
Il resto del procedimento si basa sul fatto che, si badi bene, pure i valori sono in colonna 0 (definizione implicita: rivedere tali tag, notando in esse l’assenza dell’attributo Grid.Column) e, conseguentemente, l’esplorazione alterna Squad1 con Val1, Squad2 con Val2, ..., Squad8 con Val8. Infine il gioco dello switch di primo giro swPrimogiro in combutta coi depositi DepCtrl e DepNomeCtrl è un classico su cui non mi dilungo.
Altre possibilità
Ai più esperti affido il compitino di sostituire MessageBox.Show(...) con l’aggiornamento della Dictionary SquadreValori. Ma ne vale la pena?
Piuttosto, chi ne avesse voglia e tempo potrebbe cimentarsi con modifiche e/o ampliamenti come il controllo dei valori (con rigetto dei non numerici) e la finale per il terzo e quarto posto.
Un’ulteriore possibilità (che ho implementato temporibus illis in Excel) potrebbe consistere nell’iterazione in un numero N sufficientemente elevato di cicli For i = 1 To N della Sub AvviaSimulaz aggiornando ad ogni giro un array Vett(7, 1) recante le varie squadre nella prima colonna e, nella seconda, il conteggio delle vincite. In tal modo, al termine, la squadra che avrà vinto più campionati simulati potrebbe considerarsi come il vincitore più probabile.
Gianni Giaccaglini