Udostępnij przez


Omówienie powiązania danych systemu Windows

Powiązanie danych w aplikacjach WinUI 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 zestawu SDK aplikacji systemu Windows. Aby uzyskać instrukcje dotyczące tworzenia pierwszej aplikacji WinUI, zobacz Tworzenie aplikacji WinUI.

Tworzenie projektu

Utwórz nową pustą aplikację WinUI, spakowany projekt C#. 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.

Zrzut ekranu aplikacji WinUI przedstawiający element TextBlock powiązany z jednym elementem.

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 znaną usterkę trybu Release WinUI z .NET 8 lub nowszymi 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ć czas zmiany kolekcji 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.

wiążenie widoku listy 1

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>

powiązywanie widoku listy 2

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

wiązanie widoku listy 3

Aby uzyskać więcej informacji na temat składni XAML, zobacz Create a UI with XAML(Tworzenie interfejsu użytkownika przy użyciu języka XAML). Aby uzyskać więcej informacji na temat układu kontrolki, zobacz Define layouts with XAML(Definiowanie układów za pomocą języka XAML).

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.

wiązanie widoku listy 4

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.

wyświetlanie daty z niestandardowym formatowaniem

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:Bind nie obsługuje powiązania bezpośrednio między dwoma elementami interfejsu użytkownika.
  • Powiązanie z DataContext: x:Bind nie automatycznie dziedziczy DataContext elementu nadrzędnego.
  • Powiązania dwukierunkowe z elementem Mode=TwoWay: Chociaż są obsługiwane, x:Bind wymaga jawnego zaimplementowania INotifyPropertyChanged dla 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: