Omówienie powiązania danych (WPF .NET)

Powiązanie danych w 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 i dowolne ItemsControl, takie jak ListBoxButton i ListView, mają wbudowaną funkcjonalność umożliwiającą elastyczne stylizowanie pojedynczych elementów danych lub kolekcji elementów danych. Widoki sortowania, filtrowania i grup można wygenerować na podstawie danych.

Powiązanie danych w WPF ma kilka zalet w porównaniu z tradycyjnymi modelami, 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 z interfejsu użytkownika.

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

Ważne

Dokumentacja przewodnika po pulpicie dla platform .NET 6 i .NET 5 (w tym .NET Core 3.1) jest w trakcie budowy.

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 dane zmieniają jego wartość, elementy powiązane z danymi są automatycznie odzwierciedlane. Powiązanie danych może również oznaczać, że jeśli zewnętrzna reprezentacja danych w elemecie 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 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 (w tym obiektami lub obiektami ADO.NET skojarzonymi z usługami sieci Web i właściwościami sieci Web) oraz danymi XML.

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

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

Diagram that shows the basic data binding model.

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ść docelowa.
    • Źródło powiązania.
    • Ścieżka do wartości w źródle powiązania do użycia.

    Jeśli na przykład powiązana jest zawartość elementu z TextBoxEmployee.Name właściwością, 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 to właściwości zależności, a większość UIElement 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 UIElement typy pochodzą z DependencyObjectklasy .

  • Ź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ć obiektem UIElementlisty , obiektem ADO.NET lub usługami sieci Web 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 powiązaniu przy użyciu danych, powiązanie danych jest powiązane ListBox z danymi XML.

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

Kontekst danych

Gdy powiązanie danych jest deklarowane w elementach XAML, rozwiązuje powiązanie danych, sprawdzając ich natychmiastową DataContext właściwość. 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 . DataContext Jeśli właściwość obiektu hostująca powiązanie nie jest ustawiona, właściwość elementu DataContext nadrzędnego 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 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 DataContext zmianie właściwości wszystkie powiązania, które mogą mieć wpływ na kontekst danych, są ponownie oceniane.

Kierunek przepływu danych

Jak wskazano na 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 ze zmianami w źródle powiązania), jeśli źródło powiązania dostarcza odpowiednie 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:

Data binding data flow

  • OneWay powiązanie 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 powią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 OneWay trybu powiązania pozwala uniknąć nakładu pracy w TwoWay trybie powiązania.

  • TwoWay powiązanie powoduje zmianę właściwości źródłowej lub właściwości docelowej, aby automatycznie zaktualizować 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 OneWay wiązana, 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 domyślne powiązanie TwoWay . Programowy sposób określania, czy właściwość zależności wiąże się domyślnie z jednokierunkowym lub dwukierunkowym sposobem, jest pobranie metadanych właściwości za DependencyProperty.GetMetadata pomocą polecenia , a następnie sprawdzenie wartości logicznej FrameworkPropertyMetadata.BindsTwoWayByDefault właściwości.

  • 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 sprawdzić 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 lub dane są naprawdę statyczne. Ten typ powiązania jest również przydatny, jeśli chcesz zainicjować właściwość docelową z 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ą OneWay powiązania, która zapewnia lepszą wydajność w przypadkach, gdy wartość źródłowa nie zmienia się.

Aby wykryć zmiany źródła (dotyczy OneWay powiązań i TwoWay ), źródło musi zaimplementować 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 implementacjiINotifyPropertyChanged.

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łowe

Powiązania, które są TwoWay lub OneWayToSource nasłuchiwanie zmian we właściwości docelowej i propagują je z powrotem do źródła, znanego 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 lub 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ę Binding.UpdateSourceTrigger właściwości.

Diagram that shows the role of the UpdateSourceTrigger property.

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

Podobnie jak w przypadku Mode właściwości, 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 innych prostych kontrolek. Jednak w przypadku pól tekstowych aktualizacja po każdym naciśnięciu klawiszy może zmniejszyć wydajność i odrzucać użytkownikowi zwykłą okazję do backspace i naprawić błędy wpisywania przed zatwierdzeniem nowej wartości. Na przykład TextBox.Text właściwość jest domyślnie ustawiona na UpdateSourceTrigger wartość , LostFocusco powoduje zmianę wartości źródłowej tylko wtedy, gdy element kontrolki traci fokus, a nie po TextBox.Text zmianie właściwości. Zobacz stronę właściwości, UpdateSourceTrigger aby uzyskać informacje na temat znajdowania wartości domyślnej właściwości zależności.

Poniższa tabela zawiera przykładowy scenariusz dla każdej UpdateSourceTrigger wartości przy użyciu przykładu TextBox .

Wartość UpdateSourceTrigger Po zaktualizowaniu wartości źródłowej Przykładowy scenariusz dla elementu TextBox
LostFocus (wartość domyślna dla TextBox.Textelementu ) Gdy kontrolka TextBox traci fokus. Pole tekstowe skojarzone z logiką walidacji (zobacz Walidacja danych poniżej).
PropertyChanged Podczas wpisywania w obiekcie 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 wtedy, gdy użytkownik naciska przycisk przesyłania).

Aby zapoznać się z przykładem, zobacz How to: Control when the TextBox text updates the source (.NET Framework)( Jak kontrolować, kiedy tekst TextBox aktualizuje źródło (.NET Framework).

Przykład powiązania danych

Na przykład powiązania danych zapoznaj się z następującym interfejsem użytkownika aplikacji z pokazu powiązania danych, który wyświetla listę elementów aukcji.

Data binding sample screenshot

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

  • Zawartość pola 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 (obiekty AuctionItem ) wyświetlane w elemencie ListBox są szablonowane tak, 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 wyświetlanej wartości SpecialFeatureselementu AuctionItem . Jeśli wartość SpecialFeatures elementu AuctionItem to Color, element ma niebieskie obramowanie. Jeśli wartość to Wyróżnienie, element ma pomarańczową krawędź i gwiazdkę. Sekcja Data Templating zawiera informacje o tworzeniu szablonów danych.

  • Użytkownik może grupować, filtrować lub sortować dane przy użyciu podanej CheckBoxes wartości. 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ć z obrazu, ale elementy są również sortowane według daty rozpoczęcia w każdej kategorii. Sortowanie odbywa się przy użyciu widoku kolekcji. W sekcji Wiązanie z kolekcjami omówiono widoki kolekcji.

  • Po wybraniu elementu ContentControl przez użytkownika zostaną wyświetlone szczegóły wybranego elementu. To środowisko jest nazywane scenariuszem szczegółów wzorca. Sekcja scenariusza szczegółów wzorca zawiera informacje o tym typie powiązania.

  • Typ właściwości StartDate to DateTime, która zwraca datę zawierającą godzinę do milisekund. W tej aplikacji użyto niestandardowego konwertera tak, 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.

Add Product Listing page

Użytkownik może edytować pola w formularzu, wyświetlić podgląd listy 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 .

Nie pokazano na tym obrazie logiki walidacji podanej w dacieTextBox rozpoczęcia. Jeśli użytkownik wprowadzi nieprawidłową datę (nieprawidłowe formatowanie lub poprzednią datę), użytkownik zostanie powiadomiony o czerwonym ToolTip wykrzykniku TextBoxobok . W sekcji Weryfikacja 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 Binding obiektu, a każde powiązanie zwykle ma cztery składniki: element 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 elementem. Elementy są automatycznie dziedziczone 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 myData ma właściwość ciągu o nazwie ColorName , której wartość jest ustawiona na "Czerwony". W związku z tym 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 konfigurowania powiązania w kodzie, zobacz Omówienie deklaracji powiązań.

Jeśli zastosujemy ten przykład do naszego podstawowego diagramu, wynikowa ilustracja będzie wyglądać następująco. Na rysunku opisano powiązanie, OneWay ponieważ właściwość Background obsługuje OneWay powiązanie domyślnie.

Diagram that shows the data binding Background property.

Możesz się zastanawiać, dlaczego to powiązanie działa, mimo że właściwość ColorName jest ciągiem typu , podczas gdy Background właściwość jest typu Brush. To powiązanie używa domyślnej konwersji typów, która jest omawiana 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ślone przez ustawienie właściwości DockPanel.DataContext . Następnie Button dziedziczy DataContext wartość z DockPanelelementu , 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. DataContext Użycie właściwości elementu nadrzędnego jest przydatne w przypadku powiązania wielu właściwości z tym samym źródłem. Czasami jednak może być bardziej odpowiednie określenie źródła powiązania dla poszczególnych deklaracji powiązań. W poprzednim przykładzie zamiast używać DataContext właściwości można określić źródło powiązania, ustawiając Binding.Source właściwość bezpośrednio na 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 DataContext właściwości elementu bezpośrednio, dziedziczenie DataContext wartości z modułu ancestor (takiego jak przycisk w pierwszym przykładzie) i jawne określenie źródła powiązania przez ustawienie Binding.Source właściwości powiązania (na przykład przycisku ostatniego przykładu), można również użyć Binding.ElementName właściwości lub Binding.RelativeSource właściwości, aby określić źródło powiązania. Właściwość ElementName jest przydatna w przypadku powiązania z innymi elementami w aplikacji, takimi jak użycie suwaka w celu dostosowania szerokości przycisku. Właściwość jest przydatna RelativeSource , gdy powiązanie jest określone w elemecie ControlTemplateStylelub . 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ć Binding.Path właściwości , aby określić wartość do użycia dla powiązania. Jeśli powiązanie z danymi XML jest wiązane, użyj Binding.XPath właściwości , aby określić wartość. W niektórych przypadkach może to mieć zastosowanie do używania Path właściwości nawet wtedy, gdy dane są xml. Jeśli na przykład chcesz uzyskać dostęp do właściwości Name zwróconego węzła XmlNode (w wyniku zapytania XPath), należy użyć Path właściwości oprócz XPath właściwości .

Aby uzyskać więcej informacji, zobacz Path właściwości 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 nieokreślinia 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 DataContext dziedziczy z nadrzędnego elementu DockPanel (nie jest pokazany 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ż jest wiązana ItemsSource właściwość z całym obiektem. (Zobacz sekcję Wiązanie z kolekcjami , aby uzyskać szczegółową dyskusję).

Poza powiązaniem z kolekcją ten scenariusz jest również przydatny, gdy chcesz powiązać z całym obiektem zamiast tylko jedną właściwość obiektu. Jeśli na przykład obiekt źródłowy ma typ 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 być w postaci konwertera niestandardowego, jeśli konwersja typu domyślnego nie istnieje. Zobacz Konwersja danych , aby uzyskać informacje o konwerterach.

Powiązanie i powiązanieExpression

Przed wprowadzeniem do innych funkcji i użycia powiązania danych warto wprowadzić klasę BindingExpression . Jak pokazano w poprzednich sekcjach, Binding klasa jest klasą wysokiego poziomu dla deklaracji powiązania. Zawiera 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 można udostępniać w kilku wyrażeniach powiązań. A BindingExpression to wyrażenie wystąpienia, którego nie można udostępnić i zawiera wszystkie informacje o wystąpieniu elementu Binding.

Rozważmy poniższy przykład, gdzie myDataObject jest wystąpieniem MyData klasy, 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, jest powiązane z 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)

Możesz użyć tego samego obiektu myBinding , aby utworzyć inne powiązania. Możesz na przykład użyć obiektu myBinding , aby powiązać zawartość tekstową pola wyboru z polem wyboru ColorName. W tym scenariuszu będą dostępne dwa wystąpienia BindingExpression współużytkowania obiektu myBinding .

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

Konwersja danych

W sekcji Tworzenie powiązania przycisk jest czerwony, ponieważ jego Background właściwość 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 Brushwartość .

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

Diagram that shows the data binding Default property.

Co jednak zrobić, jeśli zamiast właściwości ciągu typu 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ś, Background co właściwość akceptuje. Należy utworzyć konwerter niestandardowy, implementując IValueConverter interfejs, 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 konwerter niestandardowy jest używany zamiast konwersji domyślnej, a nasz diagram wygląda następująco.

Diagram that shows the data binding custom converter.

Aby powtórzyć, konwersje domyślne mogą być dostępne z powodu konwerterów typów, które są obecne w typie powiązanym. To zachowanie będzie zależeć od tego, które konwertery typów są dostępne w obiekcie docelowym. Jeśli masz 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żesz zaimplementować konwerter waluty lub konwerter daty/godziny kalendarza na podstawie konwencji używanych w określonej kulturze.

  • Używane dane nie muszą być przeznaczone do zmiany wartości tekstowej właściwości, ale zamiast tego mają zmienić inną wartość, taką jak źródło obrazu, kolor lub styl tekstu wyświetlanego. Konwertery mogą być używane w tym wystąpieniu, konwertując powiązanie właściwości, która może nie wydawać się odpowiednia, na przykład powiązanie pola tekstowego z właściwością Background komórki tabeli.

  • Więcej niż jedna kontrolka lub wiele właściwości kontrolek jest powiązanych z tymi samymi danymi. 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 ma kolekcję powiązań, która jest określana MultiBindingjako . W przypadku MultiBindingprogramu należy użyć niestandardowego IMultiValueConverter elementu , 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 przykłady i informacje.

Wią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 zbieranie 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, ListViewlub TreeView wyświetlenie 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 z ItemsControl kolekcją, diagram wygląda następująco.

Diagram that shows the data binding ItemsControl object.

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

Jak zaimplementować kolekcje

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

WPF udostępnia klasę ObservableCollection<T> , która jest wbudowaną implementacją kolekcji danych, która uwidacznia INotifyCollectionChanged interfejs. 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ż zaimplementować INotifyPropertyChanged interfejs. Aby uzyskać więcej informacji, zobacz Omówienie źródeł powiązań.

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

Widoki kolekcji

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

Co to są widoki kolekcji?

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

Ponieważ widoki nie zmieniają bazowych kolekcji źródłowych, każda kolekcja źródłowa może mieć wiele widoków skojarzonych z nimi. Na przykład może istnieć kolekcja obiektów Zadań . Korzystając z 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 aplikację demonstracyjną Powiązania danych pokazaną w sekcji Co to jest powiązanie danych . Aplikacja jest implementowana tak, że ListBox powiązania z widokiem na zbieranie danych zamiast zbierania danych bezpośrednio. Poniższy przykład jest wyodrębniany z aplikacji demonstracyjnej powiązania danych . Klasa CollectionViewSource jest serwerem proxy XAML klasy dziedziczonej z CollectionViewklasy . W tym przykładzie Source widok 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>

Następnie zasób listDataView 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 CollectionViewSource wystąpienie i nadać mu inną x:Key nazwę.

W poniższej tabeli przedstawiono typy danych widoku tworzone jako domyślny widok kolekcji lub 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 grupowania 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 jego widokiem domyślnym. Ten widok domyślny jest współużytkowany przez wszystkie powiązania z tą samą kolekcją, więc zmiana w widoku domyślnym przez jedną powiązaną kontrolkę lub kod (na przykład sortowanie lub zmiana wskaźnika bieżącego elementu, omówionego później) jest odzwierciedlana we wszystkich innych powiązaniach z tą samą kolekcją.

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

Widoki kolekcji z tabelami danych ADO.NET

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

Sortowanie

Jak wspomniano wcześniej, widoki mogą stosować kolejność sortowania do kolekcji. Ponieważ istnieje w podstawowej kolekcji, dane mogą lub nie mają odpowiedniej, właściwej kolejności. Widok nad kolekcją umożliwia narzucenie zamówienia lub zmianę kolejności domyślnej na podstawie podanych kryteriów porównania. Ponieważ jest to widok danych oparty na kliencie, typowym scenariuszem jest to, że użytkownik może chcieć sortować kolumny danych tabelarycznych na wartość odpowiadającą kolumnie. Korzystając z widoków, można ponownie zastosować sortowanie oparte na użytkownikach bez wprowadzania żadnych zmian w kolekcji bazowej, a nawet konieczności ponownego pisania w poszukiwaniu zawartości kolekcji. Aby zapoznać się z przykładem, zobacz Sortowanie kolumny GridView po kliknięciu nagłówka (.NET Framework).

W poniższym przykładzie przedstawiono 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, aby widok wyświetlał tylko określony podzestaw pełnej kolekcji. Możesz filtrować warunek 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ń po wybraniu Filter tej CheckBox opcji.

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 CollectionView klas bezpośrednio zamiast CollectionViewSource, użyj Filter właściwości , 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 IEnumerable kolekcję, wszystkie widoki kolekcji obsługują grupowanie, co umożliwia użytkownikowi partycjonowanie kolekcji w widoku kolekcji na grupy logiczne. Grupy mogą być jawne, gdzie użytkownik dostarcza listę grup lub niejawnych, gdzie 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 Group Items in a ListView That Implements a GridView (.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 obiekcie CollectionView (.NET Framework).

Ponieważ WPF wiąże się z kolekcją tylko przy użyciu widoku (określonego widoku lub widoku domyślnego kolekcji), wszystkie powiązania z kolekcjami mają bieżący wskaźnik elementu. W przypadku powiązania z widokiem ukośnik ("/") w Path wartości 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ż stosowana w celu przechodzenia przez hierarchię kolekcji. Poniższy przykład wiąże się z bieżącym elementem kolekcji o nazwie Offices, która jest właściwością bieżącego elementu kolekcji źródłowej.

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

Bieżący wskaźnik elementu może mieć wpływ na dowolne sortowanie lub filtrowanie stosowane do kolekcji. Sortowanie zachowuje bieżący wskaźnik elementu w wybranym ostatnim elemencie, ale widok kolekcji jest teraz restrukturyzacji 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 filtrowanego widoku kolekcji.

Scenariusz powiązania szczegółów wzorca

Pojęcie bieżącego elementu jest przydatne nie tylko w przypadku nawigacji elementów w kolekcji, ale także dla scenariusza powiązania szczegółów wzorca. Ponownie rozważ interfejs użytkownika aplikacji w sekcji Co to jest powiązanie danych . W tej aplikacji wybór w elemecie ListBox określa zawartość wyświetlaną w elemecie ContentControl. Aby umieścić go w inny sposób, po ListBox wybraniu ContentControl elementu wyświetlane są szczegóły wybranego elementu.

Scenariusz szczegółów wzorca można zaimplementować po prostu, używając co najmniej dwóch kontrolek powiązanych z tym samym widokiem. Poniższy przykład z pokazu powiązania danych przedstawia znaczniki ListBox i 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 oba kontrolki są powiązane z tym samym źródłem, a element listDataView — zasób statyczny (zobacz definicję tego zasobu w sekcji Jak utworzyć widok). To powiązanie działa, ponieważ gdy pojedynczy obiekt ( ContentControl w tym przypadku) jest powiązany z widokiem kolekcji, automatycznie wiąże CurrentItem się z widokiem widoku. Obiekty CollectionViewSource są automatycznie synchronizowane z walutą i zaznaczeniem. Jeśli kontrolka listy nie jest powiązana z obiektem CollectionViewSource , tak jak w tym przykładzie, należy ustawić jej IsSynchronizedWithCurrentItem właściwość na true wartość , aby ta wartość działała.

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 szczegółów wzorca z danymi hierarchicznymi (.NET Framework).

Być moż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 jawnie używany przez element ContentControl i używany niejawnie przez ListBoxobiekt ). 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 następująco:

Data Binding Demo without Data Templates

Jak pokazano w przykładzie w poprzedniej sekcji, ListBox zarówno kontrolka, jak i ContentControl obiekt są powiązane z całym obiektem kolekcji (lub dokładniej widokiem na obiekt kolekcji) elementu AuctionItems. Bez konkretnych instrukcji wyświetlania zbierania ListBox danych wyświetla reprezentację ciągu każdego obiektu w kolekcji bazowej i ContentControl wyświetla reprezentację ciągu obiektu, do których 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 jest wyświetlany w sekcji Co to jest powiązanie danych . Jak widać na tym zrzucie ekranu, oprócz umożliwienia umieszczania danych w kontrolkach, platforma DataTemplates umożliwia definiowanie atrakcyjnych wizualizacji dla danych. Na przykład wartości s są używane w powyższych DataTemplate elementach, DataTriggeraby element AuctionItems z wartością SpecialFeatures funkcji HighLight był wyświetlany 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ę weryfikacji, 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 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 z TextBox właściwością o nazwie StartPrice i dodaje ExceptionValidationRule obiekt do Binding.ValidationRules właściwości.

<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>

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

Możesz również utworzyć własną regułę weryfikacji, korzystając z ValidationRule klasy i implementując metodę Validate . W poniższym przykładzie przedstawiono regułę używaną przez sekcję Dodawanie listy produktów "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>

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

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 Validation.ErrorTemplate właściwości na niestandardową ControlTemplate. Jak pokazano w poprzedniej podsekcji, element StartDateEntryFormTextBox używa ErrorTemplate elementu o nazwie validationTemplate. W poniższym przykładzie przedstawiono definicję validationTemplate.

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

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

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

<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 ErrorTemplateToolTipi elementu , element StartDateEntryFormTextBox wygląda następująco, gdy wystąpi błąd walidacji.

Data binding validation error for date

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

Data binding validation error for price

Aby zapoznać się z przykładem sposobu zapewnienia logiki sprawdzania poprawności wszystkich kontrolek w oknie dialogowym, zobacz sekcję Niestandardowe okna dialogowe w przeglądzie okien dialogowych.

Proces walidacji

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

Poniższe elementy opisują proces weryfikacji . 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ą zdefiniowane obiekty niestandardowe ValidationRule , których ValidationStep wartość jest ustawiona RawProposedValue na wartość Binding, w takim przypadku wywołuje Validate metodę dla każdego ValidationRule z nich, dopóki jeden z nich nie wystąpi błąd lub do momentu przekazania wszystkich z nich.

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

  3. Jeśli konwerter powiedzie się, aparat powiązania sprawdza, czy istnieją jakiekolwiek obiekty niestandardowe ValidationRule zdefiniowane ValidationStepConvertedProposedValue dla tego Bindingelementu , w takim przypadku wywołuje Validate metodę dla każdego ConvertedProposedValueValidationRuleValidationStep ustawionego na wartość , dopóki jeden z nich nie wystąpi błąd lub dopóki wszystkie z nich nie zostaną przekazane.

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

  5. Aparat powiązań sprawdza, czy istnieją zdefiniowane obiekty niestandardoweValidationRule, których ValidationStep wartość jest ustawiona UpdatedValue na wartość Binding, w takim przypadku wywołuje metodę dla każdego ValidationRuleValidationStep ustawionego Validate na wartość , UpdatedValue dopóki jeden z nich nie przejdzie do błędu lub do momentu przekazania wszystkich z nich. Jeśli element DataErrorValidationRule jest skojarzony z powiązaniem i jest ValidationStep ustawiony na wartość domyślną, UpdatedValueDataErrorValidationRule element jest sprawdzany w tym momencie. W tym momencie wszystkie powiązania z ustawionym ustawieniem ValidatesOnDataErrorstrue są sprawdzane.

  6. Aparat powiązań sprawdza, czy istnieją zdefiniowane obiekty niestandardoweValidationRule, których ValidationStep wartość jest ustawiona CommittedValue na wartość Binding, w takim przypadku wywołuje metodę dla każdego ValidationRuleValidationStep ustawionego Validate na wartość , CommittedValue dopóki jeden z nich nie przejdzie do błędu lub do momentu przekazania wszystkich z nich.

ValidationRule Jeśli obiekt nie przechodzi w dowolnym momencie w tym procesie, aparat powiązania tworzy ValidationError obiekt i dodaje go do Validation.Errors kolekcji powiązanego elementu. Zanim aparat powiązania uruchamia ValidationRule obiekty w dowolnym kroku, usuwa wszystkie ValidationError dodane do Validation.Errors dołączonej właściwości powiązanego elementu w tym kroku. Jeśli na przykład element ValidationRule , którego ValidationStep ustawiono na UpdatedValue niepowodzenie, następnym razem, gdy wystąpi proces weryfikacji, aparat powiązania usuwa ValidationError ten element bezpośrednio przed wywołaniam dowolnego ValidationRuleValidationStep ustawionego na UpdatedValue.

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

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

Jeśli powiązanie jest ExceptionValidationRule skojarzone z nim lub właściwość ValidatesOnExceptions jest ustawiona na true , a 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 nie został określony w elemecie UpdateSourceExceptionFilterBinding, aparat powiązania tworzy ValidationError obiekt z wyjątkiem i dodaje go do Validation.Errors kolekcji powiązanego elementu.

Mechanizm debugowania

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

Zobacz też