[Xamarin] Event를 Command로 단순화 하기

2016. 4. 3. 03:46Mobile

Command란?

UI와의 상호작용으로 특정 메서드를 실행하는 방법은 Clicked나 Tapped와 같이 이벤트를 통해 실행하는 것이 대표적입니다.  그러나 Command를 이용하면 DataBinding이 ViewModel의 메서드를 호출하도록 할 수 있으며, 이는 MVVM패턴을 통해 개발을 더욱 단순하게 할 수 있습니다. 이러한 Command는 다음 Class들에서 지원합니다.

  • Button
  • MenuItem
  • ToolbarItem
  • SearchBar
  • TextCell
  • ImageCell
  • ListView
  • TapGestureRecognizer

Command를 지원하기 위해 이 Class들에는 2개의 공용 Property가 정의되어 있습니다.

  • Command: System.Windows.Input.Icommand Type을 가집니다.
  • CommandParameter: object타입입니다.

Command 구현하기

Command를 구현하기 위해서는 ViewModel은 적어도 1개이상의 Public ICommand Type의 Property를 가져야 합니다. ICommand interface는 다음 2개의 method와 1개의 event를 가지고 있습니다.

public interface ICommand
{
   void Execute(object arg);
   bool CanExecute(object arg)
   event EventHandler CanExecuteChanged; 
} 

Xamarin.Forms는 ICommand Interface를 구현한 Command, Command<T> class를 구현해두었습니다. 여기서 <T>는 Execute, CanExecute Method의 매개변수 Type에 해당합니다. Command는 ChangeCanExecute method를 포함하고 있는데, Command의 CanExecuteChanged event가 실행되도록 합니다.

ViewModel에 포함된 ICommand Property마다 Command, Command<T> type의 Object가 할당되어야 합니다. Command, Command<T>의 생성자는 Action callback object를 매개변수로 받는데, 이 Action callback object는 Button이 Click되어  ICommand.Execute가 호출될 때 마다 실행됩니다. 두번째 매개변수도 받을 수 있는데 ICommand.CanExecute에 해당하는 Func를 지정하시면 됩니다. CanExecute method란 Command가 실행할 수 있는 상태인지 아닌지를 나타내는 bool 값을 반환하는 Method를 말합니다. 즉, 작성하신 Method가 false를 반환하면 Command는 실행되지 않습니다.

다음은 제곱근을 계산하는데 사용된 Command 구현 예제입니다.

public class DemoViewModel : INotifyPropertyChanged
{
	public int Number { get; set; }
	public double SquareRootResult { get; private set; }
	public ICommand SquareRootCommand { get; private set; }
    ...
 
	public DemoViewModel ()
	{
		Number = 25;
		SquareRootCommand = new Command (CalculateSquareRoot);
        ...
	}
 
	void CalculateSquareRoot ()
	{
		SquareRootResult = Math.Sqrt (Number);
		OnPropertyChanged ("SquareRootResult");			
	}
    ...
} 

SquareRootCommand는 Button의 Command 속성에 Binding되며 구현 예는 다음과 같습니다.

<Label Text="Demo 1 - Command" FontAttributes="Bold" />
<StackLayout Orientation="Horizontal">
	<Label Text="Enter number:" />
	<Entry Text="{Binding Number, Mode=TwoWay}" WidthRequest="50" />
</StackLayout>
<!-- SquareRootCommand는 Command에 바인딩 함 -->
<Button Text="Calculate Square Root" Command="{Binding SquareRootCommand}" HorizontalOptions="Center" />
<StackLayout Orientation="Horizontal">
	<Label Text="Square root =" />
	<Label Text="{Binding SquareRootResult}" />
</StackLayout>

Button이 클릭되면 Command에 바인딩된 SquareRootCommand의 ICommand.Execute 메서드를 호출합니다.SquareRootCommand에 할당된 CalculateSquareRoot Method가 실행되게 되며 Number의 제곱근이 SquareRootResult에 할당되게 됩니다. 그려면 OnPropertyChange에 의해 Label은 갱신된 SquareRootResult 값을 출력하게 됩니다.

Command에 매개변수 전달하기

Command<T>의 생성자 매개변수를 이용하면 ICommand.Execute 메서드에 넘겨줄 매개변수를 지정할 수 있습니다.

public class DemoViewModel : INotifyPropertyChanged
{
    public double SquareRootWithParameterResult { get; private set; }
    public ICommand SquareRootWithParameterCommand { get; private set; }
    ...
 
    public DemoViewModel ()
    {
        SquareRootWithParameterCommand = new Command (CalculateSquareRoot);
        ...
    }
 
    void CalculateSquareRoot (string value)
    {
        double num = Convert.ToDouble (value);
        SquareRootWithParameterResult = Math.Sqrt (num);
        OnPropertyChanged ("SquareRootWithParameterResult");            
    }
    ...
}

CalculateSquareRoot method에 전달될 string value의 값은 다음과 같이 지정할 수 있습니다.

<Label Text="Demo 2 - Command with Parameter" FontAttributes="Bold" />
<StackLayout Orientation="Horizontal">
	<Label Text="숫자입력:" />
	<Entry x:Name="entry" Text="100" WidthRequest="50" />
</StackLayout>
<Button Text="Calculate Square Root" 
        Command="{Binding SquareRootWithParameterCommand}" 
        CommandParameter="{Binding Source={x:Reference entry}, Path=Text}" HorizontalOptions="Center" />
<StackLayout Orientation="Horizontal">
	<Label Text="Square root =" />
	<Label Text="{Binding SquareRootWithParameterResult}" />
</StackLayout>

종전과 다른점은 Entry의 Text를 CommandParameter로 지정해 준 것입니다. 즉 Entry에 입력된 값은 Button의 Command가 실행될 때 매개변수로 사용되게 됩니다.

비동기 메서드 호출하기

Command가 async, await를 통한 비동기 메서드를 호출하도록 할 수 있습니다.

public class DemoViewModel : INotifyPropertyChanged
{
    ...
    bool canDownload = true;
    string simulatedDownloadResult;
 
    public string SimulatedDownloadResult 
    {
        get { return simulatedDownloadResult; }
        private set 
        {
            if (simulatedDownloadResult != value) 
            {
                simulatedDownloadResult = value;
                OnPropertyChanged ("SimulatedDownloadResult");
            }
        }
    }
 
    public ICommand SimulateDownloadCommand { get; private set; }
 
    public DemoViewModel ()
    {
        ...
        SimulateDownloadCommand = 
            new Command (async  () => await SimulateDownloadAsync(), 
                                () => canDownload);
    }
 
    async Task SimulateDownloadAsync ()
    {
        CanInitiateNewDownload (false);
        SimulatedDownloadResult = string.Empty;
        await Task.Run (() => SimulateDownload ());
        SimulatedDownloadResult = "Simulated download complete";
        CanInitiateNewDownload (true);
    }
 
    void CanInitiateNewDownload (bool value)
    {
        canDownload = value;
        ((Command)SimulateDownloadCommand).ChangeCanExecute ();
    }
 
    void SimulateDownload ()
    {
        // Simulate a 5 second pause
        var endTime = DateTime.Now.AddSeconds (5);
        while (true) 
        {
            if (DateTime.Now >= endTime) 
                break;
        }
    }
    ...
}

ICommand Property에 할당된 메서드는 Task를 반환하는 SimulateDownloadAsync입니다. async, await keyword에 의해서 SimulateDownloadAsync는 비동기로 실행되게 됩니다.

<Label Text="Demo 3 - Async Command with CanExecute" FontAttributes="Bold" />
<Button Text="Simulate 5 Second Download" HorizontalOptions="Center"
        Command="{Binding SimulateDownloadCommand}" />
<StackLayout Orientation="Horizontal">
	<Label Text="Result: " />
	<Label Text="{Binding SimulatedDownloadResult}" />
</StackLayout>

버튼이 클릭되면 비동기 메서드로 SimulateDownloadAsync가 호출되며 async, await 비동기 패턴에 의해 SimulateDownload Method는 Background Thread에 의해서 실행되게 됩니다. 5초 후에 SimulateDownload Method가 실행완료되며, 다시 CanExecute값이 true로 변경됩니다. CanExecute Property 값이 변경되면 Command가 Binding 된 Button은 자동으로 Disabled되므로 유의해주세요.

원문: https://blog.xamarin.com/simplifying-events-with-commanding/