lunedì 1 febbraio 2010

Briciole di WPF: Finestre non rettangolari, WPF vs Windows Forms

Questa briciola vuole mettere in evidenza il completo supporto alla trasparenza presente in WPF.

Nel fare questo effettueremo un confronto con il supporto fornito nelle Windows Forms nella creazione di finestre non rettangolari.

Partiamo da WPF. Creare finestre non rettangolari in WPF è assolutamente banale. Prendiamo ad esempio il seguente XAML:

  1. <Window x:Class="MainWindow"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.     Title="MainWindow" Height="350" Width="525" Background="Transparent" WindowStyle="None" AllowsTransparency="True" MouseLeftButtonDown="Window_MouseLeftButtonDown">
  5.     <Canvas>
  6.         <Canvas.Background>
  7.             <ImageBrush ImageSource="./images/Drawing.png"></ImageBrush>
  8.         </Canvas.Background>
  9.         <Button Canvas.Top="91" Canvas.Left="100" Height="31" Width="70" Click="Button_Click">Premi</Button>
  10.         <TextBlock Canvas.Left="258" Canvas.Top="165" Height="30" Name="TextBlock1" Width="175" TextAlignment="Center" FontSize="20"/>
  11.     </Canvas>
  12. </Window>

L’immagine utilizzata come sfondo per il canvas principale è un’immagine con la trasparenza (nel nostro caso un png).
Per fare in modo che la Window supporti completamente la trasparenza è necessario:

  1. Impostare il backgroung della window a Trasparent: 
         Background="Transparent"
  2. Impostare lo stile della finestra senza i bordi e la caption:
         WindowStyle="None"
  3. Ed attivare la trasparenza:
         AllowsTransparency="True"

In particolare, se impostiamo la proprietà AllowsTransparency a true, è fondamentale impostare la proprietà WindowStyle a None, altrimenti otteniamo un errore in fase di esecuzione.

In questo modo, otteniamo una finestra con una forma non rettangolare, ma non abbiamo la possibilità di spostare la finestra, in quanto non è presente la caption.

Esempio di finestra non rettangolare

Per poter attivare lo spostamento della finestra cliccando su qualsiasi punto della finestra e trascinando la stessa è sufficiente scrivere il seguente codice come gestore dell’evento MouseLeftButtonDown:

  1. Private Sub Window_MouseLeftButtonDown(ByVal sender As System.Object, _
  2.                                        ByVal e As System.Windows.Input.MouseButtonEventArgs)
  3.     Me.DragMove()
  4. End Sub

Grazie ai routed events di WPF, la finestra viene spostata anche se ci posizioniamo con il mouse su qualsiasi controllo della nostra interfaccia che non gestisce il MouseLeftButtonDown. Nella solution che riporto nel download in fondo al post abbiamo un textblock e un button: possiamo spostare la finestra se clicchiamo sul textblock ma non sul bottone (quest’ultimo ha il gestore dell’evento click).

Vediamo, ora, come realizzare la stessa interfaccia con una windows forms. Le tecniche che si possono utilizzare per realizzare una form non rettangolare con Windows Forms sono sostanzialmente due: disegnando tramite le classi Drawing la region dell’interfaccia grafica nel metodo OnPaint della finestra  oppure, più semplicemente, impostando nel seguente modo le proprietà della finestra:

  1. Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
  2.     Me.BackgroundImage = New Bitmap(My.Resources.Drawing, New Size(Me.Width, Me.Height))
  3.     Me.FormBorderStyle = FormBorderStyle.None
  4.     Me.TransparencyKey = Color.FromArgb(0, 255, 255)
  5. End Sub

In questo caso la nostra immagine è presente nelle risorse del progetto (ma potrebbe essere letta anche da file) ed ha un colore scelto da noi (celeste (0,255,255) in questo caso) per indicare la parte di finestra trasparente.

Il risultato è una finestra di questo tipo:

Windows Forms non rettangolareIl problema nasce quando vogliamo abilitare il trascinamento della finestra da parte dell’utente. In questo caso dobbiamo simulare ciò che accade quando è presente la caption e l’utente è in grado di cliccare con il mouse al di sopra della stessa e trascinare l’intera finestra tenendo premuto il tasto sinistro del mouse.

Per fare questo dobbiamo utilizzare P/Invoke ed importare le seguenti funzioni:

  1. <DllImport("user32.dll")> _
  2. Private Shared Function SendMessage( _
  3.                 ByVal hWnd As IntPtr, _
  4.                 ByVal Msg As UInteger, _
  5.                 ByVal wParam As IntPtr, _
  6.                 ByVal lParam As IntPtr) As IntPtr
  7. End Function
  8.  
  9. <DllImport("user32.dll")> _
  10. Private Shared Function ReleaseCapture() As Boolean
  11. End Function
  12.  
  13. Const WM_NCLBUTTONDOWN As UInteger = &HA1
  14. Const HTCAPTION As Integer = 2

Potete trovare maggiori riferimenti alle API utilizzate ai seguenti link: SendMessage e ReleaseCapture.

Utilizziamo queste API contenute nella user32.dll per simulare le azioni che esegue l’utente al di sopra della caption della finestra nel momento in cui decide di spostarla:

  1. Private Sub MoveWindow()
  2.     Dim lngReturnValue As IntPtr
  3.     Call ReleaseCapture()
  4.     lngReturnValue = SendMessage(Me.Handle, WM_NCLBUTTONDOWN, New IntPtr(HTCAPTION), New IntPtr(0))
  5. End Sub

Utilizziamo questa funzione all’interno del gestore di evento mousemove della form e di tutti i controlli che non hanno interazione diretta con l’utente (nel nostro caso la label).

  1. Private Sub Form1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
  2.             Handles Me.MouseMove
  3.     If e.Button = System.Windows.Forms.MouseButtons.Left Then
  4.         MoveWindow()
  5.     End If
  6. End Sub
  7.  
  8. Private Sub TextBlock1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
  9.             Handles TextBlock1.MouseMove
  10.     If e.Button = System.Windows.Forms.MouseButtons.Left Then
  11.         MoveWindow()
  12.     End If
  13. End Sub

In definitiva possiamo osservare che, per sua natura intrinseca WPF ci consente di realizzare la form non rettangolare agendo semplicemente nello XAML e sfruttando le caratteristiche di trasparenza dei formati grafici, inoltre ci semplifica la vita nella gestione del drag and drop della finestra non costringendoci all’utilizzo di P/Invoke (che tra le altre cose rende l’applicazione poco portabile).

Nessun commento: