Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Powiązanie danych w aplikacjach WinUI 3 umożliwia efektywne łączenie kontrolek ze źródłami danych. Dowiedz się, jak powiązać kontrolkę z pojedynczym elementem lub kolekcją elementów, renderowanie elementów, implementacja widoków szczegółowych i formatowanie danych do wyświetlania. Aby uzyskać więcej informacji, zobacz Szczegółowe powiązanie danych.
Warunki wstępne
W tym temacie założono, że wiesz, jak utworzyć podstawową aplikację WinUI przy użyciu Zestaw SDK do aplikacji systemu Windows. Aby uzyskać instrukcje dotyczące konfiguracji, zobacz Szybki start: tworzenie i uruchamianie projektu WinUI 3. Nie musisz ukończyć samouczka aplikacji Notatki.
Tworzenie projektu
Utwórz nowy projekt C# pusta aplikacja WinUI (w pakiecie). Nadaj mu nazwę "Szybki start".
Wiązanie z pojedynczym elementem
Każde powiązanie składa się z elementu docelowego powiązania i źródła powiązania. Zazwyczaj elementem docelowym jest właściwość kontrolki lub innego elementu interfejsu użytkownika, a źródłem jest właściwość wystąpienia klasy (model danych lub model widoku). W tym przykładzie pokazano, jak powiązać kontrolkę z jednym elementem. Obiektem docelowym jest właściwość Text należąca do TextBlock. Źródłem jest wystąpienie prostej klasy o nazwie Recording, która reprezentuje nagranie audio. Przyjrzyjmy się najpierw klasie.
Dodaj nową klasę do projektu i nadaj jej nazwę Recording.
namespace Quickstart
{
public class Recording
{
public string ArtistName { get; set; }
public string CompositionName { get; set; }
public DateTime ReleaseDateTime { get; set; }
public Recording()
{
ArtistName = "Wolfgang Amadeus Mozart";
CompositionName = "Andante in C for Piano";
ReleaseDateTime = new DateTime(1761, 1, 1);
}
public string OneLineSummary
{
get
{
return $"{CompositionName} by {ArtistName}, released: "
+ ReleaseDateTime.ToString("d");
}
}
}
public class RecordingViewModel
{
private Recording defaultRecording = new();
public Recording DefaultRecording { get { return defaultRecording; } }
}
}
Następnie udostępnij klasę źródła powiązania z klasy, która reprezentuje twoje okno znaczników. Dodaj właściwość typu RecordingViewModel do MainWindow.xaml.cs.
namespace Quickstart
{
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
public RecordingViewModel ViewModel{ get; } = new RecordingViewModel();
}
}
Ostatnim elementem jest powiązanie elementu TextBlock z właściwością ViewModel.DefaultRecording.OneLineSummary.
<Window x:Class="Quickstart.MainWindow" ... >
<Grid>
<TextBlock Text="{x:Bind ViewModel.DefaultRecording.OneLineSummary}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</Window>
Oto wynik.
Wiązanie z kolekcją elementów
Typowym scenariuszem jest powiązanie z kolekcją obiektów biznesowych. W języku C# użyj ogólnej klasy ObservableCollection<T> do powiązania danych. Implementuje interfejs INotifyCollectionChanged , który zapewnia powiadomienie o zmianie powiązań po dodaniu lub usunięciu elementów. Jednak ze względu na znany błąd trybu Release WinUI w .NET 8 i nowszych wersjach może być konieczne użycie List<T> w niektórych scenariuszach, zwłaszcza jeśli kolekcja jest statyczna i nie zmienia się po inicjalizacji. Jeśli interfejs użytkownika musi zaktualizować się, gdy kolekcja zmienia się w czasie wykonywania, użyj polecenia ObservableCollection<T>. Jeśli potrzebujesz tylko stałego zestawu elementów, List<T> wystarczy. Ponadto jeśli chcesz, aby powiązane kontrolki były aktualizowane ze zmianami właściwości obiektów w kolekcji, te obiekty powinny implementować element INotifyPropertyChanged. Aby uzyskać więcej informacji, zobacz Szczegółowe powiązanie danych.
Notatka
Za pomocą polecenia List<T>możesz nie otrzymywać powiadomień o zmianie w przypadku zmian kolekcji. Jeśli musisz odpowiedzieć na zmiany, rozważ użycie polecenia ObservableCollection<T>. W tym przykładzie nie trzeba odpowiadać na zmiany kolekcji, więc List<T> wystarczy.
W poniższym przykładzie element ListView jest powiązany z kolekcją Recording obiektów. Najpierw dodaj kolekcję do modelu wyświetlania. Dodaj te nowe elementy członkowskie do klasy RecordingViewModel.
public class RecordingViewModel
{
...
private List<Recording> recordings = new();
public List<Recording> Recordings{ get{ return recordings; } }
public RecordingViewModel()
{
recordings.Add(new Recording(){ ArtistName = "Johann Sebastian Bach",
CompositionName = "Mass in B minor", ReleaseDateTime = new DateTime(1748, 7, 8) });
recordings.Add(new Recording(){ ArtistName = "Ludwig van Beethoven",
CompositionName = "Third Symphony", ReleaseDateTime = new DateTime(1805, 2, 11) });
recordings.Add(new Recording(){ ArtistName = "George Frideric Handel",
CompositionName = "Serse", ReleaseDateTime = new DateTime(1737, 12, 3) });
}
}
Następnie powiąż element ListView z właściwością ViewModel.Recordings .
<Window x:Class="Quickstart.MainWindow" ... >
<Grid>
<ListView ItemsSource="{x:Bind ViewModel.Recordings}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</Window>
Nie podano jeszcze szablonu danych dla Recording klasy, więc najlepszą strukturą interfejsu użytkownika może być wywołanie metody ToString dla każdego elementu w klasie ListView. Domyślna implementacja ToString zwraca nazwę typu.
Aby rozwiązać ten problem, możesz zastąpić ciąg ToString , aby zwrócić wartość OneLineSummary, lub podać szablon danych. Opcja szablonu danych jest bardziej typowym i elastycznym rozwiązaniem. Szablon danych określa się przy użyciu właściwości ContentTemplate kontrolki zawartości lub właściwości ItemTemplate kontrolki elementów. Oto dwa sposoby projektowania szablonu danych dla Recording oraz ilustrację wyniku.
<ListView ItemsSource="{x:Bind ViewModel.Recordings}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Recording">
<TextBlock Text="{x:Bind OneLineSummary}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView ItemsSource="{x:Bind ViewModel.Recordings}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Recording">
<StackPanel Orientation="Horizontal" Margin="6">
<SymbolIcon Symbol="Audio" Margin="0,0,12,0"/>
<StackPanel>
<TextBlock Text="{x:Bind ArtistName}" FontWeight="Bold"/>
<TextBlock Text="{x:Bind CompositionName}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Aby uzyskać więcej informacji na temat składni XAML, zobacz
Dodawanie widoku szczegółów
Możesz wyświetlić wszystkie szczegóły obiektów Recording w elementach ListView. Ale takie podejście zajmuje dużo miejsca. Zamiast tego możesz wyświetlić wystarczająco dużo danych w elemencie, aby je zidentyfikować. Gdy użytkownik dokona wyboru, możesz wyświetlić wszystkie szczegóły wybranego elementu w osobnym interfejsie użytkownika znanym jako widok szczegółów. Ten układ jest również znany jako widok wzorca/szczegółów lub widoku listy/szczegółów.
Ten układ można zaimplementować na dwa sposoby. Można powiązać widok szczegółów z właściwością SelectedItem obiektu ListView. Możesz też użyć elementu CollectionViewSource. W takim przypadku powiążesz zarówno ListView, jak i widok szczegółów z elementem CollectionViewSource. To podejście zajmuje się aktualnie wybranym elementem. Obie techniki są pokazane w poniższych sekcjach i dają te same wyniki (pokazane na ilustracji).
Notatka
Do tej pory w tym temacie użyto tylko rozszerzenia znaczników {x:Bind}. Obie techniki przedstawione w poniższych sekcjach wymagają bardziej elastycznego (ale mniej wydajnego) rozszerzenia znaczników {Binding}.
Najpierw poniżej przedstawiono technikę SelectedItem. W przypadku aplikacji C# jedyną konieczną zmianą jest zmiana w mark-upie.
<Window x:Class="Quickstart.MainWindow" ... >
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView x:Name="recordingsListView" ItemsSource="{x:Bind ViewModel.Recordings}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Recording">
<StackPanel Orientation="Horizontal" Margin="6">
<SymbolIcon Symbol="Audio" Margin="0,0,12,0"/>
<StackPanel>
<TextBlock Text="{x:Bind CompositionName}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel DataContext="{Binding SelectedItem, ElementName=recordingsListView}"
Margin="0,24,0,0">
<TextBlock Text="{Binding ArtistName}"/>
<TextBlock Text="{Binding CompositionName}"/>
<TextBlock Text="{Binding ReleaseDateTime}"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
W przypadku techniki CollectionViewSource najpierw dodaj CollectionViewSource jako zasób najwyższego poziomu Grid.
<Grid.Resources>
<CollectionViewSource x:Name="RecordingsCollection" Source="{x:Bind ViewModel.Recordings}"/>
</Grid.Resources>
Notatka
Klasa Window w interfejsie WinUI nie ma właściwości Resources. Zamiast tego możesz dodać CollectionViewSource do najwyższego poziomu elementu Grid (lub innego nadrzędnego elementu interfejsu użytkownika, takiego jak StackPanel). Jeśli pracujesz w Page, możesz dodać CollectionViewSource do Page.Resources.
Następnie dostosuj powiązania w widoku ListView (który nie musi już być nazwany) i w widoku szczegółów, aby użyć elementu CollectionViewSource. Powiązanie widoku szczegółów bezpośrednio z elementem CollectionViewSourceoznacza, że chcesz powiązać z bieżącym elementem w powiązaniach, w których nie można odnaleźć ścieżki w samej kolekcji. Nie ma potrzeby określania właściwości CurrentItem jako ścieżki dla powiązania, chociaż można to zrobić, jeśli istnieje jakakolwiek niejednoznaczność.
...
<ListView ItemsSource="{Binding Source={StaticResource RecordingsCollection}}">
...
<StackPanel DataContext="{Binding Source={StaticResource RecordingsCollection}}" ...>
...
I oto identyczny wynik w każdym przypadku.
Formatowanie lub konwertowanie wartości danych na potrzeby wyświetlania
Powyższe renderowanie ma problem. Właściwość ReleaseDateTime nie jest tylko datą; to jest DateTime. Dlatego jest wyświetlana z większą dokładnością niż potrzebujesz. Jednym z rozwiązań jest dodanie właściwości string do klasy Recording, która zwraca odpowiednik ReleaseDateTime.ToString("d"). Nazewnictwo tej właściwości ReleaseDate wskazuje, że zwraca datę, a nie datę i godzinę. Nazewnictwo ReleaseDateAsString dalej wskazuje, że zwraca ciąg.
Bardziej elastycznym rozwiązaniem jest użycie konwertera wartości. Oto przykład tworzenia własnego konwertera wartości. Dodaj następujący kod do pliku kodu źródłowego Recording.cs .
public class StringFormatter : Microsoft.UI.Xaml.Data.IValueConverter
{
// This converts the value object to the string to display.
// This will work with most simple types.
public object Convert(object value, Type targetType,
object parameter, string language)
{
// Retrieve the format string and use it to format the value.
string formatString = parameter as string;
if (!string.IsNullOrEmpty(formatString))
{
return string.Format(formatString, value);
}
// If the format string is null or empty, simply
// call ToString() on the value.
return value.ToString();
}
// No need to implement converting back on a one-way binding
public object ConvertBack(object value, Type targetType,
object parameter, string language)
{
throw new NotImplementedException();
}
}
Teraz możesz dodać wystąpienie StringFormatter jako zasób i użyć go w powiązaniu TextBlock, które wyświetla właściwość ReleaseDateTime.
<Grid.Resources>
...
<local:StringFormatter x:Key="StringFormatterValueConverter"/>
</Grid.Resources>
...
<TextBlock Text="{Binding ReleaseDateTime,
Converter={StaticResource StringFormatterValueConverter},
ConverterParameter=Released: \{0:d\}}"/>
...
Jak widać, w celu zapewnienia elastyczności formatowania znacznik przekazuje ciąg formatu do konwertera za pomocą parametru konwertera. W przykładzie kodu pokazanym w tym temacie konwerter wartości języka C# korzysta z tego parametru.
Oto wynik.
Różnice między wiązaniem danych a x:Bind
Podczas pracy z powiązaniem danych w aplikacjach WinUI mogą wystąpić dwa podstawowe mechanizmy powiązań: Binding i x:Bind. Oba te elementy służą do łączenia elementów interfejsu użytkownika ze źródłami danych, ale mają różne różnice:
-
x:Bind: oferuje sprawdzanie w czasie kompilacji, lepszą wydajność i jest silnie typowany. Jest to idealne rozwiązanie w scenariuszach, w których znasz strukturę danych w czasie kompilacji. -
Binding: zapewnia ocenę środowiska uruchomieniowego i jest bardziej elastyczny w scenariuszach dynamicznych, takich jak wtedy, gdy struktura danych nie jest znana w czasie kompilacji.
Scenariusze nieobsługiwane przez x:Bind
Chociaż x:Bind jest zaawansowany, nie można go używać w niektórych scenariuszach:
-
Dynamiczne struktury danych: jeśli struktura danych nie jest znana w czasie kompilacji, nie można użyć elementu
x:Bind. -
Powiązanie między elementami:
x:Bindnie obsługuje powiązania bezpośrednio między dwoma elementami interfejsu użytkownika. -
Powiązanie z
DataContext:x:Bindnie automatycznie dziedziczyDataContextelementu nadrzędnego. -
Powiązania dwukierunkowe z elementem
Mode=TwoWay: Chociaż są obsługiwane,x:Bindwymaga jawnego zaimplementowaniaINotifyPropertyChangeddla każdej właściwości, którą interfejs użytkownika ma aktualizować, gdy źródło ulega zmianie, niezależnie od tego, czy używane jest powiązanie jednokierunkowe, czy dwukierunkowe. Kluczową różnicą w powiązaniach dwukierunkowych jest to, że zmiany również przepływają z interfejsu użytkownika z powrotem do źródła.
Aby zapoznać się z praktycznymi przykładami i dokładniej zrozumieć, kiedy z nich korzystać, zapoznaj się z następującymi tematami: