Översikt över Windows-databindning

Med databindning i WinUI-appar kan du effektivt ansluta kontroller till datakällor. Lär dig hur du binder en kontroll till ett enskilt objekt eller en samling objekt, kontrollerar objektrendering, implementerar informationsvyer och formaterar data för visning. Mer information finns i Databindning på djupet.

Förutsättningar

Det här avsnittet förutsätter att du vet hur du skapar en grundläggande WinUI-app med Windows App SDK. Anvisningar om hur du skapar din första WinUI-app finns i Skapa en WinUI-app.

Skapa projektet

Skapa ett nytt tomt WinUI-program, paketerat C#-projekt. Ge den namnet "Snabbstart".

Binda till ett enskilt objekt

Varje bindning består av ett bindningsmål och en bindningskälla. Vanligtvis är målet en egenskap för en kontroll eller ett annat gränssnittselement, och källan är en egenskap för en klassinstans (en datamodell eller en vymodell). Det här exemplet visar hur du binder en kontroll till ett enda objekt. Målet är att rikta in sig på egenskapen Text hos en TextBlock. Källan är en instans av en enkel klass med namnet Recording som representerar en ljudinspelning. Låt oss titta på klassen först.

Lägg till en ny klass i projektet och ge klassen namnet 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; } }
    }
}

Exponera sedan bindningskällklassen från klassen som representerar ditt markeringsfönster. Lägg till en egenskap av typen RecordingViewModeli MainWindow.xaml.cs.

namespace Quickstart
{
    public sealed partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.InitializeComponent();
        }
        public RecordingViewModel ViewModel{ get; } = new RecordingViewModel();
    }
}

Den sista delen är att binda en TextBlock till egenskapen ViewModel.DefaultRecording.OneLineSummary.

<Window x:Class="Quickstart.MainWindow" ... >
    <Grid>
        <TextBlock Text="{x:Bind ViewModel.DefaultRecording.OneLineSummary}"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"/>
    </Grid>
</Window>

Här är resultatet.

Skärmbild av en WinUI-app som visar en TextBlock som är bunden till ett enskilt objekt.

Binda till en samling objekt

Ett vanligt scenario är att binda till en samling affärsobjekt. I C# använder du den generiska klassen ObservableCollection<T> för databindning. Den implementerar gränssnittet INotifyCollectionChanged , som tillhandahåller ändringsmeddelanden till bindningar när objekt läggs till eller tas bort. Men på grund av ett känt fel i WinUI release-läge med .NET 8 och senare kan du behöva använda en Lista<T> i vissa scenarier, särskilt om samlingen är statisk och inte ändras efter initialisering. Om användargränssnittet behöver uppdateras när samlingen ändras vid körning använder du ObservableCollection<T>. Om du bara behöver visa en fast uppsättning objekt List<T> räcker det. Om du vill att dina bundna kontroller ska uppdateras med ändringar i egenskaperna för objekt i samlingen bör dessa objekt dessutom implementera INotifyPropertyChanged. Mer information finns i Databindning på djupet.

Anteckning

Med hjälp av List<T>får du kanske inte ändringsmeddelanden för samlingsändringar. Om du behöver svara på ändringar bör du överväga att använda ObservableCollection<T>. I det här exemplet behöver du inte svara på samlingsändringar, så List<T> det räcker.

I följande exempel binder en ListView till en samling Recording objekt. Lägg först till samlingen i vymodellen. Lägg till dessa nya medlemmar i RecordingViewModel klassen.

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) });
    }
}

Bind sedan en ListView till egenskapen ViewModel.Recordings .

<Window x:Class="Quickstart.MainWindow" ... >
    <Grid>
        <ListView ItemsSource="{x:Bind ViewModel.Recordings}"
                  HorizontalAlignment="Center"
                  VerticalAlignment="Center"/>
    </Grid>
</Window>

Du har ännu inte angett någon datamall för Recording klassen, så det bästa UI-ramverket kan göra är att anropa ToString för varje objekt i ListView. Standardimplementeringen av ToString returnerar typnamnet.

Att binda en listvy 1

För att åtgärda det här problemet kan du antingen åsidosätta ToString för att returnera värdet OneLineSummaryför , eller så kan du ange en datamall. Alternativet datamall är en vanligare och mer flexibel lösning. Du anger en datamall med hjälp av egenskapen ContentTemplate för en innehållskontroll eller egenskapen ItemTemplate för en objektkontroll. Här är två sätt att utforma en datamall för Recording tillsammans med en bild av resultatet.

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

Binda en listvy 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>

Koppla en listvy 3

Mer information om XAML-syntax finns i Skapa ett användargränssnitt med XAML-. Mer information om kontrolllayout finns i Definiera layouter med XAML-.

Lägga till en informationsvy

Du kan välja att visa all information om Recording objekt i ListView objekt. Men det tillvägagångssättet tar upp mycket utrymme. I stället kan du visa tillräckligt med data i objektet för att identifiera det. När användaren gör en markering kan du visa all information om det markerade objektet i ett separat användargränssnitt som kallas för informationsvyn. Det här arrangemanget kallas även för en huvud-/detaljvy eller en list-/detaljvy.

Du kan implementera det här arrangemanget på två sätt. Du kan binda informationsvyn till egenskapen SelectedItem för ListView-. Eller så kan du använda en CollectionViewSource. I det här fallet binder du både ListView och detaljvyn till CollectionViewSource. Den här metoden tar hand om det markerade objektet åt dig. Båda teknikerna visas i följande avsnitt och båda ger samma resultat (visas i bilden).

Anteckning

Hittills i det här avsnittet har du bara använt markeringstillägget {x:Bind}. Men båda teknikerna som visas i följande avsnitt kräver det mer flexibla (men mindre högpresterande) {Binding}-markeringstillägget.

Först, här är tekniken SelectedItem. För ett C#-program är den enda ändring som krävs i märkningen.

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

För tekniken CollectionViewSource lägger du först till en CollectionViewSource som en resurs på den översta nivån Grid.

<Grid.Resources>
    <CollectionViewSource x:Name="RecordingsCollection" Source="{x:Bind ViewModel.Recordings}"/>
</Grid.Resources>

Anteckning

Klassen Window i WinUI har ingen egenskap för Resources. Du kan lägga till CollectionViewSource till det översta Grid -elementet (eller något annat överordnat gränssnittselement som StackPanel) i stället. Om du arbetar inom en Pagekan du lägga till CollectionViewSource i Page.Resources.

Justera sedan bindningarna i ListView (som inte längre behöver namnges) och på informationsvyn för att använda CollectionViewSource. Genom att binda informationsvyn direkt till CollectionViewSourceantyder du att du vill binda till det aktuella objektet i bindningar där sökvägen inte kan hittas i själva samlingen. Du behöver inte ange egenskapen CurrentItem som sökväg för bindningen, även om du kan göra det om det finns tvetydigheter.

...
<ListView ItemsSource="{Binding Source={StaticResource RecordingsCollection}}">
...
<StackPanel DataContext="{Binding Source={StaticResource RecordingsCollection}}" ...>
...

Och här är det identiska resultatet i varje fall.

Binda en listvy 4

Formatera eller konvertera datavärden för visning

Återgivningen ovan har ett problem. Egenskapen ReleaseDateTime är inte bara ett datum, det är en DateTime. Därför visas den med mer precision än du behöver. En lösning är att lägga till en strängegenskap i klassen Recording som returnerar motsvarigheten till ReleaseDateTime.ToString("d"). Att namnge egenskapen ReleaseDate anger att den returnerar ett datum och inte ett datum och en tid. Att namnge den ReleaseDateAsString ytterligare anger att den returnerar en sträng.

En mer flexibel lösning är att använda en värdekonverterare. Här är ett exempel på hur du skapar en egen värdekonverterare. Lägg till följande kod i din Recording.cs källkodsfil.

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();
    }
}

Nu kan du lägga till en instans av StringFormatter som en resurs och använda den i bindningen av TextBlock som visar egenskapen ReleaseDateTime.

<Grid.Resources>
    ...
    <local:StringFormatter x:Key="StringFormatterValueConverter"/>
</Grid.Resources>
...
<TextBlock Text="{Binding ReleaseDateTime,
    Converter={StaticResource StringFormatterValueConverter},
    ConverterParameter=Released: \{0:d\}}"/>
...

Som du ser skickar markeringen för formateringsflexitet en formatsträng till konverteraren med hjälp av konverterarens parameter. I kodexemplet som visas i det här avsnittet använder C#-värdekonverteraren den parametern.

Här är resultatet.

visar ett datum med anpassad formatering

Skillnader mellan Bindning och "x:Bind"

När du arbetar med databindning i WinUI-appar kan du stöta på två primära bindningsmekanismer: Binding och x:Bind. Båda tjänar syftet med att ansluta gränssnittselement till datakällor, men de har distinkta skillnader:

  • x:Bind: Erbjuder kompileringstidskontroll, bättre prestanda och är starkt skrivet. Det är idealiskt för scenarier där du känner till datastrukturen vid kompileringstiden.
  • Binding: Ger körningsutvärdering och är mer flexibelt för dynamiska scenarier, till exempel när datastrukturen inte är känd vid kompileringstillfället.

Scenarier som inte stöds av x:Bind

Även om x:Bind det är kraftfullt kan du inte använda det i vissa scenarier:

  • Dynamiska datastrukturer: Om datastrukturen inte är känd vid kompileringstillfället kan du inte använda x:Bind.
  • Bindning mellan element: x:Bind stöder inte bindning direkt mellan två gränssnittselement.
  • Bindning till DataContext:x:Bind ärver inte automatiskt ett DataContext-element som är överordnat.
  • Dubbelriktade bindningar med Mode=TwoWay: När det stöds x:Bind kräver explicit implementering av INotifyPropertyChanged för alla egenskaper som du vill att användargränssnittet ska uppdatera när källan ändras, oavsett om du använder enkelriktad eller dubbelriktad bindning. Den viktigaste skillnaden med dubbelriktade bindningar är att ändringar också flödar från användargränssnittet tillbaka till källan.

Praktiska exempel och en djupare förståelse för när var och en ska användas finns i följande avsnitt: