Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Ważne interfejsy API
Uwaga / Notatka
W tym temacie opisano szczegółowo funkcje powiązania danych. Krótkie, praktyczne wprowadzenie można znaleźć w temacie Omówienie powiązania danych.
Ten temat dotyczy powiązania danych dla interfejsów API znajdujących się w przestrzeni nazw Windows.UI.Xaml.Data.
Powiązanie danych to sposób, aby interfejs użytkownika aplikacji wyświetlał dane i opcjonalnie pozostaje zsynchronizowany z danymi. Powiązanie danych pozwala oddzielić obawy dotyczące danych od kwestii interfejsu użytkownika, co powoduje prostszy model koncepcyjny, a także lepszą czytelność, możliwość testowania i konserwację aplikacji.
Możesz użyć powiązania danych, aby po prostu wyświetlić wartości ze źródła danych po pierwszym wyświetleniu interfejsu użytkownika, ale nie reagować na zmiany w tych wartościach. Jest to tryb powiązania o nazwie jednorazowo i działa dobrze dla wartości, która nie zmienia się w czasie wykonywania. Alternatywnie możesz wybrać opcję "obserwowania" wartości i aktualizowania interfejsu użytkownika podczas ich zmiany. Ten tryb jest nazywany jednokierunkowym i dobrze sprawdza się w przypadku danych tylko do odczytu. Ostatecznie możesz wybrać zarówno obserwowanie, jak i aktualizowanie, aby zmiany wprowadzone przez użytkownika do wartości w interfejsie użytkownika zostały automatycznie wypchnięte z powrotem do źródła danych. Ten tryb jest nazywany dwukierunkowym i dobrze sprawdza się w przypadku danych odczytu i zapisu. Oto kilka przykładów.
- Możesz użyć trybu jednorazowego, aby powiązać obraz ze zdjęciem bieżącego użytkownika.
- Możesz użyć trybu jednokierunkowego, aby powiązać element ListView z kolekcją artykułów z wiadomościami w czasie rzeczywistym pogrupowanych według sekcji gazety.
- Możesz użyć trybu dwukierunkowego, aby powiązać pole tekstowe z nazwą klienta w formularzu.
Niezależnie od trybu istnieją dwa rodzaje powiązań i są one zwykle deklarowane w adiustacji interfejsu użytkownika. Możesz użyć rozszerzenia znaczników {x:Bind} lub rozszerzenia znaczników {Binding}. Możesz nawet użyć kombinacji tych dwóch elementów w tej samej aplikacji — nawet w tym samym elemecie interfejsu użytkownika. {x:Bind} jest nowy dla systemu Windows 10 i ma lepszą wydajność. Wszystkie szczegóły opisane w tym temacie dotyczą obu rodzajów powiązania, chyba że jawnie stwierdzimy inaczej.
Przykładowe aplikacje, które demonstrują element {x:Bind}
Przykładowe aplikacje, które demonstrują {Binding}
- Pobierz aplikację Bookstore1 .
- Pobierz aplikację Bookstore2 .
Każde powiązanie obejmuje te elementy
- Źródło powiązania. Jest to źródło danych dla powiązania. Może to być wystąpienie dowolnej klasy zawierającej elementy członkowskie, których wartości mają być wyświetlane w interfejsie użytkownika.
- Element docelowy powiązania. Jest to właściwość DependencyPropertyelementu FrameworkElement w interfejsie użytkownika, który wyświetla dane.
- Obiekt powiązania. Jest to fragment, który przesyła wartości danych ze źródła do obiektu docelowego i opcjonalnie z miejsca docelowego z powrotem do źródła. Obiekt powiązania jest tworzony w czasie ładowania XAML z rozszerzenia znaczników {x:Bind} lub {Binding} .
W poniższych sekcjach przyjrzymy się bliżej źródle powiązania, celowi powiązania i obiektowi powiązania. Połączymy sekcje wraz z przykładem powiązania zawartości przycisku z właściwością ciągu o nazwie NextButtonText, która należy do klasy o nazwie HostViewModel.
Źródło powiązania
Oto bardzo podstawowa implementacja klasy, która mogłaby zostać użyta jako źródło powiązania.
Jeśli używasz języka C++/WinRT, dodaj nowe elementy pliku Midl (idl) do projektu o nazwie , jak pokazano na poniższej liście przykładów kodu C++/WinRT. Zastąp zawartość tych nowych plików kodem MIDL 3.0 pokazanym na liście, skompiluj projekt w celu wygenerowania HostViewModel.h i .cpp, a następnie dodaj kod do wygenerowanych plików, aby dopasować go do listy. Aby uzyskać więcej informacji o wygenerowanych plikach i sposobie ich kopiowania do projektu, zobacz kontrolki XAML; wiązanie z właściwością C++/WinRT.
public class HostViewModel
{
public HostViewModel()
{
this.NextButtonText = "Next";
}
public string NextButtonText { get; set; }
}
// HostViewModel.idl
namespace DataBindingInDepth
{
runtimeclass HostViewModel
{
HostViewModel();
String NextButtonText;
}
}
// HostViewModel.h
// Implement the constructor like this, and add this field:
...
HostViewModel() : m_nextButtonText{ L"Next" } {}
...
private:
std::wstring m_nextButtonText;
...
// HostViewModel.cpp
// Implement like this:
...
hstring HostViewModel::NextButtonText()
{
return hstring{ m_nextButtonText };
}
void HostViewModel::NextButtonText(hstring const& value)
{
m_nextButtonText = value;
}
...
Ta implementacja elementu HostViewModel i jego właściwości NextButtonText są odpowiednie tylko dla powiązania jednorazowego. Jednak powiązania jednokierunkowe i dwukierunkowe są bardzo powszechne, a w tego rodzaju powiązania interfejsu użytkownika automatycznie aktualizuje się w odpowiedzi na zmiany wartości danych źródła powiązania. Aby tego rodzaju powiązanie działało poprawnie, musisz ustawić źródło powiązania jako "widoczne" dla obiektu powiązania. W naszym przykładzie, jeśli chcemy powiązać jednokierunkowo lub dwukierunkowo z właściwością NextButtonText , wszelkie zmiany, które mają miejsce w czasie wykonywania, muszą być widoczne dla obiektu powiązania.
Jednym ze sposobów wykonania tej czynności jest utworzenie klasy reprezentującej źródło powiązania z klasy DependencyObject i uwidocznienie wartości danych za pomocą właściwości DependencyProperty. W ten sposób element FrameworkElement staje się zauważalny. FrameworkElements są dobrymi źródłami powiązań bezpośrednio z pudełka.
Bardziej uproszczony sposób obserwowania klasy — i niezbędny dla klas, które mają już klasę bazową — to zaimplementowanie elementu System.ComponentModel.INotifyPropertyChanged. To naprawdę polega tylko na zaimplementowaniu pojedynczego zdarzenia o nazwie PropertyChanged. Poniżej przedstawiono przykład użycia modelu HostViewModel .
...
using System.ComponentModel;
using System.Runtime.CompilerServices;
...
public class HostViewModel : INotifyPropertyChanged
{
private string nextButtonText;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public HostViewModel()
{
this.NextButtonText = "Next";
}
public string NextButtonText
{
get { return this.nextButtonText; }
set
{
this.nextButtonText = value;
this.OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
// HostViewModel.idl
namespace DataBindingInDepth
{
runtimeclass HostViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
HostViewModel();
String NextButtonText;
}
}
// HostViewModel.h
// Add this field:
...
winrt::event_token PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler);
void PropertyChanged(winrt::event_token const& token) noexcept;
private:
winrt::event<Windows::UI::Xaml::Data::PropertyChangedEventHandler> m_propertyChanged;
...
// HostViewModel.cpp
// Implement like this:
...
void HostViewModel::NextButtonText(hstring const& value)
{
if (m_nextButtonText != value)
{
m_nextButtonText = value;
m_propertyChanged(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"NextButtonText" });
}
}
winrt::event_token HostViewModel::PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
{
return m_propertyChanged.add(handler);
}
void HostViewModel::PropertyChanged(winrt::event_token const& token) noexcept
{
m_propertyChanged.remove(token);
}
...
Teraz można zaobserwować właściwość NextButtonText . Podczas tworzenia jednokierunkowego lub dwukierunkowego powiązania z tej właściwości (pokażemy, jak później), wynikowy obiekt powiązania subskrybuje zdarzenie PropertyChanged . Po wystąpieniu tego zdarzenia program obsługi obiektu powiązania odbiera argument zawierający nazwę zmienionej właściwości. W ten sposób obiekt powiązania wie, która wartość właściwości ma być ponownie odczytywana.
Aby nie trzeba było implementować wzorca pokazanego powyżej wiele razy, jeśli używasz języka C#, możesz po prostu pochodzić z klasy bazowej BindableBase , którą znajdziesz w przykładzie QuizGame (w folderze "Common"). Oto przykład tego, jak to wygląda.
public class HostViewModel : BindableBase
{
private string nextButtonText;
public HostViewModel()
{
this.NextButtonText = "Next";
}
public string NextButtonText
{
get { return this.nextButtonText; }
set { this.SetProperty(ref this.nextButtonText, value); }
}
}
// Your BindableBase base class should itself derive from Windows::UI::Xaml::DependencyObject. Then, in HostViewModel.idl, derive from BindableBase instead of implementing INotifyPropertyChanged.
Uwaga / Notatka
W przypadku języka C++/WinRT każda klasa środowiska uruchomieniowego zadeklarowana w aplikacji, która pochodzi z klasy bazowej, jest znana jako klasa komposowalna . Istnieją ograniczenia dotyczące klas komponowalnych. Aby aplikacja mogła przejść testy zestawu certyfikacji aplikacji systemu Windows używane przez program Visual Studio i sklep Microsoft Store w celu zweryfikowania przesłanych danych (a w związku z tym aby aplikacja mogła zostać pomyślnie pozyskana do Sklepu Microsoft), klasa komponowana musi ostatecznie pochodzić z klasy bazowej systemu Windows. Oznacza to, że klasa w samym katalogu głównym hierarchii dziedziczenia musi być typem pochodzącym z przestrzeni nazw systemu Windows.* . Jeśli musisz utworzyć klasę środowiska uruchomieniowego z klasy bazowej — na przykład w celu zaimplementowania klasy BindableBase dla wszystkich modeli widoków, z których mają pochodzić — możesz pochodzić z klasy Windows.UI.Xaml.DependencyObject.
Podniesienie zdarzenia PropertyChanged z argumentem String.Empty lub null wskazuje, że wszystkie właściwości nieindeksatora w obiekcie powinny być ponownie odczytane. Zdarzenie można zgłosić, aby wskazać, że właściwości indeksatora obiektu zostały zmienione przy użyciu argumentu "Item[indexer]" dla określonych indeksatorów (gdzie indeksator jest wartością indeksatora) lub wartością "Item[]" dla wszystkich indeksatorów.
Źródło powiązania może być traktowane jako pojedynczy obiekt, którego właściwości zawierają dane, lub jako zbiór obiektów. W kodzie C# i Visual Basic można jednorazowo powiązać z obiektem, który implementuje funkcję List(Of T), aby wyświetlić kolekcję, która nie zmienia się w czasie wykonywania. W przypadku obserwowalnej kolekcji (obserwowanie, kiedy elementy są dodawane do kolekcji i usuwane z tej kolekcji), zamiast tego należy powiązać jednokierunkowe powiązanie z obiektem ObservableCollection(Of T). W kodzie C++/CX można powiązać z wektorem<T> zarówno dla obserwowalnych, jak i nieobserwowalnych kolekcji, a C++/WinRT ma własne typy. Aby powiązać z własnymi klasami kolekcji, skorzystaj ze wskazówek w poniższej tabeli.
| Scenario | C# i VB (CLR) | C++/WinRT | C++/CX |
|---|---|---|---|
| Wiązanie z obiektem. | Może być dowolnym obiektem. | Może być dowolnym obiektem. | Obiekt musi mieć właściwość BindableAttribute lub zaimplementować element ICustomPropertyProvider. |
| Pobieranie powiadomień o zmianie właściwości z powiązanego obiektu. | Obiekt musi implementować element INotifyPropertyChanged. | Obiekt musi implementować element INotifyPropertyChanged. | Obiekt musi implementować element INotifyPropertyChanged. |
| Wiązanie z kolekcją. | Lista (z T) | IVectorIInspectable lub IBindableObservableVector. Zobacz kontrolki elementów XAML; wiązanie z kolekcją I kolekcjami C++/WinRTza pomocą języka C++/WinRT. | Wektor<T> |
| Pobieranie powiadomień o zmianie kolekcji z powiązanej kolekcji. | ObservableCollection(Of T) | IObservableVectorIInspectable. Na przykład winrt::single_threaded_observable_vector<T>. | IObservableVector<T>. Wektor<T> implementuje ten interfejs. |
| Zaimplementuj kolekcję, która obsługuje powiązanie. | Rozszerz listę (T) lub zaimplementuj element IList, IList(Of Object), IEnumerable lub IEnumerable(Of Object). Powiązanie z ogólnymi listami IList(Of T) i IEnumerable(Of T) nie jest obsługiwane. | Zaimplementuj IVectorIInspectable. Zobacz kontrolki elementów XAML; wiązanie z kolekcją I kolekcjami C++/WinRTza pomocą języka C++/WinRT. | Implementowanie IBindableVector, IBindableIterable, IVector<Object^, IIterable<Object^>>, IVector<IInspectable*lub IIterable<IInspectable*>>. Powiązanie z ogólnym IVector<T> i IIterable<T> nie jest obsługiwane. |
| Zaimplementuj kolekcję, która obsługuje powiadomienia o zmianie kolekcji. | Rozszerz obiekt ObservableCollection(Of T) lub zaimplementuj (niegeneryczne) IList i INotifyCollectionChanged. | Zaimplementuj IObservableVector elementuIInspectable lub IBindableObservableVector. | Zaimplementuj IBindableVector i IBindableObservableVector. |
| Zaimplementuj kolekcję, która obsługuje ładowanie przyrostowe. | Rozszerz obiekt ObservableCollection(Of T) lub zaimplementuj (niegeneryczne) IList i INotifyCollectionChanged. Ponadto zaimplementuj ISupportIncrementalLoading. | Zaimplementuj IObservableVector elementuIInspectable lub IBindableObservableVector. Ponadto zaimplementuj ISupportIncrementalLoading | Zaimplementuj IBindableVector, IBindableObservableVector i ISupportIncrementalLoading. |
Kontrolki listy można powiązać z arbitralnie dużymi źródłami danych i nadal osiągać wysoką wydajność przy użyciu ładowania przyrostowego. Można na przykład powiązać kontrolki listy z wynikami zapytania obrazów Bing bez konieczności ładowania wszystkich wyników jednocześnie. Zamiast tego załadujesz tylko niektóre wyniki natychmiast i załaduj dodatkowe wyniki zgodnie z potrzebami. Aby obsługiwać ładowanie przyrostowe, należy zaimplementować funkcję ISupportIncrementalLoading w źródle danych obsługującym powiadomienia o zmianie kolekcji. Gdy aparat powiązania danych żąda większej ilości danych, źródło danych musi wysłać odpowiednie żądania, zintegrować wyniki, a następnie wysłać odpowiednie powiadomienia w celu zaktualizowania interfejsu użytkownika.
Obiekt docelowy powiązania
W dwóch poniższych przykładach właściwość Button.Content jest obiektem docelowym powiązania, a jego wartość jest ustawiona na rozszerzenie znaczników, które deklaruje obiekt powiązania. Pierwszy element {x:Bind} jest wyświetlany, a następnie {Binding}. Deklarowanie powiązań w adiustacji to typowy przypadek (wygodny, czytelny i narzędziowy). Jednak można uniknąć znaczników i imperatywnie (programowo) utworzyć wystąpienie klasy Binding , jeśli zajdzie taka potrzeba.
<Button Content="{x:Bind ...}" ... />
<Button Content="{Binding ...}" ... />
Jeśli używasz rozszerzeń składników języka C++/WinRT lub Visual C++ (C++/CX), musisz dodać atrybut BindableAttribute do dowolnej klasy środowiska uruchomieniowego, z którą chcesz użyć rozszerzenia znaczników {Binding} .
Ważne
Jeśli używasz języka C++/WinRT, atrybut BindableAttribute jest dostępny, jeśli zainstalowano zestaw Windows SDK w wersji 10.0.17763.0 (Windows 10, wersja 1809) lub nowszym. Bez tego atrybutu należy zaimplementować interfejsy ICustomPropertyProvider i ICustomProperty , aby móc używać rozszerzenia znaczników {Binding} .
Zadeklarowany obiekt powiązania przy użyciu elementu {x:Bind}
Przed utworzeniem naszego znaczników {x:Bind} musimy wykonać jeden krok. Musimy uwidocznić naszą klasę źródłową powiązania z klasy reprezentującej naszą stronę narzutu. Robimy to przez dodanie właściwości (typu HostViewModel w tym przypadku) do klasy strony MainPage .
namespace DataBindingInDepth
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.ViewModel = new HostViewModel();
}
public HostViewModel ViewModel { get; set; }
}
}
// MainPage.idl
import "HostViewModel.idl";
namespace DataBindingInDepth
{
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
MainPage();
HostViewModel ViewModel{ get; };
}
}
// MainPage.h
// Include a header, and add this field:
...
#include "HostViewModel.h"
...
DataBindingInDepth::HostViewModel ViewModel();
private:
DataBindingInDepth::HostViewModel m_viewModel{ nullptr };
...
// MainPage.cpp
// Implement like this:
...
MainPage::MainPage()
{
InitializeComponent();
}
DataBindingInDepth::HostViewModel MainPage::ViewModel()
{
return m_viewModel;
}
...
W tym celu możemy teraz przyjrzeć się bliżej znacznikowi, który deklaruje obiekt powiązania. W poniższym przykładzie użyto tego samego elementu docelowego powiązania Button.Content użytego wcześniej w sekcji "Wiązanie elementu docelowego" i pokazano, że jest ona powiązana z właściwością HostViewModel.NextButtonText .
<!-- MainPage.xaml -->
<Page x:Class="DataBindingInDepth.Mainpage" ... >
<Button Content="{x:Bind Path=ViewModel.NextButtonText, Mode=OneWay}" ... />
</Page>
Zwróć uwagę na wartość, którą określamy dla ścieżki. Ta wartość jest interpretowana w kontekście samej strony, a w tym przypadku ścieżka rozpoczyna się od odwołania się do właściwości ViewModel , którą właśnie dodaliśmy do strony MainPage . Ta właściwość zwraca wystąpienie Elementu HostViewModel , więc możemy umieścić kropkę w tym obiekcie, aby uzyskać dostęp do właściwości HostViewModel.NextButtonText . Określamy tryb, aby zastąpić wartość domyślną {x:Bind} jednorazową.
Właściwość Path obsługuje różne opcje składni powiązania z zagnieżdżonymi właściwościami, dołączonymi właściwościami oraz indeksatorami liczb całkowitych i ciągów. Aby uzyskać więcej informacji, zobacz Składnia ścieżki właściwości. Powiązanie z indeksatorami ciągów daje efekt powiązania z właściwościami dynamicznymi bez konieczności implementowania elementu ICustomPropertyProvider. Inne ustawienia można znaleźć w temacie {x:Bind} markup extension (Rozszerzenie znaczników {x:Bind}).
Aby zilustrować, że właściwość HostViewModel.NextButtonText jest rzeczywiście zauważalna, dodaj procedurę obsługi zdarzeń Click do przycisku i zaktualizuj wartość HostViewModel.NextButtonText. Skompiluj, uruchom i kliknij przycisk , aby wyświetlić wartość aktualizacji zawartości przycisku.
// MainPage.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
this.ViewModel.NextButtonText = "Updated Next button text";
}
// MainPage.cpp
void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
{
ViewModel().NextButtonText(L"Updated Next button text");
}
Uwaga / Notatka
Zmiany elementu TextBox.Text są wysyłane do dwukierunkowego źródła powiązanego, gdy pole TextBox traci fokus, a nie po każdym naciśnięciu użytkownika.
DataTemplate i x:DataType
Wewnątrz elementu DataTemplate (niezależnie od tego, czy jest używany jako szablon elementu, szablon zawartości, czy szablon nagłówka), wartość Path nie jest interpretowana w kontekście strony, ale w kontekście obiektu danych, który jest szablonowany. W przypadku używania elementu {x:Bind} w szablonie danych, aby można było zweryfikować jego powiązania (i efektywnie wygenerować dla nich kod) w czasie kompilacji, narzędzie DataTemplate musi zadeklarować typ obiektu danych przy użyciu elementu x:DataType. W przykładzie podanym poniżej można użyć jako elementu ItemTemplate kontrolki items powiązanej z kolekcją obiektów SampleDataGroup .
<DataTemplate x:Key="SimpleItemTemplate" x:DataType="data:SampleDataGroup">
<StackPanel Orientation="Vertical" Height="50">
<TextBlock Text="{x:Bind Title}"/>
<TextBlock Text="{x:Bind Description}"/>
</StackPanel>
</DataTemplate>
Słabo wpisane obiekty w ścieżce
Rozważmy na przykład typ o nazwie SampleDataGroup, który implementuje właściwość ciągu o nazwie Title. Masz właściwość MainPage.SampleDataGroupAsObject, która jest obiektem typu, ale która rzeczywiście zwraca wystąpienie klasy SampleDataGroup. Powiązanie <TextBlock Text="{x:Bind SampleDataGroupAsObject.Title}"/> spowoduje błąd kompilacji, ponieważ właściwość Title nie zostanie znaleziona w obiekcie typu. Rozwiązaniem tego problemu jest dodanie rzutowania do składni Ścieżka w następujący sposób: <TextBlock Text="{x:Bind ((data:SampleDataGroup)SampleDataGroupAsObject).Title}"/>. Oto inny przykład, w którym element jest zadeklarowany jako obiekt, ale jest w rzeczywistości TextBlock: <TextBlock Text="{x:Bind Element.Text}"/>. A obsada zaradczy problem: <TextBlock Text="{x:Bind ((TextBlock)Element).Text}"/>.
Jeśli dane są ładowane asynchronicznie
Kod obsługujący element {x:Bind} jest generowany w czasie kompilacji w klasach częściowych dla stron. Te pliki można znaleźć w folderze obj z nazwami takimi jak (dla języka C#). <view name>.g.cs Wygenerowany kod zawiera procedurę obsługi zdarzenia ładowania strony, a program obsługi wywołuje metodę Initialize w wygenerowanej klasie reprezentującej powiązania strony.
Inicjuj z kolei wywołuje metodę Update , aby rozpocząć przenoszenie danych między źródłem powiązania a obiektem docelowym.
Ładowanie jest wywoływane tuż przed pierwszym przekazaniem miary strony lub kontrolki użytkownika. Dlatego jeśli dane są ładowane asynchronicznie, może nie być gotowe przez wywołanie inicjowania . Dlatego po załadowaniu danych można wymusić zainicjowanie powiązań jednorazowych przez wywołanie metody this.Bindings.Update();. Jeśli potrzebujesz tylko jednorazowych powiązań dla danych asynchronicznych, znacznie tańsze jest zainicjowanie ich w ten sposób niż wiązania jednokierunkowe i nasłuchiwanie zmian. Jeśli dane nie są poddawane szczegółowym zmianom, a jeśli prawdopodobnie zostaną zaktualizowane w ramach określonej akcji, możesz jednorazowo wprowadzić powiązania i wymusić ręczną aktualizację w dowolnym momencie z wywołaniem aktualizacji do aktualizacji.
Uwaga / Notatka
Element {x:Bind} nie jest odpowiedni dla scenariuszy związanych z późnym opóźnieniem, takich jak nawigowanie po strukturze słownika obiektu JSON ani wpisywanie duck. "Duck typing" jest słabą formą pisania na podstawie dopasowań leksykalnych na nazwach właściwości (jak w, "jeśli chodzi, pływa i quacks jak kaczka, to jest kaczka"). W przypadku wpisywania kaczki powiązanie z właściwością Age byłoby równie zadowolone z obiektu Person lub Wine (przy założeniu, że te typy mają właściwość Age ). W tych scenariuszach użyj rozszerzenia znaczników {Binding} .
Obiekt powiązania zadeklarowany przy użyciu elementu {Binding}
Jeśli używasz rozszerzeń składników języka C++/WinRT lub Visual C++ (C++/CX), aby użyć rozszerzenia znaczników {Binding} , musisz dodać atrybut BindableAttribute do dowolnej klasy środowiska uruchomieniowego, z którą chcesz powiązać. Aby użyć elementu {x:Bind}, nie potrzebujesz tego atrybutu.
// HostViewModel.idl
// Add this attribute:
[Windows.UI.Xaml.Data.Bindable]
runtimeclass HostViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
...
Ważne
Jeśli używasz języka C++/WinRT, atrybut BindableAttribute jest dostępny, jeśli zainstalowano zestaw Windows SDK w wersji 10.0.17763.0 (Windows 10, wersja 1809) lub nowszym. Bez tego atrybutu należy zaimplementować interfejsy ICustomPropertyProvider i ICustomProperty , aby móc używać rozszerzenia znaczników {Binding} .
Ustawienie {Binding} zakłada, że domyślnie wiążesz się z elementem DataContext strony znaczników. Dlatego ustawimy element DataContext naszej strony jako wystąpienie klasy źródłowej powiązania (w tym przypadku typu HostViewModel ). W poniższym przykładzie przedstawiono znaczniki, które deklarują obiekt powiązania. Używamy tego samego elementu docelowego powiązania Button.Content , którego używaliśmy wcześniej w sekcji "Wiązanie elementu docelowego" i tworzymy powiązanie z właściwością HostViewModel.NextButtonText .
<Page xmlns:viewmodel="using:DataBindingInDepth" ... >
<Page.DataContext>
<viewmodel:HostViewModel x:Name="viewModelInDataContext"/>
</Page.DataContext>
...
<Button Content="{Binding Path=NextButtonText}" ... />
</Page>
// MainPage.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
this.viewModelInDataContext.NextButtonText = "Updated Next button text";
}
// MainPage.cpp
void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
{
viewModelInDataContext().NextButtonText(L"Updated Next button text");
}
Zwróć uwagę na wartość, którą określamy dla ścieżki. Ta wartość jest interpretowana w kontekście elementu DataContext strony, który w tym przykładzie jest ustawiony na wystąpienie elementu HostViewModel. Ścieżka odwołuje się do właściwości HostViewModel.NextButtonText . Możemy pominąć tryb, ponieważ w tym miejscu działa ustawienie domyślne {Binding} .
Wartość domyślna elementu DataContext dla elementu interfejsu użytkownika to dziedziczona wartość elementu nadrzędnego. Można oczywiście zastąpić to ustawienie domyślne, ustawiając jawnie element DataContext , który jest z kolei dziedziczony przez elementy podrzędne domyślnie. Jawne ustawienie elementu DataContext jest przydatne, gdy chcesz mieć wiele powiązań, które używają tego samego źródła.
Obiekt powiązania ma właściwość Source , która domyślnie jest ustawiona na Wartość DataContext elementu interfejsu użytkownika, na którym zadeklarowano powiązanie. Tę wartość domyślną można zastąpić, ustawiając jawnie ustawienie wartości Source, RelativeSource lub ElementName dla powiązania (zobacz {Binding} ), aby uzyskać szczegółowe informacje.
Wewnątrz elementu DataTemplate obiekt DataContext jest automatycznie ustawiany na obiekt danych, który jest szablonowany. W przykładzie podanym poniżej można użyć kontrolki ItemTemplate elementów powiązanej z kolekcją dowolnego typu, która ma właściwości ciągu o nazwie Title i Description.
<DataTemplate x:Key="SimpleItemTemplate">
<StackPanel Orientation="Vertical" Height="50">
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="{Binding Description"/>
</StackPanel>
</DataTemplate>
Uwaga / Notatka
Domyślnie zmiany właściwości TextBox.Text są wysyłane do dwukierunkowego źródła powiązanego, gdy pole TextBox traci fokus. Aby spowodować wysłanie zmian po każdym naciśnięciu użytkownika, ustaw właściwość UpdateSourceTrigger na WłaściwośćChanged w powiązaniu w znaczników. Możesz również całkowicie przejąć kontrolę nad tym, kiedy zmiany są wysyłane do źródła, ustawiając parametr UpdateSourceTrigger na jawne. Następnie obsługujesz zdarzenia w polu tekstowym (zazwyczaj TextBox.TextChanged), wywołaj metodę GetBindingExpression w obiekcie docelowym, aby uzyskać obiekt BindingExpression , a na koniec wywołaj metodę BindingExpression.UpdateSource , aby programowo zaktualizować źródło danych.
Właściwość Path obsługuje różne opcje składni powiązania z zagnieżdżonymi właściwościami, dołączonymi właściwościami oraz indeksatorami liczb całkowitych i ciągów. Aby uzyskać więcej informacji, zobacz Składnia ścieżki właściwości. Powiązanie z indeksatorami ciągów daje efekt powiązania z właściwościami dynamicznymi bez konieczności implementowania elementu ICustomPropertyProvider. Właściwość ElementName jest przydatna w przypadku powiązania elementu-element. Właściwość RelativeSource ma kilka zastosowań, z których jedna jest bardziej zaawansowaną alternatywą dla powiązania szablonu wewnątrz elementu ControlTemplate. Aby uzyskać inne ustawienia, zobacz rozszerzenie znaczników {Binding} i klasę Binding .
Co zrobić, jeśli źródło i obiekt docelowy nie są tego samego typu?
Jeśli chcesz kontrolować widoczność elementu interfejsu użytkownika na podstawie wartości właściwości logicznej, lub jeśli chcesz renderować element interfejsu użytkownika z kolorem, który jest funkcją zakresu lub trendu wartości liczbowej, lub jeśli chcesz wyświetlić wartość daty i/lub godziny we właściwości elementu interfejsu użytkownika, która oczekuje ciągu, następnie należy przekonwertować wartości z jednego typu na inny. W niektórych przypadkach właściwym rozwiązaniem jest uwidocznienie innej właściwości odpowiedniego typu z klasy źródłowej powiązania i zachowanie hermetyzacji logiki konwersji i możliwość ich testowania. Nie jest to jednak elastyczne ani skalowalne, jeśli masz dużą liczbę lub duże kombinacje właściwości źródłowych i docelowych. W takim przypadku masz kilka opcji:
- Jeśli używasz elementu {x:Bind}, możesz powiązać bezpośrednio z funkcją, aby wykonać tę konwersję
- Możesz też określić konwerter wartości, który jest obiektem przeznaczonym do przeprowadzenia konwersji
Konwertery wartości
Oto konwerter wartości, odpowiedni dla powiązania jednorazowego lub jednokierunkowego, który konwertuje wartość DateTime na wartość ciągu zawierającą miesiąc. Klasa implementuje element IValueConverter.
public class DateToStringConverter : IValueConverter
{
// Define the Convert method to convert a DateTime value to
// a month string.
public object Convert(object value, Type targetType,
object parameter, string language)
{
// value is the data from the source object.
DateTime thisdate = (DateTime)value;
int monthnum = thisdate.Month;
string month;
switch (monthnum)
{
case 1:
month = "January";
break;
case 2:
month = "February";
break;
default:
month = "Month not found";
break;
}
// Return the value to pass to the target.
return month;
}
// ConvertBack is not implemented for a OneWay binding.
public object ConvertBack(object value, Type targetType,
object parameter, string language)
{
throw new NotImplementedException();
}
}
// See the "Formatting or converting data values for display" section in the "Data binding overview" topic.
Oto jak używasz tego konwertera wartości w znaczniku obiektu powiązania.
<UserControl.Resources>
<local:DateToStringConverter x:Key="Converter1"/>
</UserControl.Resources>
...
<TextBlock Grid.Column="0"
Text="{x:Bind ViewModel.Month, Converter={StaticResource Converter1}}"/>
<TextBlock Grid.Column="0"
Text="{Binding Month, Converter={StaticResource Converter1}}"/>
Aparat powiązania wywołuje metody Convert i ConvertBack , jeśli parametr Konwerter jest zdefiniowany dla powiązania. Gdy dane są przekazywane ze źródła, aparat powiązania wywołuje metodę Convert i przekazuje zwrócone dane do obiektu docelowego. Gdy dane są przekazywane z obiektu docelowego (dla powiązania dwukierunkowego), aparat powiązania wywołuje metodę ConvertBack i przekazuje zwrócone dane do źródła.
Konwerter ma również opcjonalne parametry: ConverterLanguage, który umożliwia określenie języka, który ma być używany w konwersji, i ConverterParameter, który umożliwia przekazanie parametru dla logiki konwersji. Przykład, który używa parametru konwertera, zobacz IValueConverter.
Uwaga / Notatka
Jeśli w konwersji wystąpi błąd, nie zgłaszaj wyjątku. Zamiast tego zwróć wartość DependencyProperty.UnsetValue, co spowoduje zatrzymanie transferu danych.
Aby wyświetlić wartość domyślną, która ma być używana za każdym razem, gdy nie można rozpoznać źródła powiązania, ustaw właściwość FallbackValue na obiekcie powiązania w adiustacji. Jest to przydatne do obsługi błędów konwersji i formatowania. Przydatne jest również powiązanie z właściwościami źródłowymi, które mogą nie istnieć we wszystkich obiektach w powiązanej kolekcji typów heterogenicznych.
Jeśli powiążesz kontrolkę tekstową z wartością, która nie jest ciągiem, aparat powiązania danych przekonwertuje wartość na ciąg. Jeśli wartość jest typem odwołania, aparat powiązania danych pobierze wartość ciągu, wywołując metodę ICustomPropertyProvider.GetStringRepresentation lub IStringable.ToString , jeśli jest dostępna, i w przeciwnym razie wywoła metodę Object.ToString. Należy jednak pamiętać, że aparat powiązania zignoruje dowolną implementację tostringu , która ukrywa implementację klasy bazowej. Implementacje podklasy powinny zastąpić metodę ToString klasy bazowej. Podobnie w językach natywnych wszystkie obiekty zarządzane wydają się implementować ICustomPropertyProvider i IStringable. Jednak wszystkie wywołania metody GetStringRepresentation i IStringable.ToString są kierowane do metody Object.ToString lub przesłonięcia tej metody, a nigdy nie do nowej implementacji toString , która ukrywa implementację klasy bazowej.
Uwaga / Notatka
Począwszy od systemu Windows 10 w wersji 1607, platforma XAML udostępnia wbudowany konwerter logiczny do widoczności. Konwerter mapuje wartość true na wartość wyliczenia Visible i false na Zwinięte , aby można było powiązać właściwość Widoczność z wartością logiczną bez tworzenia konwertera. Aby użyć wbudowanego konwertera, minimalna wersja docelowego zestawu SDK aplikacji musi być 14393 lub nowsza. Nie można jej używać, gdy aplikacja jest przeznaczona dla starszych wersji systemu Windows 10. Aby uzyskać więcej informacji na temat wersji docelowych, zobacz Wersja kodu adaptacyjnego.
Powiązanie funkcji w elemencie {x:Bind}
Element {x:Bind} umożliwia wykonanie ostatniego kroku w ścieżce powiązania jako funkcji. Może to służyć do przeprowadzania konwersji i wykonywania powiązań, które zależą od więcej niż jednej właściwości. Zobacz Funkcje w x:Bind
Powiązanie elementu-element-element
Właściwość jednego elementu XAML można powiązać z właściwością innego elementu XAML. Oto przykład tego, jak wygląda to w znacznikach.
<TextBox x:Name="myTextBox" />
<TextBlock Text="{x:Bind myTextBox.Text, Mode=OneWay}" />
Ważne
Aby uzyskać niezbędny przepływ pracy dla powiązania elementu-element przy użyciu języka C++/WinRT, zobacz powiązanie element-element.
Słowniki zasobów z {x:Bind}
Rozszerzenie znaczników {x:Bind} zależy od generowania kodu, dlatego potrzebuje pliku za kodem zawierającego konstruktor, który wywołuje metodę InitializeComponent (aby zainicjować wygenerowany kod). Słownik zasobów należy ponownie użyć, tworząc wystąpienie jego typu (tak, aby element InitializeComponent był wywoływany) zamiast odwoływać się do jego nazwy pliku. Oto przykład tego, co zrobić, jeśli masz istniejący słownik zasobów i chcesz w nim użyć elementu {x:Bind}.
TemplatesResourceDictionary.xaml
<ResourceDictionary
x:Class="ExampleNamespace.TemplatesResourceDictionary"
.....
xmlns:examplenamespace="using:ExampleNamespace">
<DataTemplate x:Key="EmployeeTemplate" x:DataType="examplenamespace:IEmployee">
<Grid>
<TextBlock Text="{x:Bind Name}"/>
</Grid>
</DataTemplate>
</ResourceDictionary>
TemplatesResourceDictionary.xaml.cs
using Windows.UI.Xaml.Data;
namespace ExampleNamespace
{
public partial class TemplatesResourceDictionary
{
public TemplatesResourceDictionary()
{
InitializeComponent();
}
}
}
MainPage.xaml
<Page x:Class="ExampleNamespace.MainPage"
....
xmlns:examplenamespace="using:ExampleNamespace">
<Page.Resources>
<ResourceDictionary>
....
<ResourceDictionary.MergedDictionaries>
<examplenamespace:TemplatesResourceDictionary/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Page.Resources>
</Page>
Mieszanie {x:Bind} i {Binding} w stylu wielokrotnego użytku
W poprzednim przykładzie pokazano użycie elementu {x:Bind} w elemencie DataTemplates, ale można również utworzyć style wielokrotnego użytku, które łączą zarówno rozszerzenia znaczników {x:Bind} jak i {Binding}. Jest to przydatne, gdy chcesz powiązać niektóre właściwości ze znanymi wartościami w czasie kompilacji przy użyciu elementu {x:Bind} i innych właściwości do wartości DataContext środowiska uruchomieniowego przy użyciu elementu {Binding}.
Oto przykład pokazujący sposób tworzenia stylu przycisku wielokrotnego użytku, który używa obu metod powiązania:
TemplatesResourceDictionary.xaml
<ResourceDictionary
x:Class="ExampleNamespace.TemplatesResourceDictionary"
.....
xmlns:examplenamespace="using:ExampleNamespace">
<!-- DataTemplate using x:Bind -->
<DataTemplate x:Key="EmployeeTemplate" x:DataType="examplenamespace:IEmployee">
<Grid>
<TextBlock Text="{x:Bind Name}"/>
</Grid>
</DataTemplate>
<!-- Style that mixes x:Bind and Binding -->
<Style x:Key="CustomButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{Binding ButtonBackgroundBrush}"/>
<Setter Property="Foreground" Value="{Binding ButtonForegroundBrush}"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Margin" Value="4"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="RootBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<!-- x:Bind to a static property or page-level property -->
<Ellipse Width="8" Height="8"
Fill="{x:Bind DefaultIndicatorBrush}"
Margin="0,0,8,0"/>
<!-- Binding to DataContext -->
<ContentPresenter x:Name="ContentPresenter"
Content="{TemplateBinding Content}"
Foreground="{TemplateBinding Foreground}"
FontSize="{TemplateBinding FontSize}"/>
</StackPanel>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<!-- Binding to DataContext for hover color -->
<Setter Target="RootBorder.Background"
Value="{Binding ButtonHoverBrush}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<!-- x:Bind to a compile-time known resource -->
<Setter Target="RootBorder.Background"
Value="{x:Bind DefaultPressedBrush}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
TemplatesResourceDictionary.xaml.cs
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;
namespace ExampleNamespace
{
public partial class TemplatesResourceDictionary
{
public TemplatesResourceDictionary()
{
InitializeComponent();
}
// Properties for x:Bind - these are compile-time bound
public SolidColorBrush DefaultIndicatorBrush { get; } =
new SolidColorBrush(Colors.Green);
public SolidColorBrush DefaultPressedBrush { get; } =
new SolidColorBrush(Colors.DarkGray);
}
}
Użycie w pliku MainPage.xaml z modelem ViewModel udostępniającym wartości środowiska uruchomieniowego:
<Page x:Class="ExampleNamespace.MainPage"
....
xmlns:examplenamespace="using:ExampleNamespace">
<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<examplenamespace:TemplatesResourceDictionary/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Page.Resources>
<Page.DataContext>
<examplenamespace:ButtonThemeViewModel/>
</Page.DataContext>
<StackPanel Margin="20">
<!-- This button uses the mixed binding style -->
<Button Content="Save" Style="{StaticResource CustomButtonStyle}"/>
<Button Content="Cancel" Style="{StaticResource CustomButtonStyle}"/>
</StackPanel>
</Page>
ButtonThemeViewModel.cs (element DataContext, który udostępnia wartości powiązań środowiska uruchomieniowego):
using System.ComponentModel;
using Windows.UI;
using Windows.UI.Xaml.Media;
namespace ExampleNamespace
{
public class ButtonThemeViewModel : INotifyPropertyChanged
{
private SolidColorBrush _buttonBackgroundBrush = new SolidColorBrush(Colors.LightBlue);
private SolidColorBrush _buttonForegroundBrush = new SolidColorBrush(Colors.DarkBlue);
private SolidColorBrush _buttonHoverBrush = new SolidColorBrush(Colors.LightCyan);
public SolidColorBrush ButtonBackgroundBrush
{
get => _buttonBackgroundBrush;
set
{
_buttonBackgroundBrush = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ButtonBackgroundBrush)));
}
}
public SolidColorBrush ButtonForegroundBrush
{
get => _buttonForegroundBrush;
set
{
_buttonForegroundBrush = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ButtonForegroundBrush)));
}
}
public SolidColorBrush ButtonHoverBrush
{
get => _buttonHoverBrush;
set
{
_buttonHoverBrush = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ButtonHoverBrush)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
W tym przykładzie:
- Element {Binding} jest używany dla właściwości, które zależą od elementu DataContext (ButtonBackgroundBrush, ButtonForegroundBrush, ButtonHoverBrush)
- Element {x:Bind} jest używany do właściwości, które są znane w czasie kompilacji i należą do samego elementu ResourceDictionary (DefaultIndicatorBrush, DefaultPressedBrush)
- Styl jest wielokrotnego użytku i można go zastosować do dowolnego przycisku
- Motywy środowiska uruchomieniowego są możliwe za pośrednictwem elementu DataContext, a jednocześnie korzystają z wydajności elementu {x:Bind} dla elementów statycznych
Powiązanie zdarzeń i ICommand
Aplikacja {x:Bind} obsługuje funkcję o nazwie powiązanie zdarzeń. Dzięki tej funkcji można określić procedurę obsługi dla zdarzenia przy użyciu powiązania, która jest dodatkową opcją obsługi zdarzeń za pomocą metody w pliku za pomocą kodu. Załóżmy, że masz właściwość RootFrame w klasie MainPage .
public sealed partial class MainPage : Page
{
...
public Frame RootFrame { get { return Window.Current.Content as Frame; } }
}
Następnie możesz powiązać zdarzenie Click przycisku z metodą w obiekcie Frame zwróconym przez właściwość RootFrame w następujący sposób. Należy również pamiętać, że właściwość IsEnabled przycisku jest powiązana z innym elementem członkowskim tej samej ramki.
<AppBarButton Icon="Forward" IsCompact="True"
IsEnabled="{x:Bind RootFrame.CanGoForward, Mode=OneWay}"
Click="{x:Bind RootFrame.GoForward}"/>
Metody przeciążone nie mogą być używane do obsługi zdarzenia za pomocą tej techniki. Ponadto jeśli metoda obsługując zdarzenie ma parametry, wszystkie muszą być przypisywane z typów wszystkich parametrów zdarzenia, odpowiednio. W tym przypadku element Frame.GoForward nie jest przeciążony i nie ma parametrów (ale nadal będzie prawidłowy, nawet jeśli wziął dwa parametry obiektu ). Funkcja Frame.GoBack jest jednak przeciążona, więc nie możemy użyć tej metody z tą techniką.
Technika powiązania zdarzeń jest podobna do implementowania i używania poleceń (polecenie jest właściwością zwracającą obiekt implementujący interfejs ICommand ). Zarówno element {x:Bind} jak i {Binding} współpracują z poleceniami. Aby nie trzeba było wielokrotnie implementować wzorca polecenia, możesz użyć klasy pomocnika DelegateCommand , którą znajdziesz w przykładzie QuizGame (w folderze "Common").
Wiązanie z kolekcją folderów lub plików
Interfejsy API można użyć w przestrzeni nazw Windows.Storage , aby pobrać dane folderów i plików. Jednak różne metody GetFilesAsync, GetFoldersAsync i GetItemsAsync nie zwracają wartości odpowiednich do powiązania z kontrolkami listy. Zamiast tego należy powiązać z zwracanymi wartościami metod GetVirtualizedFilesVector, GetVirtualizedFoldersVector i GetVirtualizedItemsVector klasy FileInformationFactory. Poniższy przykład kodu z przykładu StorageDataSource i GetVirtualizedFilesVector przedstawia typowy wzorzec użycia. Pamiętaj, aby zadeklarować funkcję picturesLibrary w manifeście pakietu aplikacji i potwierdzić, że w folderze biblioteki obrazów znajdują się obrazy.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var library = Windows.Storage.KnownFolders.PicturesLibrary;
var queryOptions = new Windows.Storage.Search.QueryOptions();
queryOptions.FolderDepth = Windows.Storage.Search.FolderDepth.Deep;
queryOptions.IndexerOption = Windows.Storage.Search.IndexerOption.UseIndexerWhenAvailable;
var fileQuery = library.CreateFileQueryWithOptions(queryOptions);
var fif = new Windows.Storage.BulkAccess.FileInformationFactory(
fileQuery,
Windows.Storage.FileProperties.ThumbnailMode.PicturesView,
190,
Windows.Storage.FileProperties.ThumbnailOptions.UseCurrentScale,
false
);
var dataSource = fif.GetVirtualizedFilesVector();
this.PicturesListView.ItemsSource = dataSource;
}
Zazwyczaj ta metoda służy do tworzenia widoku informacji o plikach i folderach tylko do odczytu. Powiązania dwukierunkowe można tworzyć we właściwościach pliku i folderu, na przykład aby umożliwić użytkownikom ocenianie utworu w widoku muzyki. Jednak wszelkie zmiany nie są utrwalane, dopóki nie wywołasz odpowiedniej metody SavePropertiesAsync (na przykład MusicProperties.SavePropertiesAsync). Należy zatwierdzić zmiany, gdy element utraci fokus, ponieważ spowoduje to zresetowanie zaznaczenia.
Należy pamiętać, że powiązanie dwukierunkowe przy użyciu tej techniki działa tylko z indeksowanymi lokalizacjami, takimi jak Muzyka. Możesz określić, czy lokalizacja jest indeksowana, wywołując metodę FolderInformation.GetIndexedStateAsync .
Należy również pamiętać, że zwirtualizowany wektor może zwracać wartość null dla niektórych elementów przed wypełnieniem ich wartości. Na przykład przed użyciem wartości SelectedItem kontrolki listy powiązanej z wektorem zwirtualizowanym należy sprawdzić wartość null lub użyć zamiast tego polecenia SelectedIndex.
Wiązanie z danymi pogrupowane według klucza
Jeśli weźmiesz płaską kolekcję elementów (na przykład książek reprezentowanych przez klasę BookSku ) i pogrupujesz elementy przy użyciu właściwości wspólnej jako klucza (na przykład właściwości BookSku.AuthorName ), wynik jest nazywany pogrupowane dane. Pogrupowanie danych nie jest już kolekcją płaską. Pogrupowane dane to kolekcja obiektów grupowych, w których każdy obiekt grupy ma
- klucz i
- kolekcja elementów, których właściwość pasuje do tego klucza.
Aby ponownie wziąć przykładowe książki, wynik grupowania książek według nazwy autora powoduje kolekcję grup nazw autora, w których każda grupa ma
- klucz, który jest nazwą autora i
- kolekcja bookSku, której właściwość AuthorName pasuje do klucza grupy.
Ogólnie rzecz biorąc, aby wyświetlić kolekcję, należy powiązać element ItemsSource kontrolki elementów (np. ListView lub GridView) bezpośrednio z właściwością zwracającą kolekcję. Jeśli jest to płaska kolekcja elementów, nie musisz wykonywać żadnych specjalnych czynności. Jeśli jednak jest to kolekcja obiektów grupowych (tak jak w przypadku powiązania z pogrupowanych danych), potrzebne są usługi obiektu pośredniego o nazwie CollectionViewSource , który znajduje się między kontrolką elementów a źródłem powiązania. Powiążesz właściwość CollectionViewSource z właściwością, która zwraca zgrupowane dane, i powiążesz kontrolkę elementy z kontrolką CollectionViewSource. Dodatkową wartością dodatku CollectionViewSource jest to, że śledzi bieżący element, dzięki czemu można zachować więcej niż jedną kontrolkę elementów w synchronizacji przez powiązanie ich wszystkich z tym samym elementem CollectionViewSource. Dostęp do bieżącego elementu można również uzyskać programowo za pomocą właściwości ICollectionView.CurrentItem obiektu zwróconego przez właściwość CollectionViewSource.View .
Aby aktywować obiekt grupowania kolekcji CollectionViewSource, ustaw dla parametru IsSourceGroupedwartość true. Niezależnie od tego, czy należy również ustawić właściwość ItemsPath , zależy od tego, jak tworzysz obiekty grupy. Istnieją dwa sposoby tworzenia obiektu grupy: wzorzec "is-a-group" i wzorzec "has-a-group". We wzorcu "is-a-group" obiekt grupy pochodzi z typu kolekcji (na przykład Lista<T>), więc obiekt grupy jest faktycznie grupą elementów. Za pomocą tego wzorca nie trzeba ustawiać elementu ItemsPath. We wzorcu "has-a-group" obiekt grupy ma co najmniej jedną właściwość typu kolekcji (na przykład Lista<T>), więc grupa "ma" grupę elementów w postaci właściwości (lub kilka grup elementów w postaci kilku właściwości). Za pomocą tego wzorca należy ustawić element ItemsPath na nazwę właściwości zawierającej grupę elementów.
W poniższym przykładzie przedstawiono wzorzec "has-a-group". Klasa strony ma właściwość o nazwie ViewModel, która zwraca wystąpienie naszego modelu widoku. Właściwość CollectionViewSource wiąże się z właściwością Autorzy modelu widoku (Autorzy są kolekcją obiektów grupy), a także określa, że jest to właściwość Author.BookSkus zawierająca zgrupowane elementy. Na koniec element GridView jest powiązany z elementem CollectionViewSource i ma zdefiniowany styl grupy, aby umożliwić renderowanie elementów w grupach.
<Page.Resources>
<CollectionViewSource
x:Name="AuthorHasACollectionOfBookSku"
Source="{x:Bind ViewModel.Authors}"
IsSourceGrouped="true"
ItemsPath="BookSkus"/>
</Page.Resources>
...
<GridView
ItemsSource="{x:Bind AuthorHasACollectionOfBookSku}" ...>
<GridView.GroupStyle>
<GroupStyle
HeaderTemplate="{StaticResource AuthorGroupHeaderTemplateWide}" ... />
</GridView.GroupStyle>
</GridView>
Wzorzec "is-a-group" można zaimplementować na jeden z dwóch sposobów. Jednym ze sposobów jest utworzenie własnej klasy grupy. Utwórz klasę z listy<T> (gdzie T jest typem elementów). Na przykład public class Author : List<BookSku>. Drugim sposobem jest użycie wyrażenia LINQ do dynamicznego tworzenia obiektów grupy (i klasy grupowej) z takich wartości właściwości elementów BookSku . Takie podejście — utrzymywanie tylko płaskiej listy elementów i grupowanie ich razem na bieżąco — jest typowe dla aplikacji, która uzyskuje dostęp do danych z usługi w chmurze. Elastyczność grupowania książek według autora lub gatunku (na przykład) jest możliwa bez konieczności używania specjalnych klas grupowych, takich jak Autor i Gatunek.
W poniższym przykładzie przedstawiono wzorzec "is-a-group" przy użyciu LINQ. Tym razem grupujemy książki według gatunku, wyświetlane z nazwą gatunku w nagłówkach grupy. Jest to wskazywane przez ścieżkę właściwości "Klucz" w odwołaniu do wartości klucza grupy.
using System.Linq;
...
private IOrderedEnumerable<IGrouping<string, BookSku>> genres;
public IOrderedEnumerable<IGrouping<string, BookSku>> Genres
{
get
{
if (this.genres == null)
{
this.genres = from book in this.bookSkus
group book by book.genre into grp
orderby grp.Key
select grp;
}
return this.genres;
}
}
Należy pamiętać, że w przypadku korzystania z szablonów danych {x:Bind} musimy wskazać typ powiązany, ustawiając wartość x:DataType . Jeśli typ jest ogólny, nie możemy wyrazić tego w adiustacji, dlatego musimy użyć {Binding} zamiast tego w szablonie nagłówka stylu grupy.
<Grid.Resources>
<CollectionViewSource x:Name="GenreIsACollectionOfBookSku"
Source="{x:Bind Genres}"
IsSourceGrouped="true"/>
</Grid.Resources>
<GridView ItemsSource="{x:Bind GenreIsACollectionOfBookSku}">
<GridView.ItemTemplate x:DataType="local:BookTemplate">
<DataTemplate>
<TextBlock Text="{x:Bind Title}"/>
</DataTemplate>
</GridView.ItemTemplate>
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</GridView.GroupStyle>
</GridView>
Kontrolka SemanticZoom to doskonały sposób na wyświetlanie i nawigowanie po zgrupowanych danych przez użytkowników. Przykładowa aplikacja Bookstore2 ilustruje sposób korzystania z semanticZoom. W tej aplikacji możesz wyświetlić listę książek pogrupowanych według autora (widok powiększenia) lub pomniejsić, aby wyświetlić listę autorów (widok powiększony). Lista skoków zapewnia znacznie szybszą nawigację niż przewijanie listy książek. Widoki powiększenia i powiększenia są w rzeczywistości kontrolkami ListView lub GridView powiązanymi z tym samym elementem CollectionViewSource.
Po powiązaniu z danymi hierarchicznymi , takimi jak podkategorie w kategoriach, można wybrać wyświetlanie poziomów hierarchicznych w interfejsie użytkownika z serią kontrolek elementów. Zaznaczenie w jednej kontrolce elementów określa zawartość kolejnych kontrolek elementów. Listy można synchronizować przez powiązanie każdej listy z własną kolekcją CollectionViewSource i powiązanie wystąpień CollectionViewSource w łańcuchu. Jest to nazywane widokiem wzorca/szczegółów (lub listy/szczegółów). Aby uzyskać więcej informacji, zobacz Jak powiązać dane hierarchiczne i utworzyć widok wzorca/szczegółów.
Diagnozowanie i debugowanie problemów z powiązaniem danych
Znacznik powiązania zawiera nazwy właściwości (i dla języka C#, czasami pola i metody). Dlatego podczas zmiany nazwy właściwości należy również zmienić dowolne powiązanie, które się do niej odwołuje. Zapominanie o tym prowadzi do typowego przykładu błędu powiązania danych, a aplikacja nie zostanie skompilowana lub nie zostanie uruchomiona poprawnie.
Obiekty powiązania utworzone przez element {x:Bind} i {Binding} są w dużej mierze równoważne funkcjonalnie. Element {x:Bind} zawiera informacje o typie źródła powiązania i generuje kod źródłowy w czasie kompilacji. Dzięki programowi {x:Bind} uzyskasz taki sam rodzaj wykrywania problemów, które otrzymujesz z resztą kodu. Obejmuje to walidację w czasie kompilacji wyrażeń powiązań i debugowanie przez ustawienie punktów przerwania w kodzie źródłowym wygenerowanym jako klasa częściowa dla strony. Te klasy można znaleźć w plikach w obj folderze z nazwami takimi jak (dla języka C#). <view name>.g.cs Jeśli masz problem z powiązaniem, włącz opcję Przerwij w nieobsługiwane wyjątki w debugerze programu Microsoft Visual Studio. Debuger przerwie wykonywanie w tym momencie, a następnie możesz debugować, co poszło nie tak. Kod wygenerowany przez element {x:Bind} jest zgodny z tym samym wzorcem dla każdej części grafu węzłów źródłowych powiązania i można użyć informacji w oknie stosu wywołań , aby ułatwić określenie sekwencji wywołań, które doprowadziły do problemu.
Element {Binding} nie zawiera informacji o typie dla źródła powiązania. Jednak po uruchomieniu aplikacji z dołączonym debugerem wszelkie błędy powiązania są wyświetlane w oknie Dane wyjściowe w programie Visual Studio.
Tworzenie powiązań w kodzie
Nuta Ta sekcja dotyczy tylko elementu {Binding}, ponieważ nie można utworzyć powiązań {x:Bind} w kodzie. Jednak niektóre z tych samych zalet programu {x:Bind} można osiągnąć za pomocą klasy DependencyObject.RegisterPropertyChangedCallback, co umożliwia zarejestrowanie się w celu otrzymywania powiadomień o zmianach dla dowolnej właściwości zależności.
Elementy interfejsu użytkownika można również połączyć z danymi przy użyciu kodu proceduralnego zamiast kodu XAML. W tym celu utwórz nowy obiekt Binding , ustaw odpowiednie właściwości, a następnie wywołaj metodę FrameworkElement.SetBinding lub BindingOperations.SetBinding. Tworzenie powiązań programowo jest przydatne, gdy chcesz wybrać wartości właściwości powiązania w czasie wykonywania lub udostępnić jedno powiązanie między wieloma kontrolkami. Należy jednak pamiętać, że po wywołaniu metody SetBinding nie można zmienić wartości właściwości powiązania.
W poniższym przykładzie pokazano, jak zaimplementować powiązanie w kodzie.
<TextBox x:Name="MyTextBox" Text="Text"/>
// Create an instance of the MyColors class
// that implements INotifyPropertyChanged.
MyColors textcolor = new MyColors();
// Brush1 is set to be a SolidColorBrush with the value Red.
textcolor.Brush1 = new SolidColorBrush(Colors.Red);
// Set the DataContext of the TextBox MyTextBox.
MyTextBox.DataContext = textcolor;
// Create the binding and associate it with the text box.
Binding binding = new Binding() { Path = new PropertyPath("Brush1") };
MyTextBox.SetBinding(TextBox.ForegroundProperty, binding);
' Create an instance of the MyColors class
' that implements INotifyPropertyChanged.
Dim textcolor As New MyColors()
' Brush1 is set to be a SolidColorBrush with the value Red.
textcolor.Brush1 = New SolidColorBrush(Colors.Red)
' Set the DataContext of the TextBox MyTextBox.
MyTextBox.DataContext = textcolor
' Create the binding and associate it with the text box.
Dim binding As New Binding() With {.Path = New PropertyPath("Brush1")}
MyTextBox.SetBinding(TextBox.ForegroundProperty, binding)
Porównanie funkcji {x:Bind} i {Binding}
| Funkcja | {x:Bind} a {Binding} | Notatki |
|---|---|---|
| Ścieżka jest właściwością domyślną | {x:Bind a.b.c}- {Binding a.b.c} |
|
| Właściwość Path | {x:Bind Path=a.b.c}- {Binding Path=a.b.c} |
W pliku x:Bind ścieżka jest domyślnie zakorzeniona na stronie, a nie w obiekcie DataContext. |
| Indexer | {x:Bind Groups[2].Title}- {Binding Groups[2].Title} |
Wiąże się z określonym elementem w kolekcji. Obsługiwane są tylko indeksy oparte na liczbach całkowitych. |
| Dołączone właściwości | {x:Bind Button22.(Grid.Row)}- {Binding Button22.(Grid.Row)} |
Dołączone właściwości są określane przy użyciu nawiasów. Jeśli właściwość nie jest zadeklarowana w przestrzeni nazw XAML, prefiks jest poprzedzony przestrzenią nazw XML, która powinna zostać zamapowana na przestrzeń nazw kodu na czele dokumentu. |
| Casting | {x:Bind groups[0].(data:SampleDataGroup.Title)}- Nie jest wymagane dla elementu {Binding}. |
Rzuty są określane przy użyciu nawiasów. Jeśli właściwość nie jest zadeklarowana w przestrzeni nazw XAML, prefiks jest poprzedzony przestrzenią nazw XML, która powinna zostać zamapowana na przestrzeń nazw kodu na czele dokumentu. |
| Konwerter | {x:Bind IsShown, Converter={StaticResource BoolToVisibility}}- {Binding IsShown, Converter={StaticResource BoolToVisibility}} |
Konwertery muszą być deklarowane w katalogu głównym elementu Page/ResourceDictionary lub w pliku App.xaml. |
| ConverterParameter, ConverterLanguage | {x:Bind IsShown, Converter={StaticResource BoolToVisibility}, ConverterParameter=One, ConverterLanguage=fr-fr}- {Binding IsShown, Converter={StaticResource BoolToVisibility}, ConverterParameter=One, ConverterLanguage=fr-fr} |
Konwertery muszą być deklarowane w katalogu głównym elementu Page/ResourceDictionary lub w pliku App.xaml. |
| TargetNullValue | {x:Bind Name, TargetNullValue=0}- {Binding Name, TargetNullValue=0} |
Używany, gdy liść wyrażenia powiązania ma wartość null. Użyj pojedynczych cudzysłowów dla wartości ciągu. |
| FallbackValue | {x:Bind Name, FallbackValue='empty'}- {Binding Name, FallbackValue='empty'} |
Używany, gdy dowolna część ścieżki powiązania (z wyjątkiem liścia) ma wartość null. |
| Nazwa elementu | {x:Bind slider1.Value}- {Binding Value, ElementName=slider1} |
W przypadku elementu {x:Bind} powiązanie jest powiązane z polem; Ścieżka jest domyślnie zakorzeniona na stronie, więc dostęp do dowolnego nazwanego elementu można uzyskać za pośrednictwem pola. |
| Źródło względne: Samozwań | <Rectangle x:Name="rect1" Width="200" Height="{x:Bind rect1.Width}" ... />- <Rectangle Width="200" Height="{Binding Width, RelativeSource={RelativeSource Self}}" ... /> |
Za pomocą elementu {x:Bind}nadaj elementowi nazwę i użyj jej nazwy w ścieżce. |
| Źródło względne: SzablondParent | Nie jest wymagane dla elementu {x:Bind} - {Binding <path>, RelativeSource={RelativeSource TemplatedParent}} |
W elemencie TargetType elementu {x:Bind} w elemencie ControlTemplate wskazuje powiązanie z elementem nadrzędnym szablonu. W przypadku szablonu {Binding} w szablonach kontrolek w większości zastosowań można używać zwykłego powiązania szablonu. Jednak użyj szablonu SzablondParent, w którym musisz użyć konwertera lub powiązania dwukierunkowego.< |
| Źródło | Nie jest wymagane dla elementu {x:Bind} - <ListView ItemsSource="{Binding Orders, Source={StaticResource MyData}}"/> |
W przypadku elementu {x:Bind} można bezpośrednio użyć nazwanego elementu, użyć właściwości lub ścieżki statycznej. |
| Mode | {x:Bind Name, Mode=OneWay}- {Binding Name, Mode=TwoWay} |
Tryb może mieć wartość OneTime, OneWay lub TwoWay. Wartość domyślna elementu {x:Bind} to OneTime; Wartość domyślna elementu {Binding} to OneWay. |
| UpdateSourceTrigger | {x:Bind Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}- {Binding UpdateSourceTrigger=PropertyChanged} |
Element UpdateSourceTrigger może być domyślny, LostFocus lub PropertyChanged. Element {x:Bind} nie obsługuje elementu UpdateSourceTrigger=Explicit. Element {x:Bind} używa zachowania PropertyChanged we wszystkich przypadkach z wyjątkiem TextBox.Text, gdzie używa zachowania LostFocus. |