PiotrWojcik.online

Piotr Wójcik – doświadczony programista aplikacji desktopowych i baz danych.

INotifyPropertyChanged – powiadom kontrolkę gdy zajdzie zmiana wartości.

Po wielu latach przedstawianiu technologii i podawaniu ciekawych informacji ze świata informatycznego chciałbym dzisiaj zapoczątkować serię artykułów z dziedziny programowania. Moim językiem programowania będzie C#, ponieważ to właśnie w nim najlepiej się czuję. Przewiduję, że część z artykułów będzie niezależna od języka programowania. Dzisiaj jako pierwszy problem przedstawię implementację interfejsu INotifyPropertyChanged. Dlaczego to stosujemy? Z jakimi problemami możemy się spotkać? Jak to za programować? Na te pytania postaram się odpowiedzieć w poniższym artykule. Zacznijmy!

Nakreślenie problemu.

Prędzej czy później potdczas tworzenia kotrolek a w szczególności tabel typu DataGrid natchniemy się na problem aktualizacji danych. Rozważmy dwa najczęsciej spotykane przypadki:

  • edytujemy podpięte pole i wymagamy od aplikacji aby została wykonana jakaś czynność np. utrwalenie danych do bazy danych.
  • druga sytuacja pojawia się gdy chcemy za pomocą wprowadzonej nowej wartości przeliczyć wartości w innych polach lub chcemy wywołać jakieś zdarzenie chociażby odświetlenie przycisku Expand Button dla pojawiających się nowych zagnieżdżonych danych w wierszu tabeli.
INotifyPropertyChanged - powiadom kontrolkę gdy zajdzie zmiana wartości. 1

Jak sobie poradzić z problemem tym?

Złe rozwiązanie tymczasowe – podmiana DataSourca.

Najprostszym nasuwającym się rozwiązaniem jest ręczne zastąpienie DataSourca za pomocą edytowanej kolekcji. Niesie to za sobą wiele konsekwencji takich jak chociażby migotanie kontrolki spowodowane odświeżaniem danych i wygląda nieestetycznie w kodzie.

Dobre rozwiązanie tymczasowe – BindingLista.

Na początku przyjrzyjmy jedenym ze standardowych i prostych rozwiązań jakim jest struktura BindingLista. Rozwiązanie to polega na zastąpieniu tradycyjnej Listy lub innej prostej kolekcji w naszym DataSourcie.

DataGridView dt = new DataGridView();
BindingList bl = new BindingList();
dt.DataSource = bl;

Zaletą tego rozwiązania jest to, że przy niewielkim narzucie kodu otrzymujemy pełną funkcjonalność listy i zapewniamy podstawową komunikację listy z kontrolką na wypadek zmian wartości elementów tej listy. Zachęcam do przejrzenia API BindingListy w celu zapoznania się z jej funkcjonalnością.

Bardziej zaawansowany problem – struktury złożone jako element listy.

Powyższe rozwiązanie spisuje się doskonale dla typów prostych jak wartość liczbowa lub łańcuch znaków string. Teraz przyjrzyjmy się bardziej złożonemu problemowi jakim jest zagnieżdżenie złożonej struktury danych jako elementu listy. Dla przykładu możemy tą listę wyposażyć w złożoną strukturę jaką jest inna lista. W świetnych kontrolkach Devexpress takie elementy bardzo ładnie są zagnieżdżane w postaci struktury drzewiastej. Klikając z lewej strony na przycisk “plusa” ExpandButton możemy rozwinąć strukturę i zobaczyć podrzędne elementy.

Najczęściej spotykanym problemem jest dodanie nowego elementu do wiersza i odświetlenie przycisku ExpandButton. Aby kontrolka wiedziała, że należy przycisk odświetlić musimy ją w pewien sposób poinformować. Do tego służy dojrzały interfejs INotifyPropertyChanged.

Co to jest?

Interfejs INotifyPropertyChanged to ogólnie przyjęty standard informowania kontrolek o zmianach wartości pól. Należy interfejs ten zaimplementować w agregowanej strukturze danych.

 class Data : INotifyPropertyChanged
 {
    …
 }

Lecz to nie wszystko. Aby program wiedział, że doszło do zmiany wartości musimy wyposażyć nasze akcesory do danych set w wywołanie metody NotifyPropertyChanged().

    class Data : INotifyPropertyChanged
    {
        public int _id;
        public int Id
        {
            get => _id;
            set
            {
                _id = value;
                NotifyPropertyChanged();
            }
        }
    }

Metoda NotifyPropertyChanged() w momencie wywoływania będzie wysyłać zdarzenie PropertyChanged, ale wcześniej musimy ją zaprogramować.

public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}  

Rozwiązanie problemu powiadomiania DataSourca dla kolekcji złożonych struktur danych.

Do tej pory naświetliliśmy zagadnienie implementacji interfejsu INotifyPropertyChanged, z którym równie dorze radzi sobie BindingLista. Teraz zajmijmy się postawionym problemem złożonych struktur danych.
Stwórzmy klasę A, która będzie zawierać trzy atrybuty id, imię i zagnieżdżoną listę środek.

     public class A
     {
         public int Id
         public string Imię
         public BindingList Środek { get; set; } = new BindingList();
    }

Zaimplementujmy teraz interfejs INotifyPropertyChanged dla klasy A.

public class A : INotifyPropertyChanged
    {
        public int _id;
        public int Id
        {
            get => _id;
            set
            {
                _id = value;
                NotifyPropertyChanged();
            }
        }

        public string _imię;
        public string Imię
        {
            get => _imię;
            set
            {
                _imię = value;
                NotifyPropertyChanged();
            }
        }

    public BindingList<int> Środek { get; set; } = new BindingList<int>(); // linia a: o tym za chwilę
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }   
    }

Celowo zamieściłem komenarz i oznaczyłem go linią a w miejscu gdzie coś nam nie powinno grać. Dlaczego nie wyposażyliśmy akcesorów set dla pola Środek? Kto zna reprezentację obeiktów typu Collection w pamięci wie, że lista w pamięci repreznetowana jest jedynie przez adres do obiektu. Zamiana wartości tego adresu występuje w momencie stworzenia lub zastąpienia listy przez nową instancję. Dodanie lub edycja elementów listy nie wpływa na wartość tego adresu.

Jak rozwiązać ten problem?

Musimy tu wprowadzić pewną rekurencję, ale nie w znaczeniu algorytmicznym. Tak jak klase A rozszerzyliśmy o interfejs INotifyPropertyChanged również stworzmy klasę zagnieżdzoną Awew, która również będzie posiadała implementację tego interfejsu.

    public class Awew : INotifyPropertyChanged
    {
         public int _numer;
         public int Numer
         {
             get => _numer;
             set
             {
                 _numer = value;
                 NotifyPropertyChanged();
             }
        }

        public string _nazwa;
        public string Nazwa
        {
            get => _nazwa;
            set
            {
                _nazwa = value;
                NotifyPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

Możemy teraz zastąpić linię a w następujący sposób:

public BindingList środek { get; set; } = new BindingList();

Czy to wszystko? Niestety nie. Do tej pory stworzyliśmy dwie klasy A i Awew z implementacją interfejsu INotifyPropertyChanged, teraz musimy połączyć te dwa światy w jakiś sposób. Jak dobrze wiemy klasa BindingList implementuje ogólny interfejs IList, które posiada podstawowe, ale większości wypadkach wystarczające API. Wystarczy teraz zaimplementować metody interfejsu IList w klasie A. U nas wystarczy, że zaimplementujemy metody Add() i Remove().

public void Add(Awew item)
    {
        środek.Add(item);
        NotifyPropertyChanged();
    }

    public void Remove(Awew item)
    {
        środek.Remove(item);
        NotifyPropertyChanged();
    }

I to już naprawdę tyle. Mam nadzieję, że poniższy artykuł był interesujący i dość prosto opisany.

Miłej pracy!

Leave a Reply

Your email address will not be published. Required fields are marked *

Contact

Piotr Wójcik

error: Content is protected !!