Dziś będzie o tym jak w WPF, możemy stworzyć sobie user kontrolkę. Kontrolka ta zawiera zarówno logikę jak i wygląd. Głównie składa się ją z już istniejących kontrolek udostępnianych przez WPF.

Nowa kontrolka

Visual Studio daje nam wsparcie do tworzenia tego typu kontrolek, więc wystarczy, że dodamy nowy element tego typu do projektu.

Po ustaleniu nazwy naszej nowej kontrolki możemy przystąpić do pracy (z braku ciekawego przykładu, posłużę się tym zawartym w książce – WPF Unleashed)

Zakodujmy sobie zatem kontrolkę, która pokaże nam przycisk umożliwiający wybór pliku oraz pole TextBox, które po wybraniu pliki będzie zawierać ścieżkę do niego.

<DockPanel>

    <Button Content=”Wybierz …” DockPanel.Dock=”Right” x:Name=”button” Click=”button_Click”/>

    <TextBox MinWidth=”{Binding ActualWidth, ElementName=button}” Margin=”0,0,2,0″ x:Name=”fileNameTextBox”/>       

DockPanel>

Tyle w XAMLu. W CodeBehind też niewiele musimy napisać.

private void button_Click(object sender, RoutedEventArgs e)

{

    using(var ofd = new OpenFileDialog())

    {

        if (ofd.ShowDialog() != DialogResult.OK) return;

        FileName = ofd.FileName;

    }

}

 

public string FileName

{

    get { return fileNameTextBox.Text; }

    set { fileNameTextBox.Text = value; }

}

I to już w zasadzie tyle, ale…tak przygotowana kontrolka nie pozwala użyć wielu z ciekawych rozwiązań dostępnych w WPF. Dopracujmy ją zatem.

Wartość domyślna

Nasza kontrolka “ładnie działa”, ale ktoś może się bardzo zdziwić jej używając. Załóżmy, że ktoś napisze tak:

<WpfExamDemo:FileOpen x:Name=”fip” Content=”C:\autoexec.bat”/>

lub tak

<WpfExamDemo:FileOpen x:Name=”fip”>c:\autoexec.batWpfExamDemo:FileOpen>

Ponieważ kontrolka jest typu Content, jest to jak najbardziej poprawne jej użycie. Jednak efekt może nie być zadowalający.

Stało się tak, gdyż umieszczony w szablonie klasy bazowej ContentPresenter nadpisał nasze obiekty. Jak temu zaradzić? Na drugi przypadek istnieje eleganckie rozwiązanie. Wystarczy dodać atrybut na naszej klasie i wskazać, jaka właściwość na być “odpowiedzialna” za zawartość.

[ContentProperty(“FileName”)]

public partial class FileOpen : UserControl

Pierwszy przypadek nie jest taki prosty. Musimy przeładować metodę OnContentChanged

protected override void OnContentChanged(object oldContent, object newContent)

{

    if (oldContent != null)

        throw new InvalidOperationException(“Content cannot be set explicitly.”);

    base.OnContentChanged(oldContent, newContent);

}

Nie jest to rozwiązanie ładne ni eleganckie, ale działa.

Więcej zależności…

Jeśli jednak właściwość FileName będziemy chcieli użyć w DataBindingu na zewnątrz naszej kontrolki, spotka nasz rozczarowanie. Nie zadziała. Musimy naszą właściwość zamienić na dependency property. Robimy to w kilku krokach:

  1. Rejestrujemy nową dependency property:

    public static readonly DependencyProperty FileNameProperty =

                DependencyProperty.Register(“FileName”,

                typeof(string),

                typeof(FileOpen));

    Nazwa, typ i wartość to elementy, które musimy podać.

  2. Tworzymy właściwość, która ustawia i odczytuję wartość z niej

    public string FileName

    {

        get { return (string)GetValue(FileNameProperty); }

        set { SetValue(FileNameProperty, value); }

    }

    Uwaga:Nie twórz żadnej logiki w get i set za wyjątkiem ustawienia i odczytania wartości! WPF czasem ją omija i ustawia wartość bezpośrednio a zatem twoja logika, może czasem nie być wykonana.

  3. “Naprawienie” funkcjonalności pokazania wybranego pliku w kontrolce TextBox

    Text=”{Binding FileName, ElementName=root}”

    Nazwę root nadajemy całej User Controlce

Po takich zabiegach, możemy już naszą właściwość FileName używać w DataBindingu.

Rutowalne zdarzenia

Możemy też zdefiniować rutowalny event, który będzie informować o zmianie ścieżki. Robimy to następująco:

  1. Definiujemy event:

    public static readonly RoutedEvent FileNameChangedEvent =

        EventManager.RegisterRoutedEvent(“FileNameChanged”,

        RoutingStrategy.Bubble,

        typeof (RoutedEventHandler),

        typeof (FileOpen));

  2. Add i Get

    public event RoutedEventHandler FileNameChanged

    {

        add { AddHandler(FileNameChangedEvent, value);}

        remove { RemoveHandler(FileNameChangedEvent, value); }

    }

  3. Wołamy go z odpowiedniego miejsca. TextChanged na TextBox brzmi odpowiednio.

    void fileName_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)

    {

        var args = new RoutedEventArgs(FileNameChangedEvent);

        RaiseEvent(args);

    }

I gotowe. Możemy już z niego korzystać.

<StackPanel WpfExamDemo:FileOpen.FileNameChanged=”StackPanel_FileNameChanged”>

    <WpfExamDemo:FileOpen x:Name=”fip”>WpfExamDemo:FileOpen> 

    <TextBlock Text=”{Binding FileName, ElementName=fip}” />

StackPanel>

Na dziś tyle. W następnym odcinku będzie o ContentControls CustomControls.