Sdílet prostřednictvím


Velící

Browse sample. Procházení ukázky

V aplikaci .NET Multi-Platform App UI (.NET MAUI), která používá model-View-ViewModel (MVVM), jsou datové vazby definovány mezi vlastnostmi v modelu viewmodel, což je obvykle třída odvozená od INotifyPropertyChangeda vlastnosti v zobrazení, což je obvykle soubor XAML. Někdy aplikace potřebuje, aby přesahovala tyto vazby vlastností tím, že vyžaduje, aby uživatel inicioval příkazy, které mají vliv na něco v modelu viewmodel. Tyto příkazy jsou obecně signalizovány kliknutím na tlačítko nebo prstem klepněte a tradičně se zpracovávají v souboru kódu v obslužné rutině pro Clicked událost události Button nebo Tapped události TapGestureRecognizerudálosti .

Příkazové rozhraní poskytuje alternativní přístup k implementaci příkazů, které jsou pro architekturu MVVM mnohem vhodnější. Model viewmodel může obsahovat příkazy, které jsou metody spouštěné v reakci na konkrétní aktivitu v zobrazení, například Button kliknutí. Datové vazby jsou definovány mezi těmito příkazy a Button.

Chcete-li povolit datovou vazbu mezi modelem Button a modelem viewmodel, Button definuje dvě vlastnosti:

Chcete-li použít příkazové rozhraní, definujete datovou vazbu, která cílí na Command vlastnost Button , kde zdroj je vlastnost v modelu viewmodel typu ICommand. Model viewmodel obsahuje kód přidružený k této ICommand vlastnosti, která se spustí při kliknutí na tlačítko. Vlastnost můžete nastavit CommandParameter na libovolná data, abyste mohli rozlišovat mezi více tlačítky, pokud jsou všechny vázány na stejnou ICommand vlastnost v modelu viewmodel.

Mnoho dalších zobrazení také definuje Command a CommandParameter vlastnosti. Všechny tyto příkazy je možné zpracovat v modelu viewmodel pomocí přístupu, který nezávisí na objektu uživatelského rozhraní v zobrazení.

ICommands

Rozhraní ICommand je definováno v oboru názvů System.Windows.Input a skládá se ze dvou metod a jedné události:

public interface ICommand
{
    public void Execute (Object parameter);
    public bool CanExecute (Object parameter);
    public event EventHandler CanExecuteChanged;
}

Chcete-li použít příkazové rozhraní, model viewmodel by měl obsahovat vlastnosti typu ICommand:

public ICommand MyCommand { private set; get; }

Model viewmodel musí také odkazovat na třídu, která implementuje ICommand rozhraní. V zobrazení Command je vlastnost Button vlastnosti vázána na tuto vlastnost:

<Button Text="Execute command"
        Command="{Binding MyCommand}" />

Když uživatel stiskne Button, Button volá metodu Execute v objektu ICommand vázaném na jeho Command vlastnost.

Když je vazba poprvé definována na Command vlastnosti , a když se datová vazba nějakým způsobem změní, Button volá metodu CanExecute v objektu ICommand Button. Pokud CanExecute se vrátí false, pak Button se zakáže sám. To znamená, že konkrétní příkaz je momentálně nedostupný nebo neplatný.

Také Button připojí obslužnou rutinu k CanExecuteChanged události ICommand. Událost je vyvolána z modelu viewmodel. Při vyvolání této události se Button volání CanExecute znovu zobrazí. Umožňuje Button sám sebe, pokud CanExecute vrátí true a zakáže sám sebe, pokud CanExecute vrátí false.

Upozorňující

IsEnabled Nepoužívejte vlastnostButton, pokud používáte příkazové rozhraní.

Když váš model viewmodel definuje vlastnost typu ICommand, viewmodel musí také obsahovat nebo odkazovat na třídu, která implementuje ICommand rozhraní. Tato třída musí obsahovat nebo odkazovat na Execute metody a CanExecute aktivovat CanExecuteChanged událost vždy, když CanExecute metoda může vrátit jinou hodnotu. K implementaci ICommand rozhraní můžete použít Command rozhraní nebo Command<T> třídu, která je součástí rozhraní .NET MAUI. Tyto třídy umožňují určit těla Execute a CanExecute metody v konstruktorech tříd.

Tip

Používáte-li Command<T> CommandParameter vlastnost k rozlišení mezi více zobrazeními vázanými na stejnou ICommand vlastnost, a Command třídu, pokud to není požadavek.

Základní příkazování

Následující příklady ukazují základní příkazy implementované v modelu viewmodel.

Třída PersonViewModel definuje tři vlastnosti s názvem Name, Agea Skills které definují osobu:

public class PersonViewModel : INotifyPropertyChanged
{
    string name;
    double age;
    string skills;

    public event PropertyChangedEventHandler PropertyChanged;

    public string Name
    {
        set { SetProperty(ref name, value); }
        get { return name; }
    }

    public double Age
    {
        set { SetProperty(ref age, value); }
        get { return age; }
    }

    public string Skills
    {
        set { SetProperty(ref skills, value); }
        get { return skills; }
    }

    public override string ToString()
    {
        return Name + ", age " + Age;
    }

    bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (Object.Equals(storage, value))
            return false;

        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Níže PersonCollectionViewModel uvedená třída vytvoří nové objekty typu PersonViewModel a umožní uživateli vyplnit data. Pro tento účel třída definuje IsEditing, typ boola PersonEdit, typ PersonViewModel, vlastnosti. Třída navíc definuje tři vlastnosti typu ICommand a vlastnost s názvem Persons typu IList<PersonViewModel>:

public class PersonCollectionViewModel : INotifyPropertyChanged
{
    PersonViewModel personEdit;
    bool isEditing;

    public event PropertyChangedEventHandler PropertyChanged;
    ···

    public bool IsEditing
    {
        private set { SetProperty(ref isEditing, value); }
        get { return isEditing; }
    }

    public PersonViewModel PersonEdit
    {
        set { SetProperty(ref personEdit, value); }
        get { return personEdit; }
    }

    public ICommand NewCommand { private set; get; }
    public ICommand SubmitCommand { private set; get; }
    public ICommand CancelCommand { private set; get; }

    public IList<PersonViewModel> Persons { get; } = new ObservableCollection<PersonViewModel>();

    bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (Object.Equals(storage, value))
            return false;

        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

V tomto příkladu se změní na tři ICommand vlastnosti a Persons vlastnost nemá za následek PropertyChanged vyvolání událostí. Tyto vlastnosti jsou nastaveny při prvním vytvoření třídy a nemění se.

Následující příklad ukazuje XAML, který využívá PersonCollectionViewModel:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.PersonEntryPage"
             Title="Person Entry">
    <ContentPage.BindingContext>
        <local:PersonCollectionViewModel />
    </ContentPage.BindingContext>
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <!-- New Button -->
        <Button Text="New"
                Grid.Row="0"
                Command="{Binding NewCommand}"
                HorizontalOptions="Start" />

        <!-- Entry Form -->
        <Grid Grid.Row="1"
              IsEnabled="{Binding IsEditing}">
            <Grid BindingContext="{Binding PersonEdit}">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>

                <Label Text="Name: " Grid.Row="0" Grid.Column="0" />
                <Entry Text="{Binding Name}"
                       Grid.Row="0" Grid.Column="1" />
                <Label Text="Age: " Grid.Row="1" Grid.Column="0" />
                <StackLayout Orientation="Horizontal"
                             Grid.Row="1" Grid.Column="1">
                    <Stepper Value="{Binding Age}"
                             Maximum="100" />
                    <Label Text="{Binding Age, StringFormat='{0} years old'}"
                           VerticalOptions="Center" />
                </StackLayout>
                <Label Text="Skills: " Grid.Row="2" Grid.Column="0" />
                <Entry Text="{Binding Skills}"
                       Grid.Row="2" Grid.Column="1" />
            </Grid>
        </Grid>

        <!-- Submit and Cancel Buttons -->
        <Grid Grid.Row="2">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <Button Text="Submit"
                    Grid.Column="0"
                    Command="{Binding SubmitCommand}"
                    VerticalOptions="Center" />
            <Button Text="Cancel"
                    Grid.Column="1"
                    Command="{Binding CancelCommand}"
                    VerticalOptions="Center" />
        </Grid>

        <!-- List of Persons -->
        <ListView Grid.Row="3"
                  ItemsSource="{Binding Persons}" />
    </Grid>
</ContentPage>

V tomto příkladu je vlastnost stránky BindingContext nastavena na PersonCollectionViewModelhodnotu . Obsahuje Grid s textem Button New s jeho Command vlastností vázanou na NewCommand vlastnost v modelu viewmodel, vstupní formulář s vlastnostmi vázanými na IsEditing vlastnost, stejně jako vlastnosti PersonViewModel, a dva další tlačítka svázané s SubmitCommand a CancelCommand vlastnosti modelu viewmodel. Zobrazí ListView kolekci osob, které již byly zadány:

Následující snímek obrazovky ukazuje tlačítko Odeslat povolené po nastavení věku:

Person Entry.

Když uživatel poprvé stiskne tlačítko Nový , povolí se formulář pro zadání, ale zakáže se tlačítko Nový . Uživatel pak zadá jméno, věk a dovednosti. Kdykoli během úprav může uživatel stisknout tlačítko Storno a začít znovu. Je-li zadáno jméno a platný věk, je tlačítko Odeslat povoleno. Stisknutím tohoto tlačítka Odeslat přenesete osobu do kolekce zobrazené pomocí ListViewtlačítka . Po stisknutí tlačítka Zrušit nebo Odeslat se formulář pro zadání vymaže a tlačítko Nový se znovu povolí.

Veškerá logika tlačítek Nový, Odeslat a Zrušit se zpracovává PersonCollectionViewModel prostřednictvím definic , NewCommandSubmitCommanda CancelCommand vlastností. Konstruktor těchto PersonCollectionViewModel tří vlastností nastaví na objekty typu Command.

Konstruktor Command třídy umožňuje předat argumenty typu Action a Func<bool> odpovídající metodámExecute.CanExecute Tuto akci a funkci lze v konstruktoru Command definovat jako funkce lambda:

public class PersonCollectionViewModel : INotifyPropertyChanged
{
    ···
    public PersonCollectionViewModel()
    {
        NewCommand = new Command(
            execute: () =>
            {
                PersonEdit = new PersonViewModel();
                PersonEdit.PropertyChanged += OnPersonEditPropertyChanged;
                IsEditing = true;
                RefreshCanExecutes();
            },
            canExecute: () =>
            {
                return !IsEditing;
            });
        ···
    }

    void OnPersonEditPropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        (SubmitCommand as Command).ChangeCanExecute();
    }

    void RefreshCanExecutes()
    {
        (NewCommand as Command).ChangeCanExecute();
        (SubmitCommand as Command).ChangeCanExecute();
        (CancelCommand as Command).ChangeCanExecute();
    }
    ···
}

Když uživatel klikne na tlačítko Nový , execute funkce předaná konstruktoru Command se spustí. Tím se vytvoří nový PersonViewModel objekt, nastaví obslužnou rutinu pro událost tohoto objektu PropertyChanged , nastaví IsEditing na truea zavolá metodu RefreshCanExecutes definovanou za konstruktorem.

Kromě implementace ICommand rozhraní třída Command také definuje metodu s názvem ChangeCanExecute. Model viewmodel by měl volat ChangeCanExecute vlastnost ICommand vždy, když se stane cokoli, co by mohlo změnit návratovou CanExecute hodnotu metody. Volání ChangeCanExecute způsobí, že Command třída aktivuje metodu CanExecuteChanged . Připojil Button obslužnou rutinu pro tuto událost a reaguje opětovným voláním CanExecute a pak se povolí na základě návratové hodnoty této metody.

execute Když metoda NewCommand volání RefreshCanExecutes, NewCommand vlastnost získá volání ChangeCanExecute, a Button volání canExecute metody, která nyní vrátífalse, protože IsEditing vlastnost je nyní true.

Obslužná rutina PropertyChanged nového objektu volá metodu ChangeCanExecute SubmitCommand:PersonViewModel

public class PersonCollectionViewModel : INotifyPropertyChanged
{
    ···
    public PersonCollectionViewModel()
    {
        ···
        SubmitCommand = new Command(
            execute: () =>
            {
                Persons.Add(PersonEdit);
                PersonEdit.PropertyChanged -= OnPersonEditPropertyChanged;
                PersonEdit = null;
                IsEditing = false;
                RefreshCanExecutes();
            },
            canExecute: () =>
            {
                return PersonEdit != null &&
                       PersonEdit.Name != null &&
                       PersonEdit.Name.Length > 1 &&
                       PersonEdit.Age > 0;
            });
        ···
    }
    ···
}

Funkce canExecute se SubmitCommand volá při každé změně vlastnosti objektu, který PersonViewModel se upravuje. Vrátí true pouze v případě, že Name je vlastnost alespoň jeden znak dlouhý a Age je větší než 0. V té době se aktivuje tlačítko Odeslat .

Funkce execute Submit odebere obslužnou rutinu změněnou vlastností z objektu PersonViewModel, přidá objekt do Persons kolekce a vrátí vše do původního stavu.

Funkce execute tlačítka Zrušit dělá vše, co tlačítko Odeslat dělá, s výjimkou přidání objektu do kolekce:

public class PersonCollectionViewModel : INotifyPropertyChanged
{
    ···
    public PersonCollectionViewModel()
    {
        ···
        CancelCommand = new Command(
            execute: () =>
            {
                PersonEdit.PropertyChanged -= OnPersonEditPropertyChanged;
                PersonEdit = null;
                IsEditing = false;
                RefreshCanExecutes();
            },
            canExecute: () =>
            {
                return IsEditing;
            });
    }
    ···
}

Metoda canExecute se vrátí true kdykoliv PersonViewModel , kdy se upravuje.

Poznámka:

Není nutné definovat execute a canExecute metody jako funkce lambda. V modelu viewmodel je můžete napsat jako soukromé metody a odkazovat na ně v Command konstruktorech. Tento přístup ale může vést k mnoha metodám, na které se odkazuje pouze jednou v modelu viewmodel.

Použití parametrů příkazu

Někdy je vhodné použít jedno nebo více tlačítek nebo jiných objektů uživatelského rozhraní ke sdílení stejné ICommand vlastnosti v modelu viewmodel. V tomto případě můžete pomocí CommandParameter vlastnosti rozlišovat mezi tlačítky.

Pro tyto sdílené ICommand vlastnosti můžete i nadále používat Command třídu. Třída definuje alternativní konstruktor, který přijímá execute a canExecute metody s parametry typu Object. CommandParameter Takto se tyto metody předávají. Při zadávání CommandParameter, je nejjednodušší použít generické Command<T> třídy určit typ objektu nastaven na CommandParameter. canExecute Zadané execute metody mají parametry tohoto typu.

Následující příklad ukazuje klávesnici pro zadávání desetinných čísel:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.DecimalKeypadPage"
             Title="Decimal Keyboard">
    <ContentPage.BindingContext>
        <local:DecimalKeypadViewModel />
    </ContentPage.BindingContext>
    <ContentPage.Resources>
        <Style TargetType="Button">
            <Setter Property="FontSize" Value="32" />
            <Setter Property="BorderWidth" Value="1" />
            <Setter Property="BorderColor" Value="Black" />
        </Style>
    </ContentPage.Resources>

    <Grid WidthRequest="240"
          HeightRequest="480"
          ColumnDefinitions="80, 80, 80"
          RowDefinitions="Auto, Auto, Auto, Auto, Auto, Auto"
          ColumnSpacing="2"
          RowSpacing="2"
          HorizontalOptions="Center"
          VerticalOptions="Center">
        <Label Text="{Binding Entry}"
               Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"
               Margin="0,0,10,0"
               FontSize="32"
               LineBreakMode="HeadTruncation"
               VerticalTextAlignment="Center"
               HorizontalTextAlignment="End" />
        <Button Text="CLEAR"
                Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
                Command="{Binding ClearCommand}" />
        <Button Text="&#x21E6;"
                Grid.Row="1" Grid.Column="2"
                Command="{Binding BackspaceCommand}" />
        <Button Text="7"
                Grid.Row="2" Grid.Column="0"
                Command="{Binding DigitCommand}"
                CommandParameter="7" />
        <Button Text="8"
                Grid.Row="2" Grid.Column="1"
                Command="{Binding DigitCommand}"
                CommandParameter="8" />        
        <Button Text="9"
                Grid.Row="2" Grid.Column="2"
                Command="{Binding DigitCommand}"
                CommandParameter="9" />
        <Button Text="4"
                Grid.Row="3" Grid.Column="0"
                Command="{Binding DigitCommand}"
                CommandParameter="4" />
        <Button Text="5"
                Grid.Row="3" Grid.Column="1"
                Command="{Binding DigitCommand}"
                CommandParameter="5" />
        <Button Text="6"
                Grid.Row="3" Grid.Column="2"
                Command="{Binding DigitCommand}"
                CommandParameter="6" />
        <Button Text="1"
                Grid.Row="4" Grid.Column="0"
                Command="{Binding DigitCommand}"
                CommandParameter="1" />
        <Button Text="2"
                Grid.Row="4" Grid.Column="1"
                Command="{Binding DigitCommand}"
                CommandParameter="2" />
        <Button Text="3"
                Grid.Row="4" Grid.Column="2"
                Command="{Binding DigitCommand}"
                CommandParameter="3" />
        <Button Text="0"
                Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2"
                Command="{Binding DigitCommand}"
                CommandParameter="0" />
        <Button Text="&#x00B7;"
                Grid.Row="5" Grid.Column="2"
                Command="{Binding DigitCommand}"
                CommandParameter="." />
    </Grid>
</ContentPage>

V tomto příkladu je stránka BindingContext .DecimalKeypadViewModel Vlastnost Entry tohoto modelu zobrazení je vázána na Text vlastnost objektu Label. Button Všechny objekty jsou vázány na příkazy v modelu viewmodel: ClearCommand, BackspaceCommanda DigitCommand. Tlačítka 11 pro 10 číslic a desetinná čárka sdílejí vazbu s DigitCommand. Rozlišení CommandParameter mezi těmito tlačítky. Hodnota nastavená na CommandParameter hodnotu je obecně stejná jako text zobrazený tlačítkem s výjimkou desetinné čárky, která je pro účely srozumitelnosti zobrazena střední tečkou:

Decimal keyboard.

Definuje DecimalKeypadViewModel vlastnost typu string a tři vlastnosti typuICommand:Entry

public class DecimalKeypadViewModel : INotifyPropertyChanged
{
    string entry = "0";

    public event PropertyChangedEventHandler PropertyChanged;
    ···

    public string Entry
    {
        private set
        {
            if (entry != value)
            {
                entry = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Entry"));
            }
        }
        get
        {
            return entry;
        }
    }

    public ICommand ClearCommand { private set; get; }
    public ICommand BackspaceCommand { private set; get; }
    public ICommand DigitCommand { private set; get; }
}

Tlačítko odpovídající ho ClearCommand je vždy povoleno a nastaví položku zpět na "0":

public class DecimalKeypadViewModel : INotifyPropertyChanged
{
    ···
    public DecimalKeypadViewModel()
    {
        ClearCommand = new Command(
            execute: () =>
            {
                Entry = "0";
                RefreshCanExecutes();
            });
        ···
    }

    void RefreshCanExecutes()
    {
        ((Command)BackspaceCommand).ChangeCanExecute();
        ((Command)DigitCommand).ChangeCanExecute();
    }
    ···
}

Protože tlačítko je vždy povoleno, není nutné zadat canExecute argument v konstruktoru Command .

Tlačítko Backspace je povoleno pouze v případě, že délka položky je větší než 1 nebo pokud Entry není rovna řetězci "0":

public class DecimalKeypadViewModel : INotifyPropertyChanged
{
    ···
    public DecimalKeypadViewModel()
    {
        ···
        BackspaceCommand = new Command(
            execute: () =>
            {
                Entry = Entry.Substring(0, Entry.Length - 1);
                if (Entry == "")
                {
                    Entry = "0";
                }
                RefreshCanExecutes();
            },
            canExecute: () =>
            {
                return Entry.Length > 1 || Entry != "0";
            });
        ···
    }
    ···
}

Logika pro funkci tlačítka execute Backspace zajišťuje, že Entry je alespoň řetězec "0".

Vlastnost DigitCommand je vázána na 11 tlačítek, z nichž každý identifikuje sám se CommandParameter vlastností. Je DigitCommand nastavena na instanci Command<T> třídy. Při použití příkazového rozhraní s XAML CommandParameter jsou vlastnosti obvykle řetězce, což je typ obecného argumentu. canExecute Funkce execute pak mají argumenty typustring:

public class DecimalKeypadViewModel : INotifyPropertyChanged
{
    ···
    public DecimalKeypadViewModel()
    {
        ···
        DigitCommand = new Command<string>(
            execute: (string arg) =>
            {
                Entry += arg;
                if (Entry.StartsWith("0") && !Entry.StartsWith("0."))
                {
                    Entry = Entry.Substring(1);
                }
                RefreshCanExecutes();
            },
            canExecute: (string arg) =>
            {
                return !(arg == "." && Entry.Contains("."));
            });
    }
    ···
}

Metoda execute připojí řetězcový argument k Entry vlastnosti. Pokud ale výsledek začíná nulou (ale ne nulou a desetinnou čárkou), musí být tato počáteční nula odebrána pomocí Substring funkce. Metoda canExecute vrátí false pouze v případě, že argument je desetinná čárka (označuje, že desetinná čárka je stisknuta) a Entry již obsahuje desetinnou čárku. Všechny metody volají execute RefreshCanExecutes, které pak volá ChangeCanExecute obojí DigitCommand a ClearCommand. Tím zajistíte, že jsou tlačítka desetinné čárky a backspace povolená nebo zakázaná na základě aktuální sekvence zadaných číslic.