Monthly Archives: Styczeń 2013

WPF – TemplateBinding i TemplatedParent a wyzwalacze w szablonie kontrolki

2013-01-26

Niedawno tworząc szablon kontrolki w WPF natknąłem się na pewien problem. Chcąc odwołać się z szablonu do właściwości kontrolki standardowo użyłem rozszerzenia TemplateBinding. Kod się skompilował ale po uruchomieniu aplikacji pojawił się wyjątek. Oto uproszczona postać szablonu:

<ControlTemplate x:Key="buttonTemplate" TargetType="Button">
    <Border Name="border"
            Background="{TemplateBinding Background}">
        <ContentPresenter Name="content"
                          HorizontalAlignment="Center"
                          VerticalAlignment="Center" />
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter TargetName="border"
                    Property="Background"
                    Value="{TemplateBinding Foreground}" />
            <Setter TargetName="content"
                    Property="TextBlock.Foreground"
                    Value="{TemplateBinding Background}" />
        </Trigger>
        <Trigger Property="IsPressed" Value="True">
            <Setter TargetName="content"
                    Property="RenderTransform">
                <Setter.Value>
                    <TranslateTransform X="1" Y="1" />
                </Setter.Value>
            </Setter>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

Poniżej błąd jaki pojawił się po uruchomieniu aplikacji:

TemplateBinding

Wielokrotnie stosowałem TemplateBinding w szablonach więc ten błąd trochę mnie zdziwił. Po krótkich poszukiwaniach okazało się, że faktycznie używałem go wcześniej ale nigdy wewnątrz wyzwalacza. TemplateBinding jest skróconą formą wiązania zdefiniowanego w następujący sposób: {Binding RelativeSource={RelativeSource TemplatedParent}}. Jak się jednak okazało, oprócz skróconego zapisu posiada on również pewne ograniczenia. Jednym z nich jest brak możliwości stosowania go w wyzwalaczach. Po zmianie szablonu kontrolki polegającej na zastąpieniu rozszerzenia TemplateBinding wiązaniem do TemplatedParent w wyzwalaczach, wszystko działa zgodnie z oczekiwaniami:

<ControlTemplate x:Key="buttonTemplate" TargetType="Button">
    <Border x:Name="border"
            Background="{TemplateBinding Background}">
        <ContentPresenter x:Name="content"
                          HorizontalAlignment="Center"
                          VerticalAlignment="Center" />
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter TargetName="border"
                    Property="Background"
                    Value="{Binding Foreground,
                                    RelativeSource={RelativeSource TemplatedParent}}" />
            <Setter TargetName="content"
                    Property="TextBlock.Foreground"
                    Value="{Binding Background,
                                    RelativeSource={RelativeSource TemplatedParent}}" />
        </Trigger>
        <Trigger Property="IsPressed" Value="True">
            <Setter TargetName="content"
                    Property="RenderTransform">
                <Setter.Value>
                    <TranslateTransform X="1" Y="1" />
                </Setter.Value>
            </Setter>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

Udostępnianie kolekcji tylko do odczytu

2013-01-15

Poniżej znajduje się definicja klasy zawierającej kolekcję adresów e-mail danej osoby. Dostęp do listy adresów uzyskujemy poprzez właściwość Emails. Za dodawanie nowych adresów odpowiada z kolei metoda AddEmail. Przed dodaniem elementu do kolekcji sprawdzana jest jego poprawność oraz to czy taki adres nie znajduje się już na liście.

class Person
{
    private List<string> emails;

    public IList<string> Emails
    {
        get { return emails; }
    }

    public Person()
    {
        emails = new List<string>();
    }

    public void AddEmail(string email)
    {
        if (email != null && email.Contains('@') && !emails.Contains(email))
            emails.Add(email);
        else
            throw new ArgumentException();
    }
}

Teoretycznie powyższa definicja klasy jest poprawna. Co się jednak stanie po wykonaniu następującego kodu?:

Person person = new Person();

person.AddEmail("email@mail.com");
person.Emails.Add("email@mail.com");
person.Emails.Add(null);

Okazuje się, że możliwe jest ominięcie walidacji zastosowanej w metodzie AddEmail poprzez dodawanie elementów bezpośrednio do kolekcji. Co więcej, w ten sam sposób można je również usuwać.
Rozwiązaniem tych problemów jest udostępnienie kolekcji tylko do odczytu. Jest to możliwe dzięki wykorzystaniu obiektu ReadOnlyCollection<T> (przestrzeń System.Collections.ObjectModel). Klasa List<T> posiada metodę AsReadOnly() zwracającą obiekt tego typu. Oto jej zastosowanie we właściwości naszej klasy:

class Person
{
    private List<string> emails;

    public IList<string> Emails
    {
        get { return emails.AsReadOnly(); }
    }

    public Person()
    {
        emails = new List<string>();
    }

    public void AddEmail(string email)
    {
        if (email != null && email.Contains('@') && !emails.Contains(email))
            emails.Add(email);
        else
            throw new ArgumentException();
    }
}

W tej chwili przy próbie wykonania jakiejkolwiek operacji dodawania, usuwania lub zastąpienia elementu kolekcji zgłoszony zostanie wyjątek:

ReadOnly1

Obiekt ReadOnlyCollection<T> może zostać utworzony dla dowolnej kolekcji implementującej IList<T>:

ObservableCollection<string> collection = new ObservableCollection<string>();
//...
ReadOnlyCollection<string> roCollection = new ReadOnlyCollection<string>(collection);

ReadOnlyCollection jest wraperem na kolekcję zabezpieczającym ją przed modyfikacjami. Nie tworzy on ponownie elementów a jedynie przechowuje oryginalną kolekcję, dzięki czemu wszelkie wykonywane bezpośrednio na niej operacje są w nim odzwierciedlane. Zasada działania ReadOnlyCollection jest bardzo prosta: jeżeli wywołana zostanie na nim metoda modyfikująca kolekcję pojawi się wyjątek, w przeciwnym wypadku ta sama metoda zostanie wywołana na oryginalnej kolekcji.

Więcej informacji można znaleźć na MSDN: ReadOnlyCollection, List<T>.AsReadOnly.

Podsumowanie 2012

2013-01-01

Pierwszy wpis w 2013 roku dotyczyć będzie tego co już za nami. 😉

WordPress udostępnił krótki raport podsumowujący to co działo się na blogu w minionym 2012 roku: Podsumowanie 2012 by WordPress

This blog had 17 000 views in 2012. Where did they come from? That’s 35 countries in all! Most visitors came from Poland, Germany, The United States & The United Kingdom. In 2012, there were 36 new posts, not bad for the first year! There were 90 pictures uploaded, taking up a total of 3 MB. That’s about 2 pictures per week. The busiest day of the year was Kwiecień 22nd with 332 views. The most popular post that day was Leniwa inicjalizacja obiektów – klasa Lazy.

These are the posts that got the most views in 2012:

  1. Przesyłanie plików w systemach rozproszonych – streaming w WCF (Maj 2012)
  2. Data Tools i Power Tools – dodatki do Visual Studio 2010 (Czerwiec 2012)
  3. Kolekcja obiektów i plik XML – zapis, odczyt i modyfikacja danych przy użyciu LINQ (Maj 2012)
  4. Instrukcja yield return – tworzenie leniwych kolekcji danych (Sierpień 2012)
  5. WPF – dynamiczne tworzenie i wczytywanie kodu XAML (Czerwiec 2012)