다음을 통해 공유


제어

샘플을 찾아봅니다. 샘플 찾아보기

MVVM(Model-View-ViewModel) 패턴을 사용하는 .NET 다중 플랫폼 앱 UI(.NET MAUI) 앱에서 데이터 바인딩은 일반적으로 파생되는 클래스 INotifyPropertyChanged인 viewmodel의 속성과 일반적으로 XAML 파일인 뷰의 속성 간에 정의됩니다. 경우에 따라 사용자가 viewmodel의 항목에 영향을 주는 명령을 시작하도록 요구하여 앱이 이러한 속성 바인딩을 넘어서야 하는 경우가 있습니다. 이러한 명령은 보통 버튼 클릭이나 손가락 탭에 의해 신호가 되며, 전통적으로 Clicked 이벤트의 처리기나 ButtonTappedTapGestureRecognizer 이벤트 처리기에서 코드 숨김 파일에서 처리됩니다.

명령 인터페이스는 MVVM 아키텍처에 훨씬 더 적합한 명령을 구현하는 대체 방법을 제공합니다. viewmodel에는 클릭과 같은 뷰의 특정 활동에 대한 반응으로 실행되는 메서드인 명령이 Button 포함될 수 있습니다. 데이터 바인딩은 이러한 명령과 Button 사이에 정의됩니다.

`Button`과 viewmodel 간의 데이터 바인딩을 허용하려면, Button은 두 가지 속성을 정의합니다.

명령 인터페이스를 사용하려면, CommandButton 속성을 대상으로 하고 형식 ICommand인 viewmodel의 속성을 소스로 하는 데이터 바인딩을 정의해야 합니다. viewmodel에는 단추를 클릭할 때 실행되는 해당 ICommand 속성과 연결된 코드가 포함되어 있습니다. 임의의 데이터로 CommandParameter 속성을 설정하여, 여러 단추가 뷰모델의 동일한 ICommand 속성에 모두 바인딩된 경우에도 각 단추를 구분할 수 있습니다.

다른 많은 뷰에서도 CommandCommandParameter 속성을 정의합니다. 이러한 모든 명령은 뷰의 사용자 인터페이스 개체에 의존하지 않는 접근 방식을 사용하여 viewmodel 내에서 처리할 수 있습니다.

ICommands

인터페이스는 ICommandSystem.Windows.Input 네임스페이스에 정의되며 두 가지 메서드와 하나의 이벤트로 구성됩니다.

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

명령 인터페이스를 사용하려면 viewmodel에 다음과 같은 형식 ICommand의 속성이 포함되어야 합니다.

public ICommand MyCommand { private set; get; }

viewmodel은 ICommand 인터페이스를 구현하는 클래스를 참조해야 합니다. 뷰에서 Command 속성은 Button의 하나의 속성에 바인딩됩니다.

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

사용자가 Button를 누르면, Button에서 Execute 메서드를 호출하여 ICommand 속성에 바인딩된 Command 개체를 사용합니다.

바인딩이 처음으로 Command 속성에 Button으로 정의되고, 데이터 바인딩이 변화하면 ButtonCanExecute 개체에서 ICommand 메서드를 호출합니다. 만약 CanExecutefalse를 반환하면, Button는 스스로 비활성화됩니다. 이는 특정 명령을 현재 사용할 수 없거나 유효하지 않음을 나타냅니다.

또한 ButtonCanExecuteChangedICommand 이벤트에 대한 처리기를 등록합니다. 결과에 영향을 주는 CanExecute 조건이 변경될 때마다 viewmodel 내에서 이벤트를 수동으로 발생시켜야 합니다. 해당 이벤트가 발생하면 ButtonCanExecute을 다시 호출합니다. Button은/는 CanExecute이/가 true을/를 반환하면 활성화되고, CanExecute이/가 false을/를 반환하면 비활성화됩니다.

중요합니다

일부 UI 프레임워크(예: WPF)와 달리 .NET MAUI는 반환 값이 CanExecute 변경될 수 있는 시기를 자동으로 검색하지 않습니다. 상태가 변경되어 CanExecute 결과에 영향을 미칠 때마다 CanExecuteChanged 이벤트를 수동으로 발생시켜야 합니다 (또는 Command 클래스에서 ChangeCanExecute()을 호출해야 합니다). 이 작업은 일반적으로 의존하는 속성이 CanExecute 수정될 때 수행됩니다.

비고

IsEnabled 속성을 Button 메서드 대신 사용하거나, 함께 사용할 수도 있습니다. .NET MAUI 7 이하에서는 IsEnabled 메서드의 반환 값이 항상 Button 속성을 덮어쓰므로 명령 인터페이스를 사용하는 동안 CanExecute 속성을 사용할 수 없었습니다. 이 문제는 .NET MAUI 8 이상에서 해결되었습니다. IsEnabled 이제 명령 기반 Buttons에서 속성을 사용할 수 있습니다. 그러나 이제 IsEnabled 속성과 CanExecute 메서드가 모두 true를 반환해야 Button가 활성화됩니다(부모 컨트롤도 활성화되어 있어야 합니다).

viewmodel이 형식 ICommand의 속성을 정의하는 경우 viewmodel은 인터페이스를 구현 ICommand 하는 클래스도 포함하거나 참조해야 합니다. 이 클래스는 Execute 메서드와 CanExecute 메서드를 포함하거나 참조해야 하며 메서드가 CanExecute 다른 값을 반환할 때마다 CanExecuteChanged 이벤트를 수동으로 트리거해야 합니다. .NET MAUI에 포함된 Command 클래스나 Command<T> 클래스를 사용하여 ICommand 인터페이스를 구현할 수 있습니다. 이러한 클래스를 사용하면 클래스 생성자 내에서 ExecuteCanExecute 메서드의 본문을 지정할 수 있습니다.

팁 (조언)

Command<T>를 사용하여 동일한 CommandParameter 속성에 바인딩된 여러 뷰를 구분할 때, 요구 사항이 아닌 경우에는 ICommand 클래스를 사용합니다.

기본 명령

다음 예제에서는 viewmodel에서 구현된 기본 명령을 보여 줍니다.

클래스는 PersonViewModel 명명NameAge된 세 가지 속성을 정의하고 Skills 사람을 정의합니다.

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

아래 표시된 클래스는 PersonCollectionViewModel 형식 PersonViewModel 의 새 개체를 만들고 사용자가 데이터를 채울 수 있도록 합니다. 클래스는 이 목적을 위해 IsEditing 형식의 boolPersonEdit 형식의 PersonViewModel 속성을 정의합니다. 또한 클래스는 ICommand 타입의 세 가지 속성과 Persons 타입의 속성인 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));
    }
}

이 예제에서는 세 ICommand 가지 속성을 변경하고 Persons 속성을 변경해도 PropertyChanged 이벤트가 발생하지 않습니다. 이러한 속성은 클래스를 처음 만들 때 설정되며 변경되지 않습니다.

다음 예제에서는 PersonCollectionViewModel을(를) 소비하는 XAML을 보여 줍니다.

<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"
             x:DataType="local:PersonCollectionViewModel">             
    <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 x:DataType="local:PersonViewModel"
                  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>

이 예제에서는 페이지의 BindingContext 속성이 .로 PersonCollectionViewModel설정됩니다. viewmodel의 Grid 속성에 바인딩된 Button에는 New 텍스트가 포함되어 있고, Command 속성에 바인딩된 속성들이 있는 입력 양식과, viewmodel의 NewCommand, IsEditing 속성에 바인딩된 두 개의 단추도 포함됩니다. ListView 이미 입력한 사람들의 컬렉션이 표시됩니다.

다음 스크린샷은 연령이 설정된 후 활성화된 제출 단추를 보여 줍니다.

인물 입력.

사용자가 먼저 새로 만들기 단추를 누르면 입력 폼이 활성화되지만 새로 만들기 단추를 사용하지 않도록 설정합니다. 그런 다음 사용자는 이름, 연령 및 기술을 입력합니다. 편집하는 동안 언제든지 취소 단추를 눌러 다시 시작할 수 있습니다. 이름과 유효한 연령을 입력한 경우에만 제출 단추가 활성화됩니다. 이 제출 단추를 누르면 해당 사용자가 표시된 컬렉션으로 ListView전송됩니다. 취소 또는 제출 단추를 누르면 항목 양식이 지워지고 새로 만들기 단추가 다시 활성화됩니다.

새로 만들기, 제출취소 단추에 대한 모든 논리는 , PersonCollectionViewModelNewCommand 속성의 SubmitCommand정의를 통해 처리 CancelCommand 됩니다. 이 생성자는 이 세 속성을 PersonCollectionViewModel 형식의 Command 개체로 설정합니다.

클래스의 Command 생성자는 Action 메서드와 Func<bool> 메서드에 각각 해당하는 형식인 ExecuteCanExecute 형식의 인수를 전달할 수 있게 해줍니다. 이 작업 및 함수는 생성자에서 Command 람다 함수로 정의할 수 있습니다.

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

사용자가 새로 만들기 단추를 execute 클릭하면 생성자에 전달된 Command 함수가 실행됩니다. 이렇게 하면 새 PersonViewModel 개체가 만들어지고, 해당 개체의 PropertyChanged 이벤트에 대한 처리기가 설정 IsEditingtrue되고, 생성자 이후에 정의된 메서드가 RefreshCanExecutes 호출됩니다.

인터페이스를 구현하는 ICommand 것 외에도 클래스는 Command 명명 ChangeCanExecute된 메서드를 정의합니다. 뷰모델은 CanExecute 메서드의 반환 값이 변경될 수 있는 상황이 발생할 때마다 ChangeCanExecute 속성을 호출해야 합니다. ChangeCanExecute 호출이 Command 클래스에서 CanExecuteChanged 이벤트를 발생시킵니다. Button는 해당 이벤트에 대한 처리기를 첨부하고 CanExecute을 다시 호출한 후 해당 메서드의 반환 값에 따라 스스로 활성화하여 응답합니다.

호출하는 execute 메서드의 경우, NewCommandRefreshCanExecutes를 호출할 때, NewCommand 속성이 ChangeCanExecute를 호출하게 되고, ButtoncanExecute 메서드를 호출합니다. 이 메서드는 이제 false 속성이 IsEditing 되었기 때문에 true를 반환합니다.

PropertyChangedPersonViewModel 객체의 핸들러는 ChangeCanExecuteSubmitCommand 메서드를 호출합니다.

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

편집 중인 canExecute 객체에서 속성이 변경될 때마다 SubmitCommand 함수가 PersonViewModel에서 호출됩니다. trueName 속성이 최소 한 문자 이상이고 Age가 0보다 클 경우에만 반환됩니다. 이때 제출 단추가 활성화됩니다.

execute 함수는 속성 변경 처리기를 컬렉션에서 PersonViewModel제거하고, 개체를 컬렉션에 Persons 추가하고, 모든 항목을 초기 상태로 반환합니다.

execute 단추의 함수는 컬렉션에 개체를 추가하는 것을 제외하고 제출 단추가 수행하는 모든 작업을 수행합니다.

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

편집 중인 canExecute이(가) 있을 때 true는 언제든지 PersonViewModel을(를) 반환합니다.

비고

executecanExecute 메서드를 람다 함수로 정의할 필요는 없습니다. viewmodel에서 프라이빗 메서드로 작성하고 생성자에서 Command 참조할 수 있습니다. 그러나 이 방법을 사용하면 viewmodel에서 한 번만 참조되는 많은 메서드가 발생할 수 있습니다.

