Omówienie powiązań danych (WPF .NET)

Powiązanie danych w programie Windows Presentation Foundation (WPF) zapewnia prosty i spójny sposób prezentowania i interakcji z danymi przez aplikacje. Elementy mogą być powiązane z danymi z różnych rodzajów źródeł danych w postaci obiektów platformy .NET i kodu XML. Wszystkie ContentControl, takie jak Button, i dowolne ItemsControl, takie jak ListBox i ListView, mają wbudowaną funkcjonalność umożliwiającą elastyczne stylizowanie pojedynczych elementów danych lub kolekcji elementów danych. Widoki sortowania, filtrowania i grupowania można wygenerować na podstawie danych.

Powiązanie danych w programie WPF ma kilka zalet w porównaniu z tradycyjnymi modelami, a w tym nieodłączną obsługę powiązania danych przez szeroką gamę właściwości, elastyczną reprezentację danych w interfejsie użytkownika i czystą separację logiki biznesowej od interfejsu użytkownika.

W tym artykule omówiono najpierw pojęcia podstawowe dotyczące powiązania danych WPF, a następnie użycie klasy Binding i innych funkcji powiązania danych.

Ważne

Dokumentacja przewodnika dotyczącego aplikacji klasycznych dla platform .NET 6 i .NET 5 (w tym .NET Core 3.1) jest w trakcie tworzenia.

Co to jest powiązanie danych?

Powiązanie danych to proces, który ustanawia połączenie między interfejsem użytkownika aplikacji a wyświetlanymi danymi. Jeśli powiązanie ma poprawne ustawienia, a dane udostępniają odpowiednie powiadomienia, gdy zmieniają one jego wartość, elementy powiązane z danymi automatycznie odzwierciedlają zmiany. Powiązanie danych może również oznaczać, że jeśli zewnętrzna reprezentacja danych w elemencie ulegnie zmianie, dane bazowe można automatycznie zaktualizować, aby odzwierciedlić zmianę. Jeśli na przykład użytkownik edytuje wartość elementu TextBox, wartość danych bazowych zostanie automatycznie zaktualizowana w celu odzwierciedlenia tej zmiany.

Typowym zastosowaniem powiązania danych jest umieszczenie danych serwera lub lokalnej konfiguracji w formularzach lub innych kontrolkach interfejsu użytkownika. W programie WPF ta koncepcja została rozszerzona, aby uwzględnić powiązanie szerokiego zakresu właściwości z różnymi rodzajami źródeł danych. W programie WPF właściwości zależności elementów mogą być powiązane z obiektami platformy .NET (a w tym obiektami ADO.NET lub obiektami skojarzonymi z usługami i właściwościami internetowymi) oraz danymi XML.

Podstawowe pojęcia dotyczące powiązań danych

Niezależnie od tego, który element jest wiązany, i charakteru źródła danych, każde powiązanie zawsze jest zgodne z modelem przedstawionym na poniższej ilustracji.

Diagram przedstawiający podstawowy model powiązania danych.

Jak pokazano na rysunku, powiązanie danych jest zasadniczo mostem między obiektem docelowym powiązania a źródłem powiązania. Na rysunku przedstawiono następujące podstawowe pojęcia dotyczące powiązania danych WPF:

  • Zazwyczaj każde powiązanie ma cztery składniki:

    • Obiekt docelowy powiązania.
    • Właściwość docelową.
    • Źródło powiązania.
    • Ścieżkę do wartości w źródle powiązania do użycia.

    Jeśli na przykład wiązana jest zawartość elementu TextBox z właściwością Employee.Name, należy skonfigurować powiązanie, takie jak poniższa tabela:

    Ustawienie Wartość
    Cel TextBox
    Właściwość docelowa Text
    Obiekt źródłowy Employee
    Ścieżka wartości obiektu źródłowego Name
  • Właściwość docelowa musi być właściwością zależności.

    Większość właściwości UIElement to właściwości zależności, a większość właściwości zależności, z wyjątkiem właściwości tylko do odczytu, domyślnie obsługuje powiązanie danych. Właściwości zależności można definiować tylko w typach pochodnych DependencyObject. Wszystkie typy UIElement pochodzą z klasy DependencyObject.

  • Źródła powiązań nie są ograniczone do niestandardowych obiektów .NET.

    Chociaż nie pokazano na rysunku, należy zauważyć, że obiekt źródłowy powiązania nie jest ograniczony do bycia niestandardowym obiektem platformy .NET. Powiązanie danych WPF obsługuje dane w postaci obiektów .NET, XML, a nawet obiektów elementów XAML. Aby podać kilka przykładów, źródło powiązania może być UIElement, dowolnym obiektem listy, obiektem ADO.NET lub usług internetowych albo węzłem XmlNode zawierającym dane XML. Aby uzyskać więcej informacji, zobacz Omówienie źródeł powiązań.

Ważne jest, aby pamiętać, że podczas ustanawiania powiązania należy wiązać element docelowy powiązania ze źródłem powiązania. Jeśli na przykład wyświetlasz niektóre bazowe dane XML w ListBox przy użyciu powiązania danych, wiążesz ListBox z danymi XML.

Aby ustanowić powiązanie, należy użyć obiektu Binding. W pozostałej części tego artykułu omówiono wiele pojęć związanych z obiektem Binding oraz niektóre właściwości i zastosowania tego obiektu.

Kontekst danych

Gdy powiązanie danych jest deklarowane w elementach XAML, rozwiązuje powiązanie danych, sprawdzając ich natychmiastową właściwość DataContext. Kontekst danych jest zazwyczaj obiektem źródłowym powiązania dla oceny ścieżki wartości źródłowej powiązania. To zachowanie można zastąpić w powiązaniu i ustawić określoną wartość obiektu źródłowego powiązania. Jeśli właściwość DataContext obiektu hostująca powiązanie nie jest ustawiona, właściwość elementu nadrzędnego DataContext jest zaznaczona i tak dalej, aż do katalogu głównego drzewa obiektów XAML. Krótko mówiąc, kontekst danych używany do rozpoznawania powiązania jest dziedziczony z elementu nadrzędnego, chyba że jest jawnie ustawiony na obiekcie.

Powiązania można skonfigurować do rozpoznawania za pomocą określonego obiektu w przeciwieństwie do używania kontekstu danych do rozpoznawania powiązań. Określenie obiektu źródłowego bezpośrednio jest używane, gdy na przykład kolor pierwszego planu obiektu jest powiązany z kolorem tła innego obiektu. Kontekst danych nie jest potrzebny, ponieważ powiązanie jest rozpoznawane między tymi dwoma obiektami. Odwrotnie powiązania, które nie są powiązane z określonymi obiektami źródłowymi, używają rozpoznawania kontekstu danych.

Po zmianie właściwości DataContext wszystkie powiązania, na które może mieć wpływ kontekst danych, są ponownie oceniane.

Kierunek przepływu danych

Jak wskazano przez strzałkę na poprzedniej ilustracji, przepływ danych powiązania może przechodzić z elementu docelowego powiązania do źródła powiązania (na przykład wartość źródłowa zmienia się, gdy użytkownik edytuje wartość elementu TextBox) i/lub ze źródła powiązania do obiektu docelowego powiązania (na przykład zawartość TextBox jest aktualizowana za pomocą zmian w źródle powiązania), jeśli źródło powiązania zapewnia właściwe powiadomienia.

Aplikacja może umożliwić użytkownikom zmianę danych i propagację ich z powrotem do obiektu źródłowego. Możesz też nie chcieć zezwolić użytkownikom na aktualizowanie danych źródłowych. Przepływ danych można kontrolować, ustawiając wartość Binding.Mode.

Na rysunku przedstawiono różne typy przepływu danych:

Przepływ danych powiązania danych

  • Powiązanie OneWay powoduje, że zmiany właściwości źródłowej automatycznie aktualizują właściwość docelową, ale zmiany właściwości docelowej nie są propagowane z powrotem do właściwości źródłowej. Ten typ powiązania jest odpowiedni, jeśli wiązana kontrolka jest niejawnie tylko do odczytu. Na przykład można powiązać ze źródłem, takim jak znacznik akcji, lub być może właściwość docelowa nie ma interfejsu sterującego do wprowadzania zmian, takich jak kolor tła powiązanego z danymi tabeli. Jeśli nie ma potrzeby monitorowania zmian właściwości docelowej, użycie trybu powiązania OneWay pozwala uniknąć narzutu pracy w trybie powiązania TwoWay.

  • Powiązanie TwoWay powoduje zmiany właściwości źródłowej albo właściwości docelowej, aby automatycznie zaktualizować tę drugą. Ten typ powiązania jest odpowiedni dla formularzy edytowalnych lub innych w pełni interaktywnych scenariuszy interfejsu użytkownika. Większość właściwości jest domyślnie wiązana jako OneWay, ale niektóre właściwości zależności (zazwyczaj właściwości kontrolek edytowalnych przez użytkownika, takie jak TextBox.Text i CheckBox.IsChecked są domyślne powiązanie jako TwoWay. Programowy sposób określania, czy właściwość zależności wiąże się domyślnie jednokierunkowo, czy też dwukierunkowo, jest pobranie metadanych właściwości za pomocą polecenia DependencyProperty.GetMetadata, a następnie sprawdzenie wartości logicznej właściwości FrameworkPropertyMetadata.BindsTwoWayByDefault.

  • OneWayToSource jest odwrotnym powiązaniem OneWay; aktualizuje właściwość źródłową, gdy właściwość docelowa ulegnie zmianie. Jednym z przykładowych scenariuszy jest to, że wystarczy ponownie ocenić wartość źródłową z interfejsu użytkownika.

  • Nie pokazano na rysunku powiązania OneTime, co powoduje, że właściwość źródłowa inicjuje właściwość docelową, ale nie propaguje kolejnych zmian. Jeśli kontekst danych zmieni się lub obiekt w kontekście danych zmieni się, zmiana nie zostanie odzwierciedlona we właściwości docelowej. Ten typ powiązania jest odpowiedni, jeśli migawka bieżącego stanu jest odpowiednia albo dane są naprawdę statyczne. Ten typ powiązania jest również przydatny, jeśli chcesz zainicjować właściwość docelową pewną wartością z właściwości źródłowej, a kontekst danych nie jest wcześniej znany. Ten tryb jest zasadniczo prostszą formą powiązania OneWay, które zapewnia lepszą wydajność w przypadkach, gdy wartość źródłowa nie zmienia się.

Aby wykryć zmiany źródła (dotyczy powiązań OneWay i TwoWay), źródło musi wdrożyć odpowiedni mechanizm powiadamiania o zmianie właściwości, taki jak INotifyPropertyChanged. Zobacz Instrukcje: implementowanie powiadomienia o zmianie właściwości (.NET Framework), aby zapoznać się z przykładem implementacji INotifyPropertyChanged.

Właściwość Binding.Mode zawiera więcej informacji na temat trybów powiązań i przykład sposobu określania kierunku powiązania.

Co wyzwala aktualizacje źródła

Powiązania, które są TwoWay lub OneWayToSource nasłuchują zmian we właściwości docelowej i propagują je z powrotem do źródła, co jest znane jako aktualizowanie źródła. Na przykład możesz edytować tekst pola tekstowego, aby zmienić podstawową wartość źródłową.

Czy jednak wartość źródłowa jest aktualizowana podczas edytowania tekstu, czy też po zakończeniu edytowania tekstu, a kontrolka traci fokus? Właściwość Binding.UpdateSourceTrigger określa, co wyzwala aktualizację źródła. Kropki strzałki w prawo na poniższej ilustracji ilustrują rolę właściwości Binding.UpdateSourceTrigger.

Diagram przedstawiający rolę właściwości UpdateSourceTrigger.

Jeśli wartość UpdateSourceTrigger to UpdateSourceTrigger.PropertyChanged, wartość wskazywana przez strzałkę w prawo elementu TwoWay lub powiązania OneWayToSource jest aktualizowana zaraz po zmianie właściwości docelowej. Jeśli jednak wartość UpdateSourceTrigger to LostFocus, ta wartość jest aktualizowana tylko przy użyciu nowej wartości, gdy właściwość docelowa traci fokus.

Podobnie jak w przypadku właściwości Mode, różne właściwości zależności mają różne wartości domyślne UpdateSourceTrigger. Wartość domyślna dla większości właściwości zależności to PropertyChanged, co powoduje natychmiastową zmianę wartości właściwości źródłowej po zmianie wartości właściwości docelowej. Natychmiastowe zmiany są odpowiednie dla CheckBox i innych prostych kontrolek. Jednak w przypadku pól tekstowych aktualizacja po każdym naciśnięciu klawiszy może zmniejszyć wydajność i uniemożliwić użytkownikowi zwykłą możliwość cofnięcia się i naprawienia błędów pisowni przed zatwierdzeniem nowej wartości. Na przykład właściwość TextBox.Text jest domyślnie ustawiona na wartość UpdateSourceTrigger równą LostFocus, co powoduje zmianę wartości źródłowej tylko, gdy element kontrolki traci fokus, a nie po zmianie właściwości TextBox.Text. Zobacz stronę właściwości UpdateSourceTrigger, aby uzyskać informacje na temat sposobu znajdowania wartości domyślnej właściwości zależności.

W poniższej tabeli przedstawiono przykładowy scenariusz dla każdej wartości UpdateSourceTrigger przy użyciu elementu TextBox jako przykładu.

Wartość UpdateSourceTrigger Po zaktualizowaniu wartości źródłowej Przykładowy scenariusz dla pola tekstowego
LostFocus (wartość domyślna dla TextBox.Text) Gdy kontrolka TextBox traci fokus. Pole tekstowe skojarzone z logiką weryfikacji (patrz Sprawdzanie poprawności danych poniżej).
PropertyChanged Podczas wpisywania do elementu TextBox. Kontrolki TextBox w oknie pokoju rozmów.
Explicit Gdy aplikacja wywołuje metodę UpdateSource. Kontrolki TextBox w formularzu edytowalnym (aktualizuje wartości źródłowe tylko, gdy użytkownik naciśnie przycisk przesyłania).

Na przykład zobacz Jak kontrolować, kiedy tekst pola tekstowego aktualizuje źródło (.NET Framework).

Przykład powiązania danych

Aby zapoznać się z przykładem powiązania danych, przyjrzyj się następującemu interfejsowi użytkownika aplikacji z Data Binding Demo, który wyświetla listę przedmiotów aukcyjnych.

Zrzut ekranu przedstawiający przykład powiązania danych

Aplikacja demonstruje następujące funkcje powiązania danych:

  • Zawartość obiektu ListBox jest powiązana z kolekcją obiektów AuctionItem. Obiekt AuctionItem ma właściwości, takie jak Description, StartPrice, StartDate, Category i SpecialFeatures.

  • Dane (obiektów AuctionItem) wyświetlane w obiekcie ListBox są dopasowywane do szablonu, aby opis i bieżąca cena zostały wyświetlone dla każdego elementu. Szablon jest tworzony przy użyciu elementu DataTemplate. Ponadto wygląd każdego elementu zależy od wartości SpecialFeatures wyświetlanego elementu AuctionItem. Jeśli wartość SpecialFeatures elementu AuctionItem to Color, element ma niebieskie obramowanie. Jeśli wartość to Highlight, element ma pomarańczowe obramowanie i gwiazdkę. Sekcja Szablony danych zawiera informacje o tworzeniu szablonów danych.

  • Użytkownik może grupować, filtrować lub sortować dane przy użyciu podanego elementu CheckBoxes. Na powyższej ilustracji wybrano pozycję Grupuj według kategorii i Sortuj według kategorii i datyCheckBoxes. Być może zauważysz, że dane są pogrupowane na podstawie kategorii produktu, a nazwa kategorii jest w kolejności alfabetycznej. Trudno zauważyć na obrazie, ale elementy są również sortowane według daty rozpoczęcia w ramach każdej kategorii. Sortowanie odbywa się przy użyciu widoku kolekcji. W sekcji Wiązanie z kolekcjami omówiono widoki kolekcji.

  • Gdy użytkownik wybierze element, ContentControl wyświetli szczegóły wybranego elementu. To środowisko jest nazywane scenariuszem główny-szczegółowy. Sekcja scenariusza główny-szczegółowy zawiera informacje o tym typie powiązania.

  • Typ właściwości StartDate to DateTime, co zwraca datę zawierającą godzinę do milisekund. W tej aplikacji użyto niestandardowego konwertera, aby był wyświetlany krótszy ciąg daty. Sekcja Konwersja danych zawiera informacje o konwerterach.

Gdy użytkownik wybierze przycisk Dodaj produkt, zostanie wyświetlony następujący formularz.

Dodaj stronę listy produktów

Użytkownik może edytować pola w formularzu, wyświetlić podgląd oferty produktów przy użyciu krótkich lub szczegółowych okienek podglądu i wybrać Submit, aby dodać nową listę produktów. Wszystkie istniejące ustawienia grupowania, filtrowania i sortowania będą stosowane do nowego wpisu. W tym konkretnym przypadku element wprowadzony na powyższym obrazie będzie wyświetlany jako drugi element w kategorii Komputer.

Na tej ilustracji nie pokazano logiki weryfikacji podanej w dacie rozpoczęciaTextBox. Jeśli użytkownik wprowadzi nieprawidłową datę (nieprawidłowe formatowanie lub wcześniejszą datę), zostanie on powiadomiony za pomocą ToolTip i czerwonego wykrzyknika obok TextBox. W sekcji Walidacja danych omówiono sposób tworzenia logiki walidacji.

Przed przejściem do różnych funkcji powiązania danych opisanych powyżej najpierw omówimy podstawowe pojęcia, które mają kluczowe znaczenie dla zrozumienia powiązania danych WPF.

Tworzenie powiązania

Aby odtworzyć niektóre pojęcia omówione w poprzednich sekcjach, należy ustanowić powiązanie przy użyciu obiektu Binding, a każde powiązanie ma zwykle cztery składniki: obiekt docelowy powiązania, właściwość docelową, źródło powiązania i ścieżkę do wartości źródłowej do użycia. W tej sekcji omówiono sposób konfigurowania powiązania.

Źródła powiązań są powiązane z aktywnym DataContext dla elementu. Elementy automatycznie dziedziczą DataContext, jeśli nie zostały jawnie zdefiniowane.

Rozważmy poniższy przykład, w którym obiekt źródłowy powiązania jest klasą o nazwie MyData zdefiniowaną w przestrzeni nazw SDKSample. W celach demonstracyjnych klasa MyData ma właściwość ciągu o nazwie ColorName, której wartość jest ustawiona na „Czerwony”. W efekcie ten przykład generuje przycisk z czerwonym tłem.

<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:c="clr-namespace:SDKSample">
    <DockPanel.Resources>
        <c:MyData x:Key="myDataSource"/>
    </DockPanel.Resources>
    <DockPanel.DataContext>
        <Binding Source="{StaticResource myDataSource}"/>
    </DockPanel.DataContext>
    <Button Background="{Binding Path=ColorName}"
            Width="150" Height="30">
        I am bound to be RED!
    </Button>
</DockPanel>

Aby uzyskać więcej informacji na temat składni deklaracji powiązania i przykładów sposobu konfigurowania powiązania w kodzie, zobacz Omówienie deklaracji powiązań.

Jeśli zastosujemy ten przykład do naszego podstawowego diagramu, wynikowy rysunek będzie wyglądać podobnie do poniższego. Na tym rysunku opisano powiązanie OneWay, ponieważ właściwość Background domyślnie obsługuje powiązanie OneWay.

Diagram przedstawiający właściwość Background powiązania danych.

Możesz się zastanawiać, dlaczego to powiązanie działa, mimo że właściwość ColorName jest typu ciąg, podczas gdy właściwość Background jest typu Brush. To powiązanie używa domyślnej konwersji typów, która została omówiona w sekcji Konwersja danych.

Określanie źródła powiązania

Zwróć uwagę, że w poprzednim przykładzie źródło powiązania jest określane przez ustawienie właściwości DockPanel.DataContext. Następnie Button dziedziczy wartość DataContext z DockPanel elementu, który jest jego elementem nadrzędnym. Aby powtórzyć, obiekt źródłowy powiązania jest jednym z czterech niezbędnych składników powiązania. Dlatego bez określonego obiektu źródłowego powiązania powiązanie nie zrobi nic.

Istnieje kilka sposobów określania obiektu źródłowego powiązania. Użycie właściwości DataContext do elementu nadrzędnego jest przydatne w przypadku wiązania wielu właściwości z tym samym źródłem. Czasami jednak bardziej odpowiednie może być określenie źródła powiązania dla poszczególnych deklaracji powiązań. W poprzednim przykładzie zamiast używać właściwości DataContext, można określić źródło powiązania, ustawiając właściwość Binding.Source bezpośrednio w deklaracji powiązania przycisku, jak w poniższym przykładzie.

<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:c="clr-namespace:SDKSample">
    <DockPanel.Resources>
        <c:MyData x:Key="myDataSource"/>
    </DockPanel.Resources>
    <Button Background="{Binding Source={StaticResource myDataSource}, Path=ColorName}"
            Width="150" Height="30">
        I am bound to be RED!
    </Button>
</DockPanel>

Poza ustawieniem właściwości DataContext elementu bezpośrednio dziedziczenie wartości DataContext z obiektu przodka (na przykład przycisku w pierwszym przykładzie) i jawne określenie źródła powiązania przez ustawienie właściwości Binding.Source powiązania (na przykład przycisku ostatniego przykładu), możesz również użyć właściwości Binding.ElementName lub właściwości Binding.RelativeSource w celu określenia źródła powiązania. Właściwość ElementName jest przydatna podczas tworzenia powiązań z innymi elementami w aplikacji, takimi jak użycie suwaka w celu dostosowania szerokości przycisku. Właściwość RelativeSource jest przydatna, gdy powiązanie jest określone w obiekcie ControlTemplate lub Style. Aby uzyskać więcej informacji, zobacz Omówienie źródeł powiązań.

Określanie ścieżki do wartości

Jeśli źródło powiązania jest obiektem, należy użyć właściwości Binding.Path, aby określić wartość do użycia dla powiązania. Jeśli tworzysz powiązanie z danymi XML, użyj właściwości Binding.XPath, aby określić wartość. W niektórych przypadkach może to mieć zastosowanie do używania właściwości Path nawet, gdy dane są danymi XML. Jeśli na przykład chcesz uzyskać dostęp do właściwości Name zwróconego elementu XmlNode (w wyniku zapytania XPath), należy użyć właściwości Path oprócz właściwości XPath.

Aby uzyskać więcej informacji, zobacz właściwości Path i XPath.

Mimo że podkreśliliśmy, że wartość Path do użycia jest jednym z czterech niezbędnych składników powiązania, w scenariuszach, które chcesz powiązać z całym obiektem, wartość do użycia będzie taka sama jak obiekt źródłowy powiązania. W takich przypadkach ma zastosowanie do braku określenia elementu Path. Rozważmy następujący przykład.

<ListBox ItemsSource="{Binding}"
         IsSynchronizedWithCurrentItem="true"/>

W powyższym przykładzie użyto pustej składni powiązania: {Binding}. W tym przypadku element ListBox dziedziczy DataContext z nadrzędnego elementu DockPanel (nie pokazano w tym przykładzie). Jeśli ścieżka nie zostanie określona, wartością domyślną jest powiązanie z całym obiektem. Innymi słowy, w tym przykładzie ścieżka została pominięta, ponieważ wiążemy właściwość ItemsSource z całym obiektem. (Zobacz sekcję Wiązanie z kolekcjami, aby zapoznać się ze szczegółową dyskusją).

Poza powiązaniem z kolekcją ten scenariusz jest również przydatny, gdy chcesz powiązać z całym obiektem zamiast, a nie tylko z jedną właściwością obiektu. Jeśli na przykład obiekt źródłowy jest typu String, możesz po prostu chcieć powiązać z samym ciągiem. Innym typowym scenariuszem jest powiązanie elementu z obiektem z kilkoma właściwościami.

Może być konieczne zastosowanie logiki niestandardowej, aby dane miały znaczenie dla powiązanej właściwości docelowej. Logika niestandardowa może mieć postać konwertera niestandardowego, jeśli domyślna konwersja typu nie istnieje. Zobacz Konwersja danych, aby uzyskać informacje o konwerterach.

Wiązanie i BindingExpression

Przed rozpoczęciem pracy z innymi funkcjami i użyciem powiązania danych warto wprowadzić klasę BindingExpression. Jak pokazano w poprzednich sekcjach, klasa Binding jest klasą wysokiego poziomu dla deklaracji powiązania. Zawiera ona wiele właściwości, które umożliwiają określenie cech powiązania. Powiązana klasa, BindingExpression, jest obiektem bazowym, który utrzymuje połączenie między źródłem a obiektem docelowym. Powiązanie zawiera wszystkie informacje, które mogą być współużytkowane przez kilka wyrażeń powiązań. BindingExpression jest wyrażeniem wystąpienia, którego nie można udostępnić i zawiera wszystkie informacje o wystąpieniu obiektu Binding.

Rozważmy następujący przykład, gdzie myDataObject jest wystąpieniem klasy MyData, myBinding jest obiektem źródłowym Binding i MyData jest zdefiniowaną klasą zawierającą właściwość ciągu o nazwie ColorName. W tym przykładzie zawartość tekstowa klasy myText, wystąpienie klasy TextBlock są powiązane z elementem ColorName.

// Make a new source
var myDataObject = new MyData();
var myBinding = new Binding("ColorName")
{
    Source = myDataObject
};

// Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding);
' Make a New source
Dim myDataObject As New MyData
Dim myBinding As New Binding("ColorName")
myBinding.Source = myDataObject

' Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding)

Do utworzenia innych powiązań można użyć tego samego obiektu myBinding. Na przykład można użyć obiektu myBinding, aby powiązać zawartość tekstową pola wyboru z właściwością colorName. W tym scenariuszu będą istnieć dwa wystąpienia BindingExpression współużytkujące obiekt myBinding.

Obiekt BindingExpression jest zwracany przez wywołanie GetBindingExpression obiektu powiązanego z danymi. W poniższych artykułach przedstawiono niektóre użycia klasy BindingExpression:

Konwersja danych

W sekcji Tworzenie powiązania przycisk jest czerwony, ponieważ jego właściwość Background jest powiązana z właściwością ciągu o wartości „Czerwony”. Ta wartość ciągu działa, ponieważ konwerter typów jest obecny w typie Brush, aby przekonwertować wartość ciągu na wartość Brush.

Dodanie tych informacji do rysunku w sekcji Tworzenie powiązania wygląda następująco.

Diagram przedstawiający właściwość Default powiązania danych.

Co jednak zrobić, jeśli zamiast właściwości typu ciąg obiekt źródłowy powiązania ma właściwość Color typu Color? W takim przypadku, aby powiązanie działało, należy najpierw przekształcić wartość właściwości Color w coś, co akceptuje właściwość Background. Należy utworzyć konwerter niestandardowy, implementując interfejs IValueConverter, jak w poniższym przykładzie.

[ValueConversion(typeof(Color), typeof(SolidColorBrush))]
public class ColorBrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Color color = (Color)value;
        return new SolidColorBrush(color);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }
}
<ValueConversion(GetType(Color), GetType(SolidColorBrush))>
Public Class ColorBrushConverter
    Implements IValueConverter
    Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.Convert
        Dim color As Color = CType(value, Color)
        Return New SolidColorBrush(color)
    End Function

    Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
        Return Nothing
    End Function
End Class

Aby uzyskać więcej informacji, zobacz IValueConverter.

Teraz niestandardowy konwerter jest używany zamiast konwersji domyślnej, a nasz diagram wygląda następująco.

Diagram przedstawiający niestandardowy konwerter powiązania danych.

Aby powtórzyć, konwersje domyślne mogą być dostępne ze względu na konwertery typów, które są obecne w typie wiązanym. To zachowanie będzie zależeć od tego, które konwertery typów są dostępne w obiekcie docelowym. W razie wątpliwości utwórz własny konwerter.

Poniżej przedstawiono kilka typowych scenariuszy, w których warto zaimplementować konwerter danych:

  • Dane powinny być wyświetlane inaczej w zależności od kultury. Na przykład można zaimplementować konwerter walut lub konwerter daty/godziny kalendarza na podstawie konwencji używanych w określonej kulturze.

  • Używane dane niekoniecznie są przeznaczone do zmiany wartości tekstowej właściwości, ale zamiast tego mają na celu zmianę innej wartości, takiej jak źródło obrazu, kolor lub styl tekstu wyświetlanego. Konwertery mogą być używane w tym wystąpieniu poprzez przekonwertowanie powiązania właściwości, która może nie być odpowiednia, na przykład powiązanie pola tekstowego z właściwością Background komórki tabeli.

  • Do tych samych danych jest powiązanych więcej niż jedna kontrolka lub wiele właściwości kontrolek. W takim przypadku powiązanie podstawowe może po prostu wyświetlać tekst, podczas gdy inne powiązania obsługują określone problemy z wyświetlaniem, ale nadal używają tego samego powiązania, co informacje źródłowe.

  • Właściwość docelowa zawiera kolekcję powiązań, która jest określana jako MultiBinding. W przypadku elementu MultiBinding należy użyć niestandardowego elementu IMultiValueConverter, aby wygenerować ostateczną wartość z wartości powiązań. Na przykład kolor może być obliczany z czerwonych, niebieskich i zielonych wartości, które mogą być wartościami z tych samych lub różnych obiektów źródłowych powiązania. Zobacz MultiBinding, aby poznać przykłady i informacje.

Powiązanie z kolekcjami

Obiekt źródłowy powiązania może być traktowany jako pojedynczy obiekt, którego właściwości zawierają dane, lub jako kolekcja danych obiektów polimorficznych, które są często grupowane razem (na przykład wynik zapytania do bazy danych). Do tej pory omówiliśmy tylko powiązanie z pojedynczymi obiektami. Jednak powiązanie z kolekcją danych jest typowym scenariuszem. Na przykład typowym scenariuszem jest użycie elementu ItemsControl, takiego jak ListBox, ListView lub TreeView w celu wyświetlenia kolekcji danych, na przykład w aplikacji pokazanej w sekcji Co to jest powiązanie danych.

Na szczęście nasz podstawowy diagram nadal ma zastosowanie. Jeśli tworzysz powiązanie ItemsControl z kolekcją, diagram wygląda następująco.

Diagram przedstawiający obiekt ItemsControl powiązania danych.

Jak pokazano na tym diagramie, aby powiązać element ItemsControl z obiektem kolekcji, właściwość ItemsControl.ItemsSource jest właściwością do użycia. Możesz traktować ItemsSource jako zawartość elementu ItemsControl. Powiązanie to OneWay, ponieważ właściwość ItemsSource domyślnie obsługuje powiązanie OneWay.

Jak zaimplementować kolekcje

Możesz wyliczyć dowolną kolekcję, która implementuje interfejs IEnumerable. Aby jednak skonfigurować powiązania dynamiczne tak, aby wstawienie lub usunięcie w kolekcji automatycznie zaktualizowało interfejs użytkownika, kolekcja musi zaimplementować interfejs INotifyCollectionChanged. Ten interfejs uwidacznia zdarzenie, które powinno być zgłaszane za każdym razem, gdy podstawowa kolekcja ulegnie zmianie.

WPF udostępnia klasę ObservableCollection<T>, która jest wbudowaną implementacją kolekcji danych, która uwidacznia interfejs INotifyCollectionChanged. Aby w pełni obsługiwać przesyłanie wartości danych z obiektów źródłowych do obiektów docelowych, każdy obiekt w kolekcji obsługujący właściwości możliwe do powiązania musi również implementować interfejs INotifyPropertyChanged. Aby uzyskać więcej informacji, zobacz Omówienie źródeł powiązań.

Przed wdrożeniem własnej kolekcji rozważ użycie ObservableCollection<T> lub jednej z istniejących klas kolekcji, takich jak, między innymi, List<T>, Collection<T> i BindingList<T>. Jeśli masz zaawansowany scenariusz i chcesz zaimplementować własną kolekcję, rozważ użycie metody IList, która udostępnia inną niż ogólna kolekcję obiektów, do których można uzyskać dostęp indywidualnie przez indeks, a tym samym zapewnia najlepszą wydajność.

Widoki kolekcji

Po powiązaniu ItemsControl z kolekcją danych możesz sortować, filtrować lub grupować dane. W tym celu należy użyć widoków kolekcji, czyli klas implementujących interfejs ICollectionView.

Co to są widoki kolekcji?

Widok kolekcji jest warstwą powyżej kolekcji źródłowej powiązania, która umożliwia nawigowanie i wyświetlanie kolekcji źródłowej na podstawie zapytań sortowania, filtrowania i grupowania bez konieczności zmieniania samej bazowej kolekcji źródłowej. Widok kolekcji zachowuje również wskaźnik do bieżącego elementu w kolekcji. Jeśli kolekcja źródłowa implementuje interfejs INotifyCollectionChanged, zmiany zgłoszone przez zdarzenie CollectionChanged są propagowane do widoków.

Ponieważ widoki nie zmieniają bazowych kolekcji źródłowych, każda kolekcja źródłowa może mieć skojarzone wiele widoków. Na przykład może istnieć kolekcja obiektów Task. Za pomocą widoków można wyświetlać te same dane na różne sposoby. Na przykład po lewej stronie możesz wyświetlić zadania posortowane według priorytetu, a po prawej stronie — pogrupowane według obszaru.

Jak utworzyć widok

Jednym ze sposobów tworzenia i używania widoku jest utworzenie wystąpienia obiektu widoku bezpośrednio, a następnie użycie go jako źródła powiązania. Rozważmy na przykład Pokaz powiązania danych aplikację wyświetlaną w sekcji Co to jest powiązanie danych. Aplikacja jest implementowana tak, aby ListBox wiązała z widokiem kolekcji danych zamiast bezpośrednio z kolekcją danych. Poniższy przykład został wyodrębniony z aplikacji Pokaz powiązania danych. Klasa CollectionViewSource jest serwerem proxy XAML klasy dziedziczonej z klasy CollectionView. W tym konkretnym przykładzie element Source widoku jest powiązany z kolekcją AuctionItems (typu ObservableCollection<T>) bieżącego obiektu aplikacji.

<Window.Resources>
    <CollectionViewSource 
      Source="{Binding Source={x:Static Application.Current}, Path=AuctionItems}"   
      x:Key="listingDataView" />
</Window.Resources>

Zasób listingDataView następnie służy jako źródło powiązania dla elementów w aplikacji, takich jak ListBox.

<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8" 
         ItemsSource="{Binding Source={StaticResource listingDataView}}" />

Aby utworzyć inny widok dla tej samej kolekcji, możesz utworzyć inne wystąpienie CollectionViewSource i nadać mu inną nazwę x:Key.

W poniższej tabeli przedstawiono typy danych widoku tworzone jako domyślny widok kolekcji lub przez CollectionViewSource na podstawie typu kolekcji źródłowej.

Typ kolekcji źródłowej Typ widoku kolekcji Uwagi
IEnumerable Typ wewnętrzny oparty na CollectionView Nie można zgrupować elementów.
IList ListCollectionView Najszybszy.
IBindingList BindingListCollectionView

Korzystanie z widoku domyślnego

Określenie widoku kolekcji jako źródła powiązania jest jednym ze sposobów tworzenia i używania widoku kolekcji. WPF tworzy również domyślny widok kolekcji dla każdej kolekcji używanej jako źródło powiązania. Jeśli powiążesz bezpośrednio z kolekcją, WPF wiąże się z jej widokiem domyślnym. Ten widok domyślny jest współużytkowany przez wszystkie powiązania z tą samą kolekcją, więc zmiany wprowadzone w widoku domyślnym przez jedną powiązaną kontrolkę lub kod (takie jak sortowanie lub zmiana w bieżącym wskaźniku do elementu, które zostaną omówionego później) zostaną odzwierciedlone we wszystkich innych powiązaniach z tą samą kolekcją.

Aby uzyskać widok domyślny, użyj metody GetDefaultView. Aby zapoznać się z przykładem, zobacz Pobieranie domyślnego widoku kolekcji danych (.NET Framework).

Widoki kolekcji z tabelami ADO.NET DataTables

Aby zwiększyć wydajność, widoki kolekcji dla ADO.NET obiekty DataTable lub DataView delegują sortowanie i filtrowanie do obiektu DataView, co powoduje udostępnianie sortowania i filtrowania we wszystkich widokach kolekcji źródła danych. Aby umożliwić każdemu widokowi kolekcji sortowanie i filtrowanie niezależnie, zainicjuj każdy widok kolekcji własnym obiektem DataView.

Sortowanie

Jak wspomniano wcześniej, widoki mogą stosować kolejność sortowania do kolekcji. Ponieważ istnieje to w kolekcji bazowej, dane mogą mieć odpowiedni, nieodłączny porządek lub mogą go nie mieć. Widok nad kolekcją umożliwia narzucenie kolejności lub zmianę kolejności domyślnej na podstawie kryteriów porównania, które podajesz. Ponieważ jest to widok danych oparty na kliencie, typowym scenariuszem jest to, że użytkownik może chcieć sortować kolumny danych tabelarycznych zgodnie z wartością odpowiadającą kolumnie. Za pomocą widoków można ponownie zastosować sortowanie oparte na użytkownikach bez wprowadzania żadnych zmian w kolekcji źródłowej, a nawet konieczności ponownego wprowadzania zapytania dla zawartości kolekcji. Aby zapoznać się z przykładem, zobacz Sortowanie kolumny GridView po kliknięciu nagłówka (.NET Framework).

Poniższy przykład przedstawia logikę sortowania „Sortuj według kategorii i daty” CheckBox interfejsu użytkownika aplikacji w sekcji Co to jest powiązanie danych.

private void AddSortCheckBox_Checked(object sender, RoutedEventArgs e)
{
    // Sort the items first by Category and then by StartDate
    listingDataView.SortDescriptions.Add(new SortDescription("Category", ListSortDirection.Ascending));
    listingDataView.SortDescriptions.Add(new SortDescription("StartDate", ListSortDirection.Ascending));
}
Private Sub AddSortCheckBox_Checked(sender As Object, e As RoutedEventArgs)
    ' Sort the items first by Category And then by StartDate
    listingDataView.SortDescriptions.Add(New SortDescription("Category", ListSortDirection.Ascending))
    listingDataView.SortDescriptions.Add(New SortDescription("StartDate", ListSortDirection.Ascending))
End Sub

Filtrowanie

Widoki mogą również stosować filtr do kolekcji, tak aby widok pokazywał tylko określony podzbiór pełnej kolekcji. Możesz filtrować według warunku w danych. Na przykład, podobnie jak to jest wykonywane przez aplikację w sekcji Co to jest powiązanie danych, „Pokaż tylko okazje” CheckBox zawiera logikę filtrowania elementów, które kosztują 25 USD lub więcej. Poniższy kod jest wykonywany, aby ustawić właściwość ShowOnlyBargainsFilter jako procedurę obsługi zdarzeń Filter po wybraniu opcji CheckBox.

private void AddFilteringCheckBox_Checked(object sender, RoutedEventArgs e)
{
    if (((CheckBox)sender).IsChecked == true)
        listingDataView.Filter += ListingDataView_Filter;
    else
        listingDataView.Filter -= ListingDataView_Filter;
}
Private Sub AddFilteringCheckBox_Checked(sender As Object, e As RoutedEventArgs)
    Dim checkBox = DirectCast(sender, CheckBox)

    If checkBox.IsChecked = True Then
        AddHandler listingDataView.Filter, AddressOf ListingDataView_Filter
    Else
        RemoveHandler listingDataView.Filter, AddressOf ListingDataView_Filter
    End If
End Sub

Procedura obsługi zdarzeń ShowOnlyBargainsFilter ma następującą implementację.

private void ListingDataView_Filter(object sender, FilterEventArgs e)
{
    // Start with everything excluded
    e.Accepted = false;

    // Only inlcude items with a price less than 25
    if (e.Item is AuctionItem product && product.CurrentPrice < 25)
        e.Accepted = true;
}
Private Sub ListingDataView_Filter(sender As Object, e As FilterEventArgs)

    ' Start with everything excluded
    e.Accepted = False

    Dim product As AuctionItem = TryCast(e.Item, AuctionItem)

    If product IsNot Nothing Then

        ' Only include products with prices lower than 25
        If product.CurrentPrice < 25 Then e.Accepted = True

    End If

End Sub

Jeśli używasz jednej z klas CollectionView bezpośrednio zamiast CollectionViewSource, użyj właściwości Filter, aby określić wywołanie zwrotne. Aby zapoznać się z przykładem, zobacz Filtrowanie danych w widoku (.NET Framework).

Grupowanie

Z wyjątkiem klasy wewnętrznej, która wyświetla kolekcję IEnumerable, wszystkie widoki kolekcji obsługują grupowanie, co umożliwia użytkownikowi partycjonowanie kolekcji w widoku kolekcji na grupy logiczne. Grupy mogą być jawne, gdy użytkownik dostarcza listę grup, lub niejawne, gdy grupy są generowane dynamicznie w zależności od danych.

W poniższym przykładzie przedstawiono logikę „Grupuj według kategorii” CheckBox.

// This groups the items in the view by the property "Category"
var groupDescription = new PropertyGroupDescription();
groupDescription.PropertyName = "Category";
listingDataView.GroupDescriptions.Add(groupDescription);
' This groups the items in the view by the property "Category"
Dim groupDescription = New PropertyGroupDescription()
groupDescription.PropertyName = "Category"
listingDataView.GroupDescriptions.Add(groupDescription)

Aby uzyskać inny przykład grupowania, zobacz Grupowanie elementów w widoku listy, który implementuje widok siatki (.NET Framework).

Bieżące wskaźniki elementów

Widoki obsługują również pojęcie bieżącego elementu. Możesz nawigować po obiektach w widoku kolekcji. Podczas nawigowania przenosisz wskaźnik elementu, który umożliwia pobranie obiektu, który istnieje w tej określonej lokalizacji w kolekcji. Aby zapoznać się z przykładem, zobacz Nawigowanie po obiektach w widoku kolekcji danych (.NET Framework).

Ponieważ WPF wiąże się z kolekcją tylko przy użyciu widoku (określonego widoku albo widoku domyślnego kolekcji), wszystkie powiązania z kolekcjami mają bieżący wskaźnik elementu. W przypadku powiązania z widokiem ukośnik („/”) w wartości Path wyznacza bieżący element widoku. W poniższym przykładzie kontekst danych jest widokiem kolekcji. Pierwszy wiersz jest powiązany z kolekcją. Drugi wiersz wiąże się z bieżącym elementem w kolekcji. Trzeci wiersz wiąże się z właściwością Description bieżącego elementu w kolekcji.

<Button Content="{Binding }" />
<Button Content="{Binding Path=/}" />
<Button Content="{Binding Path=/Description}" />

Składnia ukośnika i właściwości może być również układana w stos w celu przechodzenia przez hierarchię kolekcji. Poniższy przykład wiąże się z bieżącym elementem kolekcji o nazwie Offices, który jest właściwością bieżącego elementu kolekcji źródłowej.

<Button Content="{Binding /Offices/}" />

Na bieżący wskaźnik elementu może mieć wpływ dowolne sortowanie lub filtrowanie stosowane do kolekcji. Sortowanie zachowuje bieżący wskaźnik elementu na wybranym ostatnim elemencie, ale struktura widoku kolekcji jest teraz ustawiana wokół niego. (Być może wybrany element był na początku listy wcześniej, ale teraz wybrany element może być gdzieś w środku). Filtrowanie zachowuje wybrany element, jeśli ten wybór pozostaje w widoku po filtrowaniu. W przeciwnym razie bieżący wskaźnik elementu jest ustawiony na pierwszy element przefiltrowanego widoku kolekcji.

Scenariusz powiązania główny-szczegółowy

Pojęcie bieżącego elementu jest przydatne nie tylko w przypadku nawigacji po elementach w kolekcji, ale także dla scenariusza powiązania główny-szczegółowy. Ponownie rozważ interfejs użytkownika aplikacji w sekcji Co to jest powiązanie danych. W tej aplikacji wybór w elemencie ListBox określa zawartość wyświetlaną w elemencie ContentControl. Mówiąc inaczej, po wybraniu elementu ListBox element ContentControl wyświetla szczegóły wybranego elementu.

Scenariusz główny-szczegółowy można zaimplementować po prostu używając co najmniej dwóch kontrolek powiązanych z tym samym widokiem. Poniższy przykład z Pokaz powiązania danych pokazuje znaczniki elementu ListBox oraz elementu ContentControl widoczne w interfejsie użytkownika aplikacji w sekcji Co to jest powiązanie danych.

<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8" 
         ItemsSource="{Binding Source={StaticResource listingDataView}}" />
<ContentControl Name="Detail" Grid.Row="3" Grid.ColumnSpan="3"
                Content="{Binding Source={StaticResource listingDataView}}"
                ContentTemplate="{StaticResource detailsProductListingTemplate}" 
                Margin="9,0,0,0"/>

Zwróć uwagę, że obie kontrolki są powiązane z tym samym źródłem — zasobem statycznym listDataView (zobacz definicję tego zasobu w sekcji Jak utworzyć widok). To powiązanie działa, ponieważ, gdy pojedynczy obiekt (w tym przypadku ContentControl) jest powiązany z widokiem kolekcji, automatycznie ono wiąże się z CurrentItem widoku. Obiekty CollectionViewSource są automatycznie synchronizowane z walutą i zaznaczeniem. Jeśli kontrolka listy nie jest powiązana z obiektem CollectionViewSource, jak w tym przykładzie, należy ustawić jej właściwość IsSynchronizedWithCurrentItem na wartość true, aby to działało.

Aby zapoznać się z innymi przykładami, zobacz Powiązanie z kolekcją i wyświetlanie informacji na podstawie zaznaczenia (.NET Framework) i Używanie wzorca główny-szczegółowy z danymi hierarchicznymi (.NET Framework).

Można zauważyć, że w powyższym przykładzie użyto szablonu. W rzeczywistości dane nie będą wyświetlane w sposób, w jaki chcemy, bez użycia szablonów (ten jest jawnie używany przez element ContentControl i używany niejawnie przez element ListBox). Teraz przejdźmy do tworzenia szablonów danych w następnej sekcji.

Tworzenie szablonów danych

Bez użycia szablonów danych nasz interfejs użytkownika aplikacji w sekcji Przykład powiązania danych wyglądałby następująco:

Pokaz powiązania danych bez szablonów danych

Jak pokazano w przykładzie w poprzedniej sekcji, zarówno kontrolka ListBox, jak i obiekt ContentControl są powiązane z całym obiektem kolekcji (lub dokładniej widokiem na obiekt kolekcji) elementu AuctionItem. Bez konkretnych instrukcji wyświetlania kolekcji danych ListBox wyświetla reprezentację ciągu każdego obiektu w kolekcji bazowej, a ContentControl wyświetla reprezentację ciągu obiektu, z którym jest powiązany.

Aby rozwiązać ten problem, aplikacja definiuje elementy DataTemplates. Jak pokazano w przykładzie w poprzedniej sekcji, ContentControl jawnie używa szablonu danych DetailsProductListingTemplate. Kontrolka ListBox niejawnie używa następującego szablonu danych podczas wyświetlania obiektów AuctionItem w kolekcji.

<DataTemplate DataType="{x:Type src:AuctionItem}">
    <Border BorderThickness="1" BorderBrush="Gray"
            Padding="7" Name="border" Margin="3" Width="500">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="20"/>
                <ColumnDefinition Width="86"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>

            <Polygon Grid.Row="0" Grid.Column="0" Grid.RowSpan="4"
                     Fill="Yellow" Stroke="Black" StrokeThickness="1"
                     StrokeLineJoin="Round" Width="20" Height="20"
                     Stretch="Fill"
                     Points="9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7"
                     Visibility="Hidden" Name="star"/>

            <TextBlock Grid.Row="0" Grid.Column="1" Margin="0,0,8,0"
                       Name="descriptionTitle"
                       Style="{StaticResource smallTitleStyle}">Description:</TextBlock>
            
            <TextBlock Name="DescriptionDTDataType" Grid.Row="0" Grid.Column="2"
                       Text="{Binding Path=Description}"
                       Style="{StaticResource textStyleTextBlock}"/>

            <TextBlock Grid.Row="1" Grid.Column="1" Margin="0,0,8,0"
                       Name="currentPriceTitle"
                       Style="{StaticResource smallTitleStyle}">Current Price:</TextBlock>
            
            <StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal">
                <TextBlock Text="$" Style="{StaticResource textStyleTextBlock}"/>
                <TextBlock Name="CurrentPriceDTDataType"
                           Text="{Binding Path=CurrentPrice}" 
                           Style="{StaticResource textStyleTextBlock}"/>
            </StackPanel>
        </Grid>
    </Border>
    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding Path=SpecialFeatures}">
            <DataTrigger.Value>
                <src:SpecialFeatures>Color</src:SpecialFeatures>
            </DataTrigger.Value>
            <DataTrigger.Setters>
                <Setter Property="BorderBrush" Value="DodgerBlue" TargetName="border" />
                <Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
                <Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
                <Setter Property="BorderThickness" Value="3" TargetName="border" />
                <Setter Property="Padding" Value="5" TargetName="border" />
            </DataTrigger.Setters>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=SpecialFeatures}">
            <DataTrigger.Value>
                <src:SpecialFeatures>Highlight</src:SpecialFeatures>
            </DataTrigger.Value>
            <Setter Property="BorderBrush" Value="Orange" TargetName="border" />
            <Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
            <Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
            <Setter Property="Visibility" Value="Visible" TargetName="star" />
            <Setter Property="BorderThickness" Value="3" TargetName="border" />
            <Setter Property="Padding" Value="5" TargetName="border" />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

Korzystając z tych dwóch elementów DataTemplates, wynikowy interfejs użytkownika wygląda tak, jak wyświetlany w sekcji Co to jest powiązanie danych. Jak widać na tym zrzucie ekranu, oprócz umożliwienia umieszczania danych w kontrolkach, element DataTemplates umożliwia definiowanie atrakcyjnych wizualizacji dla danych. Na przykład elementy DataTrigger są używane w powyższym elemencie DataTemplate, aby elementy AuctionItem o wartości SpecialFeatures równej HighLight były wyświetlane z pomarańczowym obramowaniem i gwiazdką.

Aby uzyskać więcej informacji na temat szablonów danych, zobacz Omówienie tworzenia szablonów danych (.NET Framework).

Walidacja danych

Większość aplikacji, które przyjmują dane wejściowe użytkownika, musi mieć logikę walidacji, aby upewnić się, że użytkownik wprowadził oczekiwane informacje. Sprawdzanie poprawności może być oparte na typie, zakresie, formacie lub innych wymaganiach specyficznych dla aplikacji. W tej sekcji omówiono sposób działania walidacji danych w programie WPF.

Kojarzenie reguł walidacji z powiązaniem

Model powiązania danych WPF umożliwia skojarzenie ValidationRules z obiektem Binding. Na przykład poniższy przykład wiąże obiekt TextBox z właściwością o nazwie StartPrice i dodaje obiekt ExceptionValidationRule do właściwości Binding.ValidationRules.

<TextBox Name="StartPriceEntryForm" Grid.Row="2"
         Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
    <TextBox.Text>
        <Binding Path="StartPrice" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <ExceptionValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Obiekt ValidationRule sprawdza, czy wartość właściwości jest prawidłowa. WPF ma dwa typy wbudowanych obiektów ValidationRule:

Możesz również utworzyć własną regułę walidacji, korzystając z klasy ValidationRule i implementując metodę Validate. W poniższym przykładzie przedstawiono regułę używaną przez obiekt Dodawanie listy produktów jako „Data rozpoczęcia” TextBox z sekcji Co to jest powiązanie danych.

public class FutureDateRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        // Test if date is valid
        if (DateTime.TryParse(value.ToString(), out DateTime date))
        {
            // Date is not in the future, fail
            if (DateTime.Now > date)
                return new ValidationResult(false, "Please enter a date in the future.");
        }
        else
        {
            // Date is not a valid date, fail
            return new ValidationResult(false, "Value is not a valid date.");
        }

        // Date is valid and in the future, pass
        return ValidationResult.ValidResult;
    }
}
Public Class FutureDateRule
    Inherits ValidationRule

    Public Overrides Function Validate(value As Object, cultureInfo As CultureInfo) As ValidationResult

        Dim inputDate As Date

        ' Test if date is valid
        If Date.TryParse(value.ToString, inputDate) Then

            ' Date is not in the future, fail
            If Date.Now > inputDate Then
                Return New ValidationResult(False, "Please enter a date in the future.")
            End If

        Else
            ' // Date Is Not a valid date, fail
            Return New ValidationResult(False, "Value is not a valid date.")
        End If

        ' Date is valid and in the future, pass
        Return ValidationResult.ValidResult

    End Function

End Class

Element StartDateEntryFormTextBox używa tego elementu FutureDateRule, jak pokazano w poniższym przykładzie.

<TextBox Name="StartDateEntryForm" Grid.Row="3"
         Validation.ErrorTemplate="{StaticResource validationTemplate}" 
         Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
    <TextBox.Text>
        <Binding Path="StartDate" UpdateSourceTrigger="PropertyChanged" 
                 Converter="{StaticResource dateConverter}" >
            <Binding.ValidationRules>
                <src:FutureDateRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Ponieważ wartość UpdateSourceTrigger to PropertyChanged, aparat powiązania aktualizuje wartość źródłową przy każdym naciśnięciu klawiszy, co oznacza, że sprawdza również każdą regułę w kolekcji ValidationRules przy każdym naciśnięciu klawisza. Omówimy to dokładniej w sekcji Proces walidacji.

Przekazywanie opinii wizualnej

