Artykuł

cze 26 2011
0

WPF Tutorial - bindowanie

Od ostatniej części tutoriala WPF, poświęconej tworzeniu prostego kalkulatora minęło już kilka tygodni, więc czas najwyższy na nową część. Dziś zajmiemy się zagadnieniem niezwykle ważnym z punktu widzenia wykorzystania własnych danych w połączeniu z interfejsem graficznym. Mowa oczywiście o bindowaniu, czyli wiązaniu danych wyświetlanych w kontrolkach, a pochodzących np. z naszych własnych obiektów, właściwości, bazy danych etc.

Rodzaje bindowania

Każde bindowanie, może być jedno, lub dwustronne. Znaczenia tej sytuacji nie trudno się domyślić. Bindowanie jednostronne realizowane jest od źródła do elementu docelowego. W praktyce oznacza to, że np. gdy pobieramy dane z bazy danych i wyświetlamy je w kontrolce ListView. Jednakże nie możemy ich edytować z poziomu listy. Dodatkowe info na ten temat - patrz komentarze.

Drugą sytuacją jest bindowanie dwustronne. W tym przypadku, możemy również pobrać dane np. z bazy danych, ale tym razem wyświetlić je w DataGridzie, który umożliwia ich edycję oraz zapis do bazy, tym samym przepływ danych staje się dwustronny.

Co możemy bindować?

Bindować możemy bardzo wiele rzeczy i to na różne sposoby. Do najpopularniejszych zastosowań bindowania, z pewnością możemy zaliczyć następujące kombinacje:

  • Bindowanie dwóch kontrolek ze sobą
  • Bindowanie własnego obiektu z kontrolką/zestawem kontrolek
  • Bindowanie kolekcji danych z listą/data gridem
  • Bindowanie zbioru rezultatów bazy danych z listą/data gridem
  • Bindowanie właściwości klasy

DataContext oraz ItemsSource

Aby korzystać z bindowania, należy skorzystać z odpowiednich właściwości, czyli DataContext oraz ItemsSource. W środowisku .Netowym, panuje mały chaos związany z poprawnym rozgraniczaniem zastosowania obu właściwości, które w gruncie rzeczy realizują bardzo podobna funkcjonalność. Za pomocą dwóch prostych scenariuszy, postaram się rozróżnić ich zastosowanie.

Scenariusz I - DataContext

DataContext, używany jest wtedy, kiedy bindowanie chcemy zastosować do fragmentu drzewa logicznego kontrolek, przykładowo do określonego panelu w naszym oknie. W takim przypadku właściwość DataContext, ustawiamy dla naszego panelu, a każdy z elementów, który znajduje się w jego wnętrzu, będzie mógł korzystać z określonych właściwości naszego obiektu źródłowego.

Scenariusz II - ItemsSource

Z ItemsSource, korzystamy kiedy używamy kontrolek typu ListView oraz DataGrid, które operują na całych kolekcjach danych i mogą wyświetlać wiele wierszy.

Klasa Person

Zanim zaprezentuję praktyczne przykłady wykorzystania obu przedstawionych wyżej właściwości, napiszemy klasę Person, która zostanie wykorzystana jako kontener danych w dwóch kolejnych danych i która już nie raz sprawdziła się w przykładach praktycznych na tym blogu:

public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
 
    public Person(int nPersonId, string sFirstName, string sLastName, 
        int nAge)
    {
        PersonId = nPersonId;
        FirstName = sFirstName;
        LastName = sLastName;
        Age = nAge;
    }
}

Konstrukcja tej klasy jest na tyle elementarna, że nie wymaga raczej żadnych wyjaśnień.

DataContext w praktyce

Właściwość DataContext, posłuży nam do zrobienia prostego formularza, na którym zostaną wyświetlone dane z pojedynczego obiektu Person. Dane te będzie można zmienić właśnie edytując formularz. Nie będzie to oczywiście miało praktycznego znaczenia, jeśli nie wykorzystamy tych danych nigdzie dalej, ale pokaże samą ideę pracy z DataContext.

Zacznijmy zatem, od utworzenia warstwy prezentacji, czyli okna zawierającego panel wraz z czterema polami, zawierającymi dane naszego Jana Kowalskiego:

<Window x:Class="BindingTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="185" Width="300">
    <WrapPanel Name="plnPersonForm">
        <Label Content="ID:" MinWidth="75" Margin="5" />
        <TextBox MinWidth="180" Margin="5" Text="{Binding PersonId}" />
        <Label Content="Imię:" MinWidth="75" Margin="5" />
        <TextBox MinWidth="180" Margin="5" Text="{Binding FirstName}" />
        <Label Content="Nazwisko:" MinWidth="75" Margin="5" />
        <TextBox MinWidth="180" Margin="5" Text="{Binding LastName}" />
        <Label Content="Wiek:" MinWidth="75" Margin="5" />
        <TextBox MinWidth="180" Margin="5" Text="{Binding Age}" />
    </WrapPanel>
</Window>

Jak pewnie widzicie, nie została tutaj zastosowana właściwość DataContext, uczynimy to z poziomu Code-Behind. Nowym elementem, jest sposób ustawienia właściwości Text. W nawiasach klamrowych znajduje się słowo Binding, oznaczającego bindowanie oraz określona właściwość obiektu przekazanego jako DataContext. W ten sposób, możemy ustawić dowolną właściwość, dowolnej kontrolki, znajdującej się w obszarze bieżącego kontekstu i możemy to zrobić wielokrotnie (tzn. skorzystać z tej samej właściwości w kilku różnych miejscach). Jeśli typ właściwości będzie inny, to należy skorzystać z konwertera właściwości (o tym później).

Po napisaniu kodu XAML, czas na Code-Behind:

public partial class MainWindow : Window
{
    private Person m_oPerson = null;
    public MainWindow()
    {
        InitializeComponent();
        InitBinding();
    }

    private void InitBinding()
    {
        m_oPerson = new Person(1, "Jan", "Kowalski", 25);
        plnPersonForm.DataContext = m_oPerson;
    }
}

W tym przypadku, napisaliśmy pomocniczą metodę, która ustawia pole klasy m_oPerson, typu Person, danymi podanymi w konstruktorze, a następnie przypisuje ten obiekt, do kontekstu naszego panelu utworzonego w XAMLu. Teraz wszystkie fragmenty układanki, powinny już pasować do siebie, czego efektem będzie taki oto działający program:

Jeśli przetestujemy działanie aplikacji, to okaże się, że po zmianie tekstu w okienku, aktualizuje się również wartość określonej właściwości w obiekcie.

ItemsSource w praktyce

Do zrealizowania przykładu korzystającego z ItemsSource, wykorzystamy kontrolkę ListView, która prezentować będzie listę osób, czyli notabene listę obiektów typu Person. Przykład zrealizujemy w analogiczny sposób jak poprzedni, czyli rozpoczynamy od napisania kodu XAML:

<Window x:Class="BindingTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="185" Width="300">
    <Grid>
        <ListView Name="lstPersons">
            <ListView.View>
                <GridView AllowsColumnReorder="True">
                    <GridView.Columns>
                        <GridViewColumn Header="Id" 
                            DisplayMemberBinding="{Binding PersonId}" />
                        <GridViewColumn Header="Imię" 
                            DisplayMemberBinding="{Binding FirstName}" />
                        <GridViewColumn Header="Nazwisko" 
                            DisplayMemberBinding="{Binding LastName}" />
                        <GridViewColumn Header="Wiek" 
                            DisplayMemberBinding="{Binding Age}" />
                    </GridView.Columns>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

Tak jak napisałem wcześniej, właściwość ItemsSource ustawia się już dla konkretnej kontrolki i podobnie jak dla DataContext, uczynimy to z poziomu Code-Behind. Nasz ListView, korzystać będzie z GridView, dzięki czemu wizualnie zbliży się do DataGrida i pozwoli na wyświetlanie danych w postaci tabelarycznych.

Każdej z kolumn,przypisujemy nagłówek, a także właściwość DisplayMemberBinding, która pozwala na przypisanie odpowiedniego pola z klasy Person do wybranej kolumny.

Po analizie XAMLa, możemy przejść do Code-Behind:

public partial class MainWindow : Window
{
    private List<Person> m_oPersonList = null;
    public MainWindow()
    {
        InitializeComponent();
        InitBinding();
    }

    private void InitBinding()
    {
        m_oPersonList = new List<Person>();
        m_oPersonList.Add(new Person(1, "Jan", "Kowalski", 25));
        m_oPersonList.Add(new Person(2, "Adam", "Nowak", 24));
        m_oPersonList.Add(new Person(3, "Agnieszka", "Kowalczyk", 23));
        lstPersons.ItemsSource = m_oPersonList;
    }
}

W tym przypadku, również posiłkujemy się naszą własną metodą InitBinding, która tym razem dla odmiany, wypełnia listę osób, czyli listę instancji obiektów klasy Person. Tworzymy trzy przykładowe obiekty tego typu oraz ustawiamy właściwość ItemsSource, naszej kontrolki ListView. I to wystarczy, by osiągnąć w tym przypadku bindowanie jednostronne (brak opcji edycji z poziomu ListView). Efekt naszych działań widoczny jest na screenie 2:

Konwertery

W przypadku, kiedy właściwość kontrolki, jest niezgodna z typem bindowanej właściwości, możemy zastosować specjalny konwerter, który umożliwi przeprowadzenie takiej operacji.

Sztandarowym przykładem w tym przypadku, jest obsługa właściwości Visibility, dlatego nic nowego w tym przypadku raczej nie wymyślę i odeślę Was do tego wpisu.

Podsumowanie

Bindowanie to w .Necie temat rzeka, a przedstawione tutaj zagadnienia to zaledwie zalążek informacji, które warto znać.

Podsumowując dzisiejszy wpis, należy przede wszystkim pamiętać o różnicy jaką niosą ze sobą właściwości DataContext oraz ItemsSource.

W przypadku bindowania dwustronnego, należy uważać na odświeżanie elementów. Jeśli nie działa ono prawidłowo, koniecznym może być jego wymuszenia przez zastosowanie dodatkowych właściwości bindowania.

I to zasadniczo na tyle, co chciałbym Wam dziś powiedzieć.

W kolejnej części, postaram się Wam opisać proces tworzenia kontrolek użytkownika w oparciu o inne kontrolki:)

Jeśli podoba Ci się ten wpis, sprawdź inne części tutoriala WPF.

Poprzednia część | Następna część

Data ostatniej modyfikacji: 28.08.2013, 14:38.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus