Oj już prawie miesiąc odkąd cokolwiek pojawiło się na blogu. Nie będę się tłumaczył z tego faktu, a tylko powiem, że kolejne wpisy nie będą pojawiać się często. Jako, że ostatnimi czasy siedzę w WPF to dziś wpis także z tej kategorii.
Może nie będą to takie tajniki jak w tytule zapowiadam, ale raczej zwrócenie uwagi na pewną funkcjonalność DependencyProperty. Czasami prócz samej możliwości bindowania do takiej właściwości chcemy także uzyskać np. odmalowanie kontrolki, która daną właściwość definiuje. Ostatnio miałem właśnie taką potrzebę i ten wpis jest właśnie spuścizną na przyszłość, abym nie musiał tego szukać.
Wszyscy wiedzą (a jeśli nie to szybciutko należy się dowiedzieć), że podstawowa definicja DependencyProperty to mniej więcej taki kod.

public static DependencyProperty LengthProperty = DependencyProperty.Register(“Length”, typeof (int),
                                                                              typeof (Window1));
public int Length
{
    get { return (int) GetValue(LengthProperty); }
    set { SetValue(LengthProperty, value); }
}

Jeśli jednak chcemy wyciągnąć dodatkowe możliwości DependencyProperty pamiętajmy, że funkcja Register posiada jeszcze dodatkowy 4ty i 5ty parametr! Co można za ich pomocą uzyskać? Zobaczmy jak wygląda czwarty parametr.

PropertyMetadata

Parametr PropertyMetadata to klasa bazowa pozwalająca na zdefiniowanie dodatkowych opcji naszej właściwości. Najniżej w hierarchii znajduje się klasa FrameworkPropertyMetadata i na opisie jej się skupimy. Co możemy zdefiniować przy jej użyciu?

  • wartość domyślną (defaultValue)
  • dodatkowe parametry (FrameworkPropertyMetadataOptions)
  • funkcję wołana w przypadku zmiany wartości (PropertyChangedCallback)
  • funkcja wołana w przypadku wymuszenia przeliczenia wartości (CoerceValueCallback)

Odnośnie wartości domyślnej nie trzeba się rozpisywać, więc przejdźmy do kolejnego punktu tajemniczo oznaczonego dodatkowe parametry.
Za ich pomocą możemy powiedzieć WPF, na co wpływa zmiana wartości naszej właściwości. Np. można tę wartości ustawić na FrameworkPropertyMetadataOptions.AffectsRender. Dzięki temu, za każdym razem przy zmianie wartości zostanie wywołana funkcja OnRender. Pozostałe ciekawe właściwości, które za pomocą możemy ustawić to: AffectsArrange, AffectsMeasure, BindsTwoWayByDefault (opis wszystkich – FrameworkPropertyMetadataOptions)
Trzeci i czwarty parametr pozwalają na zdefiniowanie funkcji, które będą wołane odpowiednio w przypadku zmiany wartości lub wymuszenia jej przeliczenia (za pomocą metody CoerceValue).
Przykład:

public static DependencyProperty LengthProperty =
                                DependencyProperty.Register(“Length”, typeof (int),typeof (Window1),
                                new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsArrange)
                                                             );

Dzięki temu za każdym razem gdy zmieni się wartość zmiennej Length WPF automatycznie wywoła przeliczenie layoutu.

ValidateValueCallback

Za pomocą tego parametru możemy zdefiniować metodę, w której będziemy mogli sprawdzić czy nowa wartość dla naszej właściwości spełnia reguły walidacyjne. Dzięki temu możemy napisać taki kawałek kodu:

public static DependencyProperty LengthProperty =
                                DependencyProperty.Register(“Length”, typeof (int),typeof (Window1),
                                                   new FrameworkPropertyMetadata(1,
                                                   FrameworkPropertyMetadataOptions.AffectsArrange), Validate);
public static bool Validate(object val)
{
    if (val == null)
        return false;
    if (val.GetType() != typeof(int))
        return false;
    int value = (int) val;
    return value > 0 && value < 10;
}

który zostanie wywołany przy każdej zmianie wartości. W przypadku zwrócenia wartości false, ArgumentException zostanie wyrzucony, tak więc musimy być przygotowani na jego obsłużenie.

public int Length
{
    get { return (int) GetValue(LengthProperty); }
    set
    {
        try
        {
            SetValue(LengthProperty, value);
        }
        catch (ArgumentException ex)
        {
            //obsługa błędnej wartości               
        }               
    }
}

Tyle na dziś. Mam nadzieję, że komuś oszczędzi to trochę szukania. Do następnego razu – kiedykolwiek on będzie :).