Alessandro Del Sole's Blog

{ A programming space about Microsoft® .NET® }
posts - 1908, comments - 2047, trackbacks - 352

My Links

News

Your host

This is me! Questo spazio è dedicato a Microsoft® .NET®, di cui sono molto appassionato :-)

Cookie e Privacy

Disabilita cookie ShinyStat

Microsoft MVP

My MVP Profile

Microsoft Certified Professional

Microsoft Specialist

Xamarin Certified Mobile Developer

Il mio libro su VB 2015!

Pre-ordina il mio libro su VB 2015 Pre-ordina il mio libro "Visual Basic 2015 Unleashed". Clicca sulla copertina per informazioni!

Il mio libro su WPF 4.5.1!

Clicca sulla copertina per informazioni! E' uscito il mio libro "Programmare con WPF 4.5.1". Clicca sulla copertina per informazioni!

These postings are provided 'AS IS' for entertainment purposes only with absolutely no warranty expressed or implied and confer no rights.
If you're not an Italian user, please visit my English blog

Le vostre visite

I'm a VB!

Guarda la mia intervista a Seattle

Follow me on Twitter!

Altri spazi

GitHub
I miei progetti open-source su GitHub

Article Categories

Archives

Post Categories

Image Galleries

Privacy Policy

WPF: realizzare una FolderBrowserDialog, con dependency properties e routed events, in Visual Basic 2010

Sappiamo bene che WPF non offre un controllo nativo di tipo FolderBrowserDialog, per sfogliare le cartelle su disco, come invece fa Windows Forms. Un'idea è quindi quella di ricorrere all'interoperabilità, con alcuni accorgimenti tipici di WPF. Lo scopo è quindi quello di creare una classe con caratteristiche tipiche di WPF e, per farlo, in Visual Basic 2010 creiamo un progetto di tipo WPF User Control. Questo tipo di template ci aiuta semplicemente nell'aggiungere i riferimenti necessari a WPF. Fatto questo, aggiungiamo i riferimenti per l'interoperabilità con Windows Forms, quindi agli assembly System.Windows.Forms.dll e WindowsFormsIntegration.dll. Fatto questo, rimuoviamo qualunque file dal progetto, XAML o codice.

Aggiungiamo ora un nuovo file di codice e chiamiamolo WpfFolderBrowserDialog.vb. Ora cominciamo a ragionare in termini di WPF. Poiché il controllo originario FolderBrowserDialog che andiamo a "wrappare" da Windows Forms ha delle sue proprietà (es. SelectedPath o Description), può essere interessante esporle all'esterno di modo che, quando si utilizza il controllo, l'utente sia in grado di utilizzare i risultati del controllo stesso o impostarne le proprietà. In teoria questo può essere fatto implementando proprietà classiche che girino all'esterno quelle del controllo Win Forms. Questo approccio però non è corretto in termini di data-binding. Ad esempio, potremmo voler collegare il nome della cartella selezionata a un TextBlock oppure tramite una TextBox inviare al controllo un valore di default.

Per fare questo si può ricorrere alle dependency properties che, come forse sapete, sono speciali proprietà di WPF che si utilizzano tipicamente in elementi visuali che devono notificare all'interfaccia cambiamenti avvenuti sull'origine dati (la proprietà). Le dependency properties sono esposte dalla classe FrameworkElement, una delle più importanti classi base in WPF. Per cui cominciamo a scrivere codice in questo modo:

Imports System.Windows.Forms

Public Class FolderBrowserDialog
    Inherits FrameworkElement

Una dependency property è un campo privato di tipo DependencyProperty che va registrata utilizzando il metodo Register come nel seguente codice, relativo alla proprietà SelectedPath:

    'Argomenti:
    '1. Nome della proprietà
    '2. Tipo di dato interessato
    '3. Oggetto proprietario della proprietà
    Private Shared SelectedPathProperty As DependencyProperty =
                   DependencyProperty.Register("SelectedPath"GetType(String), GetType(FolderBrowserDialog))
Tale dependency property viene poi esposta o impostata attraverso una proprietà .NET classica, che utilizza due metodi: GetValue (che ottiene il valore) e SetValue (che imposta il valore della dependency property):
    Public Property SelectedPath As String
        Get
            Return CStr(GetValue(SelectedPathProperty))
        End Get
        Set(ByVal value As String)
            SetValue(SelectedPathProperty, value)
        End Set
    End Property
In questo modo saremo in grado non solo di restituire al consumer del controllo il valore della proprietà SelectedPath della FolderBrowserDialog, ma anche di impostarlo in data-binding.
Se siete a digiuno sulle dependency properties, il miglior punto per iniziare è la MSDN Library.
Ora consideriamo il fatto che in WPF alcuni controlli utente possono essere il contenuto di altri controlli (come avviene per Button.Content). Per questo può essere importante implementare un routed event che venga scatenato quando la proprietà SelectedPath cambia.
Per fare questo, definiamo un oggetto di tipo RoutedEvent che viene registrato tramite il metodo EventManager.RegisterRoutedEvent e che poi viene esposto tramite un custom event. 
Anche in questo caso, la MSDN Library offre tutto ciò che c'è da sapere sui routed events. Ecco il codice:
    'Argomenti:
    '1. nome dell'evento
    '2. strategia di routing (vedi MSDN)
    '3. tipo gestore di evento
    '4. proprietario dell'evento
    Private Shared PathChangedEvent As RoutedEvent = EventManager.
                                                     RegisterRoutedEvent("PathChanged",
                                                                         RoutingStrategy.Bubble,
                                                                         GetType(RoutedEventHandler),
                                                                         GetType(FolderBrowserDialog))

    Public Custom Event PathChanged As RoutedEventHandler
        AddHandler(ByVal value As RoutedEventHandler)
            Me.AddHandler(PathChangedEvent, value)
        End AddHandler

        RemoveHandler(ByVal value As RoutedEventHandler)
            Me.RemoveHandler(PathChangedEvent, value)
        End RemoveHandler

        RaiseEvent(ByVal sender As ObjectByVal e As System.Windows.RoutedEventArgs)
            Me.RaiseEvent(e)
        End RaiseEvent
    End Event
Può essere poi conveniente scrivere un metodo che sollevi l'evento, come questo:
    Private Sub RaisePathChangedEvent()
        Dim newEventArgs As New RoutedEventArgs(FolderBrowserDialog.PathChangedEvent)
        MyBase.RaiseEvent(newEventArgs)
    End Sub
Quindi, un metodo ShowDialog che, per affinità con le dialog di WPF, restituisce Nullable(Of Boolean) a seconda del risultato della dialog. Osservate come il routed event vena scatenato alla selezione del percorso:
    Public Function ShowDialog() As Nullable(Of Boolean)
        Dim fb As New System.Windows.Forms.FolderBrowserDialog
        With fb
            If .ShowDialog = DialogResult.OK Then
                Me.SelectedPath = .SelectedPath
                RaisePathChangedEvent()
                Return True
            ElseIf .ShowDialog = DialogResult.Cancel Then
                Return False
            Else
                Return Nothing
            End If
        End With
    End Function
Da ultimo, il costruttore in due overload, uno dei quali accetta degli argomenti che impostano le proprietà:
    Public Sub New(ByVal Description As StringByVal ShowNewFolderButton As BooleanByVal RootFolder As Environment.SpecialFolder)
        Me.Description = Description
        Me.ShowNewFolderButton = ShowNewFolderButton
        Me.RootFolder = RootFolder
    End Sub

    'Un costruttore vuoto cosicché si possa usare
    'anche da XAML
    Public Sub New()

    End Sub
Se vogliamo fare un esempio di utilizzo di questa classe, abbiamo due strade. A fattor comune è necessario creare un progetto WPF e aggiungere un riferimento alla libreria creata in precedenza.
La prima strada è quella di istanziare e usare la classe in un modo classico, come questo:
        Dim fbd As New WpfFolderBrowserDialog.FolderBrowserDialog
        Dim result As Boolean? = fbd.ShowDialog()

        If result = True Then Me.Title = fbd.SelectedPath
        fbd = Nothing
Ricordando, comunque, che SelectedPath è utilizzabile anche da XAML. La seconda strada è quella di usare la modalità dichiarativa, in questo modo, all'interno del codice XAML della finestra:
<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfFolderBrowserDialog;assembly=WpfFolderBrowserDialog"
    Title="MainWindow" Height="350" Width="525"  
    >
    <Window.Resources>
        <!-- Un'istanza della classe -->
        <local:FolderBrowserDialog x:Key="Fbd1"/>
    </Window.Resources>
    <StackPanel>
        <Button Content="Sfoglia" Height="30" Margin="5"
                Name="Button1" Width="80"/>
        <!-- Il testo è in binding con SelectedPath, che è dependency property-->
        <TextBlock Height="30" Margin="5" 
                   VerticalAlignment="Top" Width="200" Text="{Binding Path=SelectedPath, Source={StaticResource Fbd1}}" />
    </StackPanel>
</Window>
Il gestore dell'evento Click del pulsante è abbastanza semplice, l'unica "finezza" è che ricorre a una statement lambda di VB 2010 per gestire l'evento PathChanged invece che scrivere un handler separato:
    Private Sub Button1_Click(ByVal sender As System.ObjectByVal e As System.Windows.RoutedEventArgsHandles Button1.Click
        Dim fbd As WpfFolderBrowserDialog.FolderBrowserDialog = CType(Me.FindResource("Fbd1"), WpfFolderBrowserDialog.FolderBrowserDialog)

        'Tramite statement lambda specifico il delegate che gestisce l'evento
        AddHandler fbd.PathChanged, Sub()
                                        MessageBox.Show("Path has been changed")
                                    End Sub
        fbd.ShowDialog()
    End Sub
Ovviamente è un esempio, sicuramente migliorabile. L'intento era più che altro dare uno spunto su dependency properties e routed events.
Alessandro

Print | posted on sabato 9 ottobre 2010 21:06 | Filed Under [ Visual Basic Windows Presentation Foundation ]

Powered by:
Powered By Subtext Powered By ASP.NET