명령
MVVM(Model-View-ViewModel) 패턴을 사용하는 .NET 다중 플랫폼 앱 UI(.NET MAUI) 앱에서는 일반적으로 파생되는 클래스 INotifyPropertyChanged
인 viewmodel의 속성과 일반적으로 XAML 파일인 뷰의 속성 간에 데이터 바인딩이 정의됩니다. 경우에 따라 사용자가 viewmodel의 항목에 영향을 주는 명령을 시작하도록 요구하여 앱이 이러한 속성 바인딩을 넘어서야 하는 경우가 있습니다. 이러한 명령은 일반적으로 단추를 클릭하거나 손가락으로 탭하여 신호를 받으며, 대개 Button의 Clicked
이벤트 또는 TapGestureRecognizer의 Tapped
이벤트에 대한 처리기의 코드 숨김 파일에서 처리됩니다.
명령 인터페이스는 MVVM 아키텍처에 훨씬 더 적합한 명령을 구현하는 또 다른 방법을 제공합니다. viewmodel에는 클릭과 같은 뷰의 특정 활동에 대한 반응으로 실행되는 메서드인 명령이 Button 포함될 수 있습니다. 데이터 바인딩은 이러한 명령과 Button 간에 정의됩니다.
viewmodel과 viewmodel Button Button 간의 데이터 바인딩을 허용하려면 다음 두 가지 속성을 정의합니다.
Command
(System.Windows.Input.ICommand
형식)CommandParameter
(Object
형식)
명령 인터페이스를 사용하려면 소스가 형식의 viewmodel에 있는 속성인 위치의 Button 속성을 대상으로 Command
하는 데이터 바인딩을 정의합니다ICommand. viewmodel에는 단추를 클릭할 때 실행되는 해당 ICommand 속성과 연결된 코드가 포함되어 있습니다. 뷰모델의 CommandParameter
동일한 ICommand 속성에 모두 바인딩된 경우 속성을 임의의 데이터로 설정하여 여러 단추를 구분할 수 있습니다.
다른 많은 뷰도 정의하고 속성을 정의 Command
합니다 CommandParameter
. 이러한 모든 명령은 뷰의 사용자 인터페이스 개체에 의존하지 않는 접근 방식을 사용하여 viewmodel 내에서 처리할 수 있습니다.
ICommands
인터페이스는 ICommand System.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
에서 a Button 속성은 해당 속성에 바인딩됩니다.
<Button Text="Execute command"
Command="{Binding MyCommand}" />
사용자가 Button을 누르면 Button이 해당 Command
속성에 바인딩된 ICommand 개체에서 Execute
메서드를 호출합니다.
바인딩이 Button의 Command
속성에 처음 정의되고 데이터 바인딩이 어떤 방식으로든 변경되면 Button이 ICommand 개체의 CanExecute
메서드를 호출합니다. CanExecute
에서 false
가 반환되면 Button 자체를 사용하지 않도록 자동으로 설정됩니다. 이는 특정 명령이 현재 사용될 수 없거나 유효하지 않음을 나타냅니다.
또한 Button은 ICommand의 CanExecuteChanged
이벤트에 대한 처리기를 연결합니다. 이 이벤트는 viewmodel 내에서 발생합니다. 해당 이벤트가 발생하면 다시 호출 CanExecute
됩니다Button. CanExecute
에서 true
가 반환되면 Button 자체를 사용하도록 설정되고, CanExecute
에서 false
가 반환되면 자체를 사용하지 않도록 설정됩니다.
Warning
명령 인터페이스를 사용하는 경우 Button의 IsEnabled
속성은 사용하지 마세요.
viewmodel이 형식 ICommand의 속성을 정의하는 경우 viewmodel은 인터페이스를 구현 ICommand 하는 클래스도 포함하거나 참조해야 합니다. 이 클래스는 Execute
및 CanExecute
메서드를 포함하거나 참조해야 하며, CanExecute
메서드에서 다른 값을 반환할 수 있을 때마다 CanExecuteChanged
이벤트를 발생시킵니다. .NET MAUI에 Command
포함된 클래스 또는 Command<T>
클래스를 사용하여 인터페이스를 ICommand 구현할 수 있습니다. 이러한 클래스를 사용하면 클래스 생성자에서 Execute
및 CanExecute
메서드의 본문을 지정할 수 있습니다.
팁
속성을 사용하여 동일한 ICommand 속성에 CommandParameter
바인딩된 여러 뷰와 요구 사항이 아닌 경우 클래스를 Command
구분할 때 사용합니다Command<T>
.
기본 명령
다음 예제에서는 viewmodel에서 구현된 기본 명령을 보여 줍니다.
클래스는 PersonViewModel
명명Name
Age
된 세 가지 속성을 정의하고 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
의 새 개체를 만들고 사용자가 데이터를 채울 수 있도록 합니다. 이 목적을 위해 클래스는 형식, 형식 bool
및 PersonEdit
형식의 PersonViewModel
속성을 정의IsEditing
합니다. 또한 이 클래스는 ICommand 형식의 세 가지 속성과 IList<PersonViewModel>
형식의 Persons
속성을 정의합니다.
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
이벤트가 발생하지 않습니다. 이러한 속성은 클래스를 처음 만들 때 설정되며 변경되지 않습니다.
다음 예제에서는 다음을 사용하는 XAML을 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>
이 예제에서는 페이지의 BindingContext
속성이 .로 PersonCollectionViewModel
설정됩니다. viewmodel Grid 의 속성에 바인딩된 NewCommand
속성이 Command
있는 New 텍스트, 속성에 바인딩된 IsEditing
속성이 있는 항목 폼 및 viewmodel의 PersonViewModel
속성에 바인딩된 SubmitCommand
CancelCommand
두 개의 단추가 포함됩니다.Button ListView 이미 입력한 사람의 컬렉션이 표시됩니다.
다음 스크린샷은 연령이 설정된 후 활성화된 제출 단추를 보여 줍니다.
사용자가 먼저 새로 만들기 단추를 누르면 입력 폼이 활성화되지만 새로 만들기 단추를 사용하지 않도록 설정합니다. 그러면 사용자가 이름, 나이 및 기술을 입력합니다. 사용자는 편집하는 중에 언제라도 취소 단추를 눌러 다시 시작할 수 있습니다. 이름과 유효 기간이 입력된 경우에만 제출 단추가 활성화됩니다. 이 제출 단추를 누르면 사용자를 ListView에서 표시하는 컬렉션으로 전송합니다. 취소 또는 제출 단추를 누르면 입력 양식이 지워지고 새로 만들기 단추가 활성화됩니다.
새로 만들기, 제출 및 취소 단추에 대한 모든 논리는 NewCommand
, SubmitCommand
및 CancelCommand
속성의 정의를 통해 PersonCollectionViewModel
에서 처리됩니다. PersonCollectionViewModel
의 생성자는 이러한 세 가지 속성을 Command
형식의 개체로 설정합니다.
클래스의 Command
생성자를 사용하면 형식 및 Func<bool>
메서드에 해당하는 형식 Action
의 인수를 Execute
CanExecute
전달할 수 있습니다. 이 작업 및 함수는 생성자에서 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();
}
···
}
사용자가 새로 만들기 단추를 클릭하면 Command
생성자에 전달된 execute
함수가 실행됩니다. 이렇게 하면 새 PersonViewModel
개체가 만들어지고, 해당 개체의 PropertyChanged
이벤트에 대한 처리기가 설정되고, IsEditing
이 true
로 설정되고, 생성자 뒤에 정의된 RefreshCanExecutes
메서드가 호출됩니다.
또한 Command
클래스는 ICommand 인터페이스를 구현하는 것 외에도 ChangeCanExecute
라는 메서드를 정의합니다. viewmodel은 메서드의 ICommand CanExecute
반환 값을 변경할 수 있는 모든 일이 발생할 때마다 속성을 호출 ChangeCanExecute
해야 합니다. ChangeCanExecute
에 대한 호출로 인해 Command
클래스에서 CanExecuteChanged
메서드를 실행합니다. Button은 해당 이벤트에 대한 처리기를 연결하고, CanExecute
를 다시 호출한 후 해당 메서드의 반환 값에 따라 자체를 활성화하여 응답합니다.
NewCommand
의 execute
메서드에서 RefreshCanExecutes
를 호출하면 NewCommand
속성이 ChangeCanExecute
에 대한 호출을 가져오고, Button이 canExecute
메서드를 호출하고, IsEditing
속성이 현재 true
이므로 false
를 반환합니다.
PropertyChanged
새 PersonViewModel
개체의 처리기는 다음 메서드SubmitCommand
를 ChangeCanExecute
호출합니다.
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;
});
···
}
···
}
SubmitCommand
에 대한 canExecute
함수는 편집하는 PersonViewModel
개체에서 변경되는 속성이 있을 때마다 호출됩니다. Name
속성이 하나 이상의 문자이고 Age
가 0보다 큰 경우에만 true
를 반환합니다. 이때 제출 단추가 활성화됩니다.
Submit 함수는 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;
});
}
···
}
PersonViewModel
이 편집될 때마다 canExecute
메서드에서 true
를 반환합니다.
참고 항목
execute
및 canExecute
메서드를 람다 함수로 정의할 필요는 없습니다. viewmodel에서 프라이빗 메서드로 작성하고 생성자에서 Command
참조할 수 있습니다. 그러나 이 방법을 사용하면 viewmodel에서 한 번만 참조되는 많은 메서드가 발생할 수 있습니다.
명령 매개 변수 사용
viewmodel에서 동일한 ICommand 속성을 공유하는 하나 이상의 단추 또는 다른 사용자 인터페이스 개체에 편리한 경우도 있습니다. 이 경우 이 속성을 사용하여 CommandParameter
단추를 구분할 수 있습니다.
Command
클래스는 이러한 공유 ICommand 속성에 대해 계속 사용할 수 있습니다. 클래스는 형식Object
의 매개 변수를 사용하여 수락하고 canExecute
메서드를 허용하는 대체 생성자를 정의합니다execute
. 이는 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">
<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="⇦"
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="·"
Grid.Row="5" Grid.Column="2"
Command="{Binding DigitCommand}"
CommandParameter="." />
</Grid>
</ContentPage>
이 예제에서 페이지의 BindingContext
형식은 .입니다 DecimalKeypadViewModel
. 이 Entry viewmodel의 속성은 의 속성Label에 Text
바인딩됩니다. 모든 개체는 Button viewmodel의 ClearCommand
BackspaceCommand
DigitCommand
명령에 바인딩됩니다. 10개 숫자 및 소수점에 대한 11개 단추는 DigitCommand
에 대한 바인딩을 공유합니다. CommandParameter
는 이러한 단추를 구분합니다. 설정된 CommandParameter
값은 일반적으로 10진수 점을 제외하고 단추에 표시되는 텍스트와 동일하며, 이는 선명도를 위해 가운데 점 문자로 표시됩니다.
형식 DecimalKeypadViewModel
의 속성과 형식 string
의 세 가지 속성을 ICommand정의합니다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; }
}
이 단추에 ClearCommand
해당하는 단추는 항상 사용하도록 설정되며 항목을 다시 "0"으로 설정합니다.
public class DecimalKeypadViewModel : INotifyPropertyChanged
{
···
public DecimalKeypadViewModel()
{
ClearCommand = new Command(
execute: () =>
{
Entry = "0";
RefreshCanExecutes();
});
···
}
void RefreshCanExecutes()
{
((Command)BackspaceCommand).ChangeCanExecute();
((Command)DigitCommand).ChangeCanExecute();
}
···
}
단추가 항상 활성화되므로 Command
생성자에서 canExecute
인수를 지정할 필요가 없습니다.
항목의 길이가 1보다 크거나 Entry가 "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";
});
···
}
···
}
백스페이스 단추에 대한 execute
함수의 논리는 Entry가 적어도 "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을 제거해야 합니다. 인수가 소수점이고(소수점을 눌렀는지 나타냄) Entry에 이미 소수점이 포함되어 있는 경우에만 canExecute
메서드에서 false
를 반환합니다. 모든 execute
메서드는 RefreshCanExecutes
를 호출한 다음, DigitCommand
및 ClearCommand
모두에 대해 ChangeCanExecute
를 호출합니다. 이렇게 하면 입력된 숫자의 현재 순서에 따라 소수점 및 백스페이스 단추가 활성화 또는 비활성화되도록 보장합니다.
.NET MAUI