• Nie Znaleziono Wyników

Programownie w technologii .NET wykład 6 – Element Binding i Data Binding

N/A
N/A
Protected

Academic year: 2021

Share "Programownie w technologii .NET wykład 6 – Element Binding i Data Binding"

Copied!
41
0
0

Pełen tekst

(1)

Programownie w technologii .NET

wykład 6 – Element Binding i Data Binding

Element Binding

• Mechanizm, który pozwala wydobyć pewne informacje z obiektu źródłowego i zapisać je w pewnym obiekcie docelowym.

• Obiektem docelowym zawsze jest jakaś własność zależnościowa, przeważnie w elemencie WPF (wiązanie danych służy głównie obsłudze interfejsu użytkownika).

• Obiektem źródłowym może być cokolwiek: inne kontrolki WPF, własne obiekty dowolnych klas, ich własności, kolekcje obiektów, dane XML.

• Klasyczne podejście polegałoby na obsłudze odpowiednich zdarzeń i ręcznym ustawianiu własności elementów (w kodzie).

• Wiązanie danych tworzy łącznik między interfejsem graficznym a źródłem danych, odpowiedzialny za ich pobieranie i wyświetlanie.

• Wiązanie danych w większości dotyczyć będzie wiązania elementów interfejsu użytkownika z danymi zaczerpniętymi z zewnętrznych źródeł.

(2)

Wiązanie elementów

• Najprostszy scenariusz mechanizmu wiązania danych.

• Elementem źródłowym jest własność zależnościowa pewnej kontrolki.

• W momencie zmiany własności obiektu źródłowego, następuje automatyczne powiadomienie i aktualizacja obiektu docelowego.

(3)

Nie wymaga szczególnych zabiegów u źródła:

<Window ...>

<StackPanel>

<Slider Name="fontSize" Minimum="12" Value="18"

Maximum="50" TickFrequency="2"

TickPlacement="TopLeft"/>

<TextBlock>

Tak działa Data Binding </TextBlock>

</StackPanel>

</Window>

(4)

Wiązanie definiowane jest w elemencie docelowym (zamiast podawania wartości pewnej własności) przy użyciu binding expression:

<TextBlock Margin="5"

FontSize="{Binding Path=Value, ElementName=fontSize}">

Tak działa Data Binding

</TextBlock>

Równoznaczny zapis:

<TextBlock Margin="5">

<TextBlock.FontSize>

<Binding Path="Value" ElementName="fontSize"/>

</TextBlock.FontSize>

Tak działa Data Binding

</TextBlock>

ElementName – wskazujemy na źródło wiązania (skąd pobierzemy wartość)

Path – wskazuje, którą własność odczytamy z obiektu źródłowego i użyjemy jako wartość naszej własności.

Path może być złożoną ścieżką (np. własność własności: FontFamily.Source lub indekser własności: Children[0]). Własności dołączone umieszczane są w nawiasach:

„(Grid.Row)”.

(5)

Błędy wiązania:

Nie powodują podnoszenia wyjątków.

Pojawia się jedynie informacja w Output window (w trybie debuggowania).

(6)

Tworzenie dowiązań w kodzie

Binding binding = new Binding();

binding.Source = fontSize;

binding.Path = new PropertyPath("Value");

binding.Mode = BindingMode.TwoWay;

tekst.SetBinding(TextBlock.FontSizeProperty, binding);

Usuwanie dowiązań:

BindingOperations.ClearBinding(tekst,

TextBlock.FontSizeProperty);

BindingOperations.ClearAllBindings(tekst);

Kiedy tego potrzebujemy?

• Dynamiczne tworzenie wiązań – same wiązanie warto jednak zdefiniować jako zasób, a w kodzie tylko wywoływać SetBinding().

• Usuwanie dowiązań.

• Tworzenie własnych kontrolek.

(7)

Kierunek dowiązania

<Slider Name="fontSize" .../>

<TextBlock Name="tekst"

FontSize="{Binding Path=Value, ElementName=fontSize}">

Tak działa Data Binding

</TextBlock>

<Button Click="myClick" ...>Big</Button>

private void Button_Click(object sender, RoutedEventArgs e) {

tekst.FontSize = 30;

}

(8)

Kierunek dowiązania

<Slider Name="fontSize" .../>

<TextBlock Name="tekst"

FontSize="{Binding Path=Value, ElementName=fontSize}">

Tak działa Data Binding

</TextBlock>

<Button Click="myClick" ...>Big</Button>

private void Button_Click(object sender, RoutedEventArgs e) {

tekst.FontSize = 30;

}

(9)

Kierunek dowiązania

<Slider Name="fontSize" .../>

<TextBlock Name="tekst"

FontSize="{Binding Path=Value, ElementName=fontSize}">

Tak działa Data Binding

</TextBlock>

<Button Click="myClick" ...>Big</Button>

private void Button_Click(object sender, RoutedEventArgs e) {

tekst.FontSize = 30;

}

(10)

Kierunek dowiązania

• Element docelowy zawsze aktualizowany jest automatyczne, niezależnie od tego, jak zmieni się źródło.

• Aktualizacja źródła w wyniku zmiany elementu docelowego jest zależna od wybranego trybu wiązania (własność Binding.Mode).

System.Windows.Data.BindingMode

OneWay – aktualizowany jest wyłącznie cel, w wyniku zmiany źródła.

TwoWay – aktualizacja obustronna – cel, gdy zmienia się źródło i źródło, gdy zmienia się cel.

OneTime – własność docelowa zostaje jednorazowo ustawiona na wartość z własności źródłowej, potem zmiany są ignorowane.

OneWayToSource – jak OneWay, ale w przeciwnym kierunku: źródło jest aktualizowane w wyniku zmian celu.

Default – domyślny tryb zależy od rodzaju kontrolki i własności, własności

modyfikowane przez użytkownika w kontrolkach edycyjnych (np. Text w TextBox) mają domyślnie TwoWay, pozostałe – OneWay. Zależy od ustawienia flagi

FrameworkPropertyMetadata.BindsTwoWayByDefault.

(11)

Kierunek dowiązania

<Slider Name="fontSize" .../>

<TextBlock Name="tekst"

FontSize="{Binding Path=Value, ElementName=fontSize, Mode =TwoWay }">

Tak działa Data Binding

</TextBlock>

<Button Click="myClick" ...>Big</Button>

private void Button_Click(object sender, RoutedEventArgs e) {

tekst.FontSize = 30;

}

(12)

Wiązanie OneWayToSource

• Działa tak samo, jak OneWay.

• Jedyna różnica polega na tym, gdzie umieszczone jest wiązanie: jest to zamiana źródła z celem.

<Slider Name="fontSize" Minimum="12" ...

Value="{Binding Path=FontSize, ElementName=tekst, Mode=OneWayToSource}" />

<TextBlock Name="tekst" FontSize="18">

Tak działa Data Binding

</TextBlock>

Zastosowanie: gdy chcemy ustawić własność nie będącą własnością zależnościową.

(13)

Wielokrotne dowiązania

<Slider Name="fontSize" Minimum="12" Value="18" Maximum="50"

TickFrequency="2" TickPlacement="TopLeft"/>

<TextBlock Name="tekst"

FontSize="{Binding Path=Value, ElementName=fontSize}">

Tak działa Data Binding

</TextBlock>

<TextBox Text="{Binding Path=Value, ElementName=fontSize}"/>

<Button Click="Button_Click">Big</Button>

warto ustawić dla Slidera:

IsSnapToTickEnabled="True"

aktualizacja wartości następuje dopiero, gdy pole tekstowe utraci fokus

(14)

Inne rozwiązanie (z identycznym efektem):

<Slider Name="fontSize" Minimum="12" Value="18" Maximum="50"

TickFrequency="2" TickPlacement="TopLeft"/>

<TextBlock Name="tekst"

FontSize="{Binding Path=Value, ElementName=fontSize}">

Tak działa Data Binding

</TextBlock>

<TextBox Text="{Binding Path=FontSize, ElementName=tekst}"/>

Inne rozwiązanie (tym razem zmiana czcionki jest wprowadzana natychmiastowo):

<Slider Name="fontSize" Minimum="12"

Value="{Binding Path=Text, ElementName=pole}" Maximum="50"

TickFrequency="2" TickPlacement="TopLeft"/>

<TextBlock Name="tekst"

FontSize="{Binding Path=Value, ElementName=fontSize}">

Tak działa Data Binding

</TextBlock>

<TextBox Name="pole" Text="18"/>

Oba dowiązania (w TextBoksie i Sliderze) mogą istnieć jednocześnie.

(15)

Wielokrotne dowiązania

<Slider Name="fontSize" .../>

<TextBox Name="pole" ... />

<ComboBox Name="kolory" ...>

<ComboBoxItem Content="Red" />

<ComboBoxItem Content="Green" />

<ComboBoxItem Content="Blue" />

</ComboBox>

<TextBlock Text="{Binding Path=Text, ElementName=pole}"

FontSize="{Binding Path=Value, ElementName=fontSize}"

Foreground="{Binding Path=SelectedItem.Content, ElementName=kolory}">

</TextBlock>

(16)

Aktualizacja dowiązań

• Wartości ze źródła do celu są przesyłane natychmiast.

Dla odwrotnego kierunku (przy TwoWay lub OneWayToSource) zależy to od ustawienia Binding.UpdateSourceTrigger.

PropertyChanged – źródło jest aktualizowane natychmiast, gdy własność docelowa się zmieni

LostFocus – źródło jest aktualizowane, gdy docelowa własność się zmieni, a cel straci focusa (używane np. w kontrolce tekstowej, gdzie zawartość zmienia się często i ciągła aktualizacja mogłaby powodować problemy)

Explicit – źródło nie jest uaktualniane dopóki nie wymusimy aktualizacji wywołując metodę BindingExpression.UpdateSource()

BindingExpression binding =

poleTxt.GetBindingExpression(TextBox.TextProperty);

binding.UpdateSource();

Default – dla większości własności jest to PropertyChanged, ale np.

TextBox.Text ma LostFocus; zależy to od ustawienia FrameworkPropertyMetadata.DefaultUpdateSourceTrigger

• Powyższe ustawienia nie mają wpływu na sposób aktualizacji własności docelowej.

(17)

Aktualizacja dowiązań

<TextBox Text="{Binding Path=Text, ElementName=poleB}" />

<TextBox Name="poleB" />

• Aktualizacja drugiego pola gdy wpiszemy coś w pierwszym – dopiero po utracie focusa przez pierwsze

• Aktualizacja pierwszego pola gdy wpiszemy coś w drugim – natychmiastowa Aktualizacja natychmiastowa:

<TextBox Text="{Binding Path=Text, ElementName=poleB, UpdateSourceTrigger=PropertyChanged}" />

<TextBox Name="poleB" />

(18)

Wiązanie do obiektów, które nie są elementami

• WPF umożliwia dowiązywanie własności obiektów, które nie są elementami wizualnymi.

• Jedyny wymóg, to że muszą być to publiczne własności

• Zamiast Binding.ElementName należy użyć:

Source – referencja wskazująca na obiekt dostarczający dane

RelativeSource – wskazuje na obiekt źródłowy w odniesieniu do aktualnego elementu; użyteczne przy pisaniu szablonów kontrolek i szablonów danych

DataContext – jeśli nie wybierzemy żadnego z powyższych, WPF będzie tu poszukiwał obiektu danych, idąc w górę drzewa elementów; DataContext pozwala wiązać wiele własności tego samego obiektu do różnych elementów

(19)

Source

• Należy dostarczyć obiekt, z którego chcemy zaczerpnąć dane.

• Może być to obiekt dowolnej klasy.

public class Osoba {

public string Imie { get; set; } public string Nazwisko { get; set; } public Osoba() { }

public Osoba(string imie, string nazwisko) {

Imie = imie;

Nazwisko = nazwisko;

} }

(20)

Source

• Z zasobów:

<Window ...

xmlns:app="clr-namespace:WpfApp6">

<Window.Resources>

<app:Osoba x:Key="osoba" Imie="Adam"/>

</Window.Resources>

<StackPanel>

<TextBlock Text="{Binding Path=Imie,

Source={StaticResource osoba}}"/>

</StackPanel>

</Window>

(21)

Source

• Jako składowa statyczna:

public partial class Window1 : Window {

public static Osoba jeden = new Osoba("Mikołaj", "Rej");

}

<Window ...

xmlns:app="clr-namespace:WpfApp6">

<StackPanel>

<TextBlock Text="{Binding Path=Imie,

Source={x:Static app:Window1.jeden}}"/>

</StackPanel>

</Window>

(22)

RelativeSource

• Wskazuje na obiekt źródłowy, bazując na jego relacji z obiektem docelowym.

Self – dowiązanie do innej własności tego samego elementu

<TextBlock Text="{Binding Path=FontFamily,

RelativeSource={RelativeSource Self}}"/>

FindAncestor – dowiązanie do elementu nadrzędnego; poszukiwany jest element typu podanego jako AncestorType

<TextBlock Text="{Binding Path=Title,

RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>

PreviousData – dowiązanie do poprzedniego elementu listy

TemplatedParent – dowiązanie do elementu, dla którego zastosowano szablon

RelativeSource jest zwłaszcza przydatne w szablonach danych i szablonach kontrolek.

(23)

RelativeSource

• Można wykorzystać, aby sięgnąć do obiektów przechowywanych w oknie:

public partial class Window1 : Window {

private Osoba user = new Osoba("Jan", "Kowalski");

public Osoba User { get { return user; } } }

<TextBlock Text="{Binding Path=User.Imie,

RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>

(24)

DataContext

• Często konieczne jest wiązanie wielu elementów do tego samego źródła danych.

<Window ...>

<Window.Resources>

<app:Osoba x:Key="person" Imie="Jan"

Nazwisko="Kowalski"/>

</Window.Resources>

<Grid>

...

<TextBox Text="{Binding Path=Imie,

Source={StaticResource person}}" />

<TextBox Text="{Binding Path=Nazwisko,

Source={StaticResource person}}"/>

</Grid>

</Window>

(25)

DataContext

• Dobre rozwiązanie to: obiekt źródłowy zdefiniować raz, w elemencie nadrzędnym

<Window ...>

<Window.Resources>

<app:Osoba x:Key="person" Imie="Jan"

Nazwisko="Kowalski"/>

</Window.Resources>

<Grid DataContext="{StaticResource person}">

...

<TextBox Text="{Binding Path=Imie}" />

<TextBox Text="{Binding Path=Nazwisko}"/>

</Grid>

</Window>

(26)

Data Binding

• Klasa produktu służy jedynie reprezentowaniu danych wydobytych z bazy.

• Dostęp jedynie przy pomocy publicznych własności.

Jeśli planujemy wiązanie dwukierunkowe, własności nie mogą być read-only.

public class Product {

public string Name { get; set; } public decimal Price { get; set; } public string Opis { get; set; } public Product() { }

public Product(string name, decimal price) {

Name = name;

Price = price;

} }

(27)

Data Binding

• Kod „komunikujący się z bazą” powinien być zawarty w osobnej klasie.

public class MyDB {

private static List<Product> list = new List<Product>();

static MyDB() {

list.Add(new Product("book", 20));

list.Add(new Product("cd", 10));

list.Add(new Product("dvd", 40));

list.Add(new Product("pen", 2));

}

public Product GetProduct(int ID) {

return list[ID % list.Count];

} }

(28)

Data Binding

• Dostęp do obiektu bazy możemy uzyskiwać:

◦ tworząc go za każdym razem, gdy tego potrzebujemy

◦ dostęp przez statyczne składowe

◦ (optymalny) przez statyczną składową innej klasy

public partial class App : Application {

private static MyDB myDB = new MyDB();

public static MyDB MyDB {

get { return myDB; } }

}

(29)

Data Binding

<Window ...>

<Grid>

...

<TextBox ... Name="ID"/>

<Button ... Click="GetDataClick">Get Data</Button>

<Grid ... Name="details">

...

<TextBox Text="{Binding Path=Name}"/>

<TextBox Text="{Binding Path=Price}"/>

<TextBox Text="{Binding Path=Opis}"/>

</Grid>

</Grid>

</Window>

(30)

Data Binding

private void GetDataClick(object sender, RoutedEventArgs e) {

int id;

if (Int32.TryParse(ID.Text, out id)) {

try {

details.DataContext = App.MyDB.GetProduct(id);

} catch {

MessageBox.Show("Error contacting database.");

} } else {

MessageBox.Show("Invalid ID.");

} }

(31)

Data Binding

(32)

Data Binding

• Pola o zawartości null:

◦ typy referencyjne (stringi, obiekty) obsługują null automatycznie

typy proste można deklarować w wersji int? zamiast int public class Values

{

public int? X { get; set; } public int Y { get; set; } }

Domyślna reakcja to brak zawartości w polu. (podczas gdy dla pola int byłoby to 0)

• Można to nadpisać (nawias kwadratowy to tylko ozdobnik):

<TextBox Text="{Binding Path=Opis,

TargetNullValue=[brak danych]}"/>

(33)

Data Binding

• Aktualizacja danych:

◦ obiekty z listy są modyfikowane automatyczne w momencie utraty focusa przez odpowiednie pola tekstowe,

◦ samodzielnie powinniśmy jedynie zadbać o zapisanie zmian do bazy,

◦ dobrze jest dodać w tym celu przycisk potwierdzający zmianę:

private void UpdateButtonClick(object sender, ...) {

Product product = (Product)details.DataContext;

try {

App.MyDB.UpdateProduct(product);

} catch {

MessageBox.Show("Error contacting database.");

} }

Uwaga: jeśli zatwierdzono Enterem (przycisk typu IsDefault), nie nastąpiła zmiana focusa – musimy albo wymusić ją sami, albo ręcznie wywołać UpdateSource.

(34)

Data Binding

• Powiadomienia o zmianach: jeśli dowiązany obiekt zmieni się (np. jakiś

zewnętrzny czynnik lub inny fragment kodu zmieni wartości składowych), należy powiadomić interfejs, aby miał okazję pobrać i wyświetlić nowe wartości.

◦ własności zależnościowe same informują o swoich zmianach,

w wypadku data objects najlepiej jest zaimplementować interfejs System.ComponentModel.INotifyPropertyChanged i podnosić zdarzenie PropertyChanged, gdy któraś własność się zmieni.

public class Wybór : INotifyPropertyChanged {

public event PropertyChangedEventHandler PropertyChanged;

public void OnPropertyChanged(string property) {

if (PropertyChanged != null) PropertyChanged(this,

new PropertyChangedEventArgs(property));

} ...

}

(35)

• Zmiana jednej własności pociąga za sobą drugą i od razu jest to odzwierciedlane w interfejsie użytkownika.

public class Wybór : INotifyPropertyChanged {

...

private int x;

private int y;

public int X {

get { return x; }

set { x = value; y = 100 - x; OnPropertyChanged("Y"); } }

public int Y {

get { return y; }

set { y = value; x = 100 - y; OnPropertyChanged("X"); } }

}

• Możemy też przekazać pusty string, jeśli zmieniło się kilka własności obiektu (odświeża wszystkie).

(36)

Wiązanie z kolekcją obiektów

• Kolekcja wymaga kontrolki typu ListBox, ComboBox, ListView, Data Grid (a także Menu lub TreeView dla danych hierarchicznych).

• Dzięki szablonom danych, kontrolki list dają dużą kontrolę nad sposobem prezentacji danych.

• Własności ItemsControl odpowiedzialne za wiązanie z danymi:

ItemsSource – wskazanie na kolekcję obiektów do wyświetlenia

DisplayMemberPath – własność, która będzie użyta do stworzenia tekstu wyświetlanego na liście

ItemTemplate – określa szablon użyty do stworzenia wyglądu elementu

Kolekcja wyświetlana w liście powinna implementować IEnumerable

(37)

Wiązanie z kolekcją obiektów public class MyDB {

public List<Product> GetProducts() {

...

List<Product> products = new List<Product>();

try {

...

while (...) {

Product product = new Product(...);

products.Add(product);

} }

finally {

...

}

return products;

} }

(38)

Wiązanie z kolekcją obiektów

• Po naciśnięciu przycisku „Wczytaj dane” metoda GetProducts() pobiera z bazy i zwraca całą listę obiektów.

<Button Click="GetProductsClick">Wczytaj dane</Button>

<ListBox Margin="5" Name="lista" DisplayMemberPath="Name"/>

(39)

Wiązanie z kolekcją obiektów

• Po naciśnięciu przycisku „Wczytaj dane” metoda GetProducts() pobiera z bazy i zwraca całą listę obiektów.

private void GetProductsClick(object sender, ...) {

products = App.MyDB.GetProducts();

lista.ItemsSource = products;

}

Zamiast ustawiać DisplayMemberPath, możemy napisać ToString() w klasie produktu lub dostarczyć szablon danych.

public class Product {

public override string ToString() {

return Name + " (" + Price + ")";

} }

(40)

Wiązanie z kolekcją obiektów

• Ostatnie zadanie, to wyświetlać szczegóły produktu wybranego na liście. Zamiast reagować na zdarzenie SelectionChanged, lepiej zdefiniować odpowiednie wiązanie elementów:

<Grid Name="details"

DataContext="{Binding ElementName=lista, Path=SelectedItem}">

...

</Grid>

• W podobny sposób można tworzyć złożone listy kategorii:

◦ jedna lista kategorii

◦ druga lista produktów należących do tej kategorii

◦ następnie szczegóły danego produktu

• Pozostaje jeszcze tylko zapisanie danych

◦ dobrym pomysłem jest zmusić użytkownika do użycia przycisku „Zapisz” - np.

wyszarzyć listę, gdy wprowadzi on zmiany w polach tekstowych i dać do dyspozycji przyciski „OK” i „Anuluj”.

(41)

Usuwanie obiektów z kolekcji

• Wykorzystanie standardowej listy jako kolekcji elementów nie pozwoli na odzwierciedlenie usuwania i dodawania obiektów:

private void DeleteProductClick(object sender,...) {

products.Remove((Product)lista.SelectedItem);

}

• Obiekt zostaje usunięty z kolekcji, ale nadal jest widoczny na liście.

• Wymagane jest skorzystanie z kolekcji implementującej interfejs

INotifyCollectionChanged. Implementuje go klasa ObservableCollection.

public class MyDB {

public List<Product> GetProducts() {

products = new ObservableCollection<Product>();

...

return products;

} }

Cytaty

Powiązane dokumenty

Zmienna, której wartości w analizie traktuje się jako dane i nie próbuje wyjaśniać. Zakłada się, że zmienne niezależne determinują wartość zmiennych zależnych lub

Niebezpiecznym powikłaniem leczenia heparyną jest małopłytkowość zależna od heparyny (HIT), powodowana przez przeciwciała klasy IgG skierowane przeciwko kompleksowi heparyny

• Uczniowie wiedzą, że 14 listopada to Światowy Dzień Walki z Cukrzycą oraz że nadmierne spożycie cukru w diecie wpływa na zwiększenie ryzyka zachorowania

Z dobroci serca nie posłużę się dla zilustrowania tego mechanizmu rozwojem istoty ludzkiej, lecz zaproponuję przykład róży, która w pełnym rozkwicie osiąga stan

Te różnice /drobne/ są potęgowane tym, że kobieta w czasie miesiączki ma w ogóle mniejszą ilość krwi do dyspozycji, jest przyćmiona prze kilka dni - w wyścigach szczurów -

e)Dysk twardy – jest to element komputera mający na celu przechowywania różnych plików. Jego zaletą jest mniejsza cena i mniejsze zajmowanie przestrzeni komputera. Wadą jest

Zaprezentowane wyniki badań nad przekładem wybranych pozycji obydwu odmian (autor- stwa Andrzeja Sapkowskiego, Jacka Dukaja i Stanisława Lema) w połączeniu z analizą

informuje o wywieszeniu na tablicy ogłoszeń Urzędu Miasta wykazu nieruchomości do oddania w dzierżawę na okres do 3 lat, mieszczącej się w budynku dyrekcji MOSir w rudzie