명령 매개 변수 사용

사용자 인터페이스 개체 중 하나 이상의 버튼 또는 다른 요소들이 viewmodel에서 동일한 ICommand 속성을 공유하는 것이 편리한 경우가 있습니다. 이 경우 이 속성을 사용하여 CommandParameter 단추를 구분할 수 있습니다.

이러한 공유 Command 속성에 대해 ICommand 클래스를 계속 사용할 수 있습니다. 클래스는 executecanExecute 메서드와 형식 Object의 매개 변수를 사용하는 대체 생성자를 정의합니다. 이러한 메서드에 CommandParameter 전달되는 방법입니다. 그러나 CommandParameter를 지정하는 경우, Command<T>에 설정된 개체의 유형을 지정하기 위해 제네릭 CommandParameter 클래스를 사용하는 것이 가장 쉽습니다. execute 지정한 메서드 및 canExecute 메서드에는 해당 형식의 매개 변수가 있습니다.

다음 예제에서는 소수 자릿수를 입력하기 위한 키보드를 보여 줍니다.

<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"
             x:DataType="local:DecimalKeypadViewModel">
    <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>

이 예제에서 페이지의 BindingContextDecimalKeypadViewModel 형식입니다. 이 Entry viewmodel의 속성은 TextLabel 속성에 바인딩됩니다. 모든 개체는 Button viewmodel의 ClearCommandBackspaceCommandDigitCommand명령에 바인딩됩니다. 10자리 숫자와 소수점의 11개 단추는 바인딩을 공유합니다 DigitCommand. 이러한 CommandParameter 단추를 구분합니다. 설정된 CommandParameter 값은 일반적으로 10진수 점을 제외하고 단추에 표시되는 텍스트와 동일하며, 이는 선명도를 위해 가운데 점 문자로 표시됩니다.

10진수 키보드.

DecimalKeypadViewModel는 형식 Entry의 속성과 형식 string의 세 가지 속성을 정의합니다ICommand.

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

ClearCommand에 연결된 단추는 항상 활성화되어 있으며 항목을 '0'으로 다시 설정합니다.

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

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

단추는 항상 사용하도록 설정되므로 생성자에서 canExecute 인수를 Command 지정할 필요가 없습니다.

백스페이스 단추는 항목 길이가 1보다 크거나 문자열 "0"이 아닌 경우에만 Entry 사용하도록 설정됩니다.

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

함수는 백스페이스 버튼을 위한 논리로 이(가) 최소한 "0" 문자열임을 보장합니다.

속성 DigitCommand은 각각이 CommandParameter 속성으로 자신을 식별하는 11개의 버튼에 바인딩됩니다. 클래스 DigitCommand의 인스턴스로 Command<T>가 설정됩니다. XAML CommandParameter 에서 명령 인터페이스를 사용하는 경우 속성은 일반적으로 제네릭 인수의 형식인 문자열입니다. execute 그런 다음, 함수 canExecute 에는 다음과 같은 형식string의 인수가 있습니다.

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

메서드는 execute 속성에 문자열 인수를 Entry 추가합니다. 그러나 결과가 0(0 및 소수점 아님)으로 시작하는 경우 함수를 사용하여 Substring 초기 0을 제거해야 합니다. canExecute 메서드는 인수가 소수점(소수점이 눌리고 있음을 나타내는)이고 false가 이미 소수점을 포함하고 있는 경우에만 Entry을 반환합니다. 모든 execute 메서드는 RefreshCanExecutes을 호출한 후, ChangeCanExecuteDigitCommand에 대해 ClearCommand를 호출합니다. 이렇게 하면 입력한 숫자의 현재 시퀀스에 따라 소수점 및 백스페이스 단추가 활성화되거나 비활성화됩니다.