Jeśli użytkownik wprowadzi nieprawidłową wartość, możesz przekazać opinię na temat błędu w interfejsie użytkownika aplikacji. Jednym ze sposobów przekazywania takiej opinii jest ustawienie dołączonej właściwości Validation.ErrorTemplate na niestandardową ControlTemplate. Jak pokazano w poprzedniej podsekcji, element StartDateEntryFormTextBox używa elementu ErrorTemplate o nazwie validationTemplate. W poniższym przykładzie przedstawiono definicję elementu validationTemplate.

<ControlTemplate x:Key="validationTemplate">
    <DockPanel>
        <TextBlock Foreground="Red" FontSize="20">!</TextBlock>
        <AdornedElementPlaceholder/>
    </DockPanel>
</ControlTemplate>

Element AdornedElementPlaceholder określa, gdzie należy umieścić zdobioną kontrolkę.

Ponadto można również użyć elementu ToolTip, aby wyświetlić komunikat o błędzie. Zarówno element StartDateEntryForm, jak i StartPriceEntryFormTextBox używają stylu textStyleTextBox, który tworzy ToolTip wyświetlający komunikat o błędzie. W poniższym przykładzie przedstawiono definicję stylu textStyleTextBox. Dołączona właściwość Validation.HasError to wtedy true, gdy co najmniej jedno powiązanie we właściwościach powiązanego elementu jest błędne.

<Style x:Key="textStyleTextBox" TargetType="TextBox">
    <Setter Property="Foreground" Value="#333333" />
    <Setter Property="MaxLength" Value="40" />
    <Setter Property="Width" Value="392" />
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip" 
                    Value="{Binding (Validation.Errors).CurrentItem.ErrorContent, RelativeSource={RelativeSource Self}}" />
        </Trigger>
    </Style.Triggers>
</Style>

W przypadku elementu niestandardowego ErrorTemplate i ToolTip element StartDateEntryFormTextBox wygląda następująco, gdy wystąpi błąd walidacji.

Błąd walidacji powiązania danych dla daty

Jeśli Binding ma skojarzone reguły walidacji, ale nie zostało określone ErrorTemplate w powiązanej kontrolce, zostanie użyta wartość domyślna ErrorTemplate do powiadamiania użytkowników o błędzie walidacji. Wartość domyślna ErrorTemplate to szablon kontrolki, który definiuje czerwone obramowanie w warstwie modułu definiowania układu kodu. W przypadku wartości domyślnej ErrorTemplate i ToolTip interfejs użytkownika elementu StartPriceEntryFormTextBox wygląda następująco, gdy wystąpi błąd walidacji.

Błąd walidacji powiązania danych dla ceny

Aby zapoznać się z przykładem sposobu zapewnienia logiki w celu sprawdzenia poprawności wszystkich kontrolek w oknie dialogowym, zobacz sekcję Niestandardowe okna dialogowe w omówieniu okien dialogowych.

Proces walidacji

Walidacja zwykle występuje, gdy wartość obiektu docelowego jest przenoszona do właściwości źródła powiązania. Ten transfer odbywa się w przypadku powiązań TwoWay i OneWayToSource. Aby powtórzyć to, co powoduje aktualizację źródła, zależy od wartości właściwości UpdateSourceTrigger, zgodnie z opisem w sekcji Co wyzwala aktualizacje źródłowe.

Poniższe elementy opisują proces walidacji. Jeśli błąd weryfikacji lub inny typ błędu wystąpi w dowolnym momencie tego procesu, proces zostanie zatrzymany:

  1. Aparat powiązań sprawdza, czy istnieją jakiekolwiek zdefiniowane obiekty niestandardowe ValidationRule, których wartość ValidationStep jest ustawiona na RawProposedValue dla Binding, w którym to przypadku wywołuje to metodę Validate dla każdego ValidationRule, dopóki jeden z nich nie napotka błędu lub do momentu zaliczenia wszystkich z nich.

  2. Następnie aparat powiązania wywołuje konwerter, jeśli istnieje.

  3. Jeśli konwerter zadziała poprawnie, aparat powiązań sprawdza, czy istnieją jakiekolwiek zdefiniowane obiekty niestandardowe ValidationRule, których wartość ValidationStep jest ustawiona na ConvertedProposedValue dla Binding, w którym to przypadku wywołuje to metodę Validate dla każdego ValidationRule, który ma ValidationStep ustawione na ConvertedProposedValue, dopóki jeden z nich nie napotka błędu lub do momentu zaliczenia wszystkich z nich.

  4. Aparat powiązań ustawia właściwość źródłową.

  5. Aparat powiązań sprawdza, czy istnieją jakiekolwiek zdefiniowane obiekty niestandardowe ValidationRule, których wartość ValidationStep jest ustawiona na UpdatedValue dla Binding, w którym to przypadku wywołuje to metodę Validate dla każdego ValidationRule, który ma ValidationStep ustawione na UpdatedValue, dopóki jeden z nich nie napotka błędu lub do momentu zaliczenia wszystkich z nich. Jeśli element DataErrorValidationRule jest skojarzony z powiązaniem i jego ValidationStep jest ustawione na wartość domyślną, UpdatedValue, element DataErrorValidationRule jest sprawdzany w tym momencie. W tym momencie sprawdzane jest każde powiązanie, które ma ValidatesOnDataErrors ustawione na wartość true.

  6. Aparat powiązań sprawdza, czy istnieją jakiekolwiek zdefiniowane obiekty niestandardowe ValidationRule, których wartość ValidationStep jest ustawiona na CommittedValue dla Binding, w którym to przypadku wywołuje to metodę Validate dla każdego ValidationRule, który ma ValidationStep ustawione na CommittedValue, dopóki jeden z nich nie napotka błędu lub do momentu zaliczenia wszystkich z nich.

Jeśli obiekt ValidationRule nie zaliczy w dowolnym momencie tego procesu, aparat powiązania tworzy obiekt ValidationError i dodaje go do kolekcji Validation.Errors powiązanego elementu. Zanim aparat powiązań uruchomi obiekty ValidationRule w dowolnym kroku usuwa wszystkie ValidationError dodane do dołączonej właściwości Validation.Errors elementu powiązanego w tym kroku. Jeśli na przykład element ValidationRule, którego ValidationStep jest ustawione na UpdatedValue, nie zaliczył, następnym razem, gdy wystąpi proces walidacji, aparat powiązania usuwa ten ValidationError bezpośrednio przed wywołaniem dowolnego ValidationRule, którego ValidationStep jest ustawione na UpdatedValue.

Gdy właściwość Validation.Errors nie jest pusta, dołączona właściwość Validation.HasError elementu jest ustawiona na wartość true. Ponadto jeśli właściwość NotifyOnValidationError obiektu Binding jest ustawiona na true, aparat powiązania zgłasza dołączone zdarzenie Validation.Error w elemencie.

Należy również zauważyć, że prawidłowy transfer wartości w obu kierunkach (element docelowy do źródła lub źródło do elementu docelowego) czyści dołączoną właściwość Validation.Errors.

Jeśli powiązanie ma ExceptionValidationRule skojarzone z nim lub właściwość ValidatesOnExceptions była ustawiona na true i wyjątek jest zgłaszany, gdy aparat powiązania ustawia źródło, aparat powiązania sprawdza, czy istnieje UpdateSourceExceptionFilter. Możesz użyć wywołania zwrotnego UpdateSourceExceptionFilter, aby zapewnić niestandardową procedurę obsługi wyjątków. Jeśli parametr UpdateSourceExceptionFilter nie został określony w elemencie Binding, aparat powiązania tworzy obiekt ValidationError z wyjątkiem i dodaje go do kolekcji Validation.Errors powiązanego elementu.

Mechanizm debugowania

Możesz ustawić dołączoną właściwość PresentationTraceSources.TraceLevel dla obiektu związanego z powiązaniem, aby otrzymywać informacje o stanie określonego powiązania.

Zobacz też