레이블이 xamarin인 게시물을 표시합니다. 모든 게시물 표시
레이블이 xamarin인 게시물을 표시합니다. 모든 게시물 표시

2020년 1월 22일 수요일

Galaxy Watch용 Tizen xamarin app에서 Entry에 의한 화면 깨짐 발생 시

Galaxy Watch app을 Xamarin.forms를 이용해서 C#으로 만들 수 있음.
https://samsung.github.io/Tizen.CircularUI/guide/Quickstart.html
기존에는 어려운 EFL 기반 app이나 web app을 만들어야 했는데 이제는 C#으로도 개발할 수 있음.

암튼 Watch 용 app을 하나 만들어 보는 중에
입력을 위한 Page가 필요하여 Entry를 추가하게 되었음.

하지만 페이지에서 Entry를 선택하면 Virtual Keyboard가 떠서
Page layout 밀려 올라가 이상하게 보이는 현상이 발생했다.

관련하여 다른 옵션이 없는지 확인 중 Tizen에서 제공하는 CircularUI가 있고
그 중에 PopupEntry를 제공하는 것을 확인하였음.

https://samsung.github.io/Tizen.CircularUI/guide/PopupEntry.html

Popup Entry는 일반적으로 Galaxy Watch app에서 사용되는
전체화면을 가리는 popup으로 입력창과 키보드가 떠서
기존 Page layout에는 영향을 주지 않고 입력할 수 있게 해준다.





참고로 안드로이드에서는 다음과 같이 처리하고 있음.
https://www.milestre.nl/blog/blogitem/milestre-blog/2019/01/08/to-avoid-soft-keyboard-overlap

//To avoid soft keyboard statusbar overlap            Xamarin.Forms.Application.Current.On().UseWindowSoftInputModeAdjust(WindowSoftInputModeAdjust.Resize);

2018년 2월 1일 목요일

Xamarin.Forms app을 Google Play store에 올리기

초간단 Xamarin.forms app을 만들어 Google Play store에 올림.

https://github.com/hallower/WhooingNewsReader
https://play.google.com/store/apps/details?id=kr.blogspot.charlie0301.NewsReader

리스트뷰만 있는 앱이다 보니 생각보다 Android에서 돌아가는게 느리진 않고
C# 으로 만들앱이다 보니 Google Play store에 올리는데 문제가 있진 않을까 했는데 생각보다 잘 올라가서 신기함.

일단 Google Play store에 올리는 방법
https://developer.xamarin.com/guides/android/deployment,_testing,_and_metrics/publishing_an_application/

Apple App Store에 올리는 방법
https://developer.xamarin.com/guides/ios/deployment,_testing,_and_metrics/app_distribution/app-store-distribution/publishing_to_the_app_store/


Google Play Store에 올리는 방법은 다음과 같이 나눠져 있고
사실 간단한 앱은 Part 1-3까지만 진행하면 등록이 된다.


위 Part 1,2,3를 따라하면서 큰 문제점은 없었고
워낙 잘 정리가 되어 있어서 따라하기만 하면 되지만!

- 모든 절차를 따라해야 나중에 APK를 업로드할 때 문제가 없다.
 : APK 업로드 시 문제점이 무엇인지 알려주므로 문제점이 있다면 Guide를 참고하여 수정 후 다시 수행하면 된다.

- 처음 업로드, 앱 등록 시 Visual Studio에서 자동 처리가 되지 않는다.
 : 처음에는 수동으로 APK를 signing한 뒤 play store에 업로드 해야 한다.

- APK 업로드 시 Signing된 APK를 업로드 해야 한다.
 : 아래 예시 그림에서 signed-apks 폴더 내의 APK를 업로드 해야 한다. 아래 그림에 보이는 MyApp.MyApp.apk는 아니다.
 : 당연한 거겠지만 signing 할 때는 Google Play에 등록된 upload 인증서를 사용해서 signing해야 한다.
  . 참고 http://charlie0301.blogspot.kr/2018/01/google-play-app-signing.html


2017년 7월 9일 일요일

MVVM, Xamarin.Forms

Xamarin을 사용하게 되면서 MVVM에 대한 개념은 그래도 이해가 되긴 했지만
실제 코드에 적용하기에는 어려움이 있었던 관계로
링크와 함께 간단하게 대충 정리함....

보통 MVVM 검색하면 아래 그림과 link가 나와서 이것을 위주로 정리함.


https://msdn.microsoft.com/en-us/library/hh848246.aspx


그외 볼만한 link들

안드로이드의 MVC, MVP, MVVM 종합 안내서, Eric Maxwell
: https://news.realm.io/kr/news/eric-maxwell-mvc-mvp-and-mvvm-on-android/

우선 Xamarin 홈페이지에서도 XAML, Binding, MVVM에 대해서 자세히 설명하고 있다.
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_binding_basics/
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/



The MVVM Pattern


https://msdn.microsoft.com/en-us/library/hh848246.aspx

(이건 사실 Xamarin보다는 WPF, Silverlight 관련된 article이라 method나 xaml tag가 다른 부분도 있음.)

MVVM의 가장 큰 이유는 separation of concerns
Tightly couple될 수 밖에 없는 GUI와 logic을 분리하기 위함이다.

View와 Model은 View Model을 중간에 두고 서로 분리되어 있고
View는 View Mode간에서는 data binding과 notification을 사용해서 연동된다.

MVVM pattern은 명칭에서 알 수 있듯이 Model, View, View Model로 구성되어 있다.


View

View는 Screen에서 사용자들이 보는 GUI 부분이며 XAML과 code-behind 파일(.cs)로 구성된다. (물론 cs 파일로 구성될 수 도 있을 것이고..)
View는 각각 View Model을 가지고 View Model에게서 binding을 통해 data를 가져오거나 method를 호출한다.
run-time중 UI control이 변경되면 binding된 View Model의 property가 변경되어 View Model에서 적절한 처리를 할 수 있다.

View 파일 (XAML)
- Title, Label, Image, HtmlLabel, button이 binding 되어 있음.
https://github.com/hallower/WhooingNewsReader/blob/master/WhooingNewsReader/WhooingNewsReader/Views/ItemDetailPage.xaml

View code-behind 파일 (.cs)
- ItemDetailPage 두번째 constrctor에서
  ViewModel을 인자로 받아 BindingContext로 지정하고 있음.
https://github.com/hallower/WhooingNewsReader/blob/master/WhooingNewsReader/WhooingNewsReader/Views/ItemDetailPage.xaml.cs


Model

Model은 application의 domain model의 구현체로서 data model, business, validation logic을 포함한다.


View Model

View Mode은 View와 Model 중간 매개자 역할을 하며 View logic을 처리한다.
View Model은 Model의 method를 호출하여 model과 연동하고 전달 받은 data를 View에 맞게 가공한 뒤 View로 전달한다.
View Model은 UI의 event에 따른 action을 처리하기 위한 command를 제공한다.

View Model이 View와 two-way data binding을 하고자 할 경우 PropertyChanged event를 발생 시켜야 한다. 이를 위해서 View Model은 INotifyPropertyChanged inteface를 구현해야하고 실제 binding된 property가 변경 되었을 때 PropertyChanged를 호출하여 변경을 알려야 한다.

View Model 파일
- View에서 binding된 property들이 public으로 존재하는 것을 볼 수 있음.
- GetDetail에서 OnPropertyChanged()를 호출하여 View를 업데이트 하는 것을 볼 수 있음.
https://github.com/hallower/WhooingNewsReader/blob/master/WhooingNewsReader/WhooingNewsReader/ViewModels/ItemDetailViewModel.cs

Connecting View Models to Views


동작, 이벤트 처리를 위해 MVVM의 data binding을 사용하여 View와 View Model을 연결해야 한다. View Model이 연결되므로 view의 code-behind에는 business logic이 있을 필요가 없다.

Code-behind
View의 Code-behind file의 생성자에서 View Model을 DataContext(Xamarin에서는 BindingContext)로 View Model을 지정한다.

View
View Model가 default constructor 만 가지고 있을 경우 View에서 DataContext(Xamarin에서는 BindingContext)로 View Model을 연결 할 수 있다. 일반적으로 View Model Locator를 사용하는데 이는 각 view가 binding할 View Model을 제공하는 것으로 application이 여러 View들을 하나의 View Model로 연결하여 관리할 수 있다.



그리고 개발을 하다 보니

- View, View Model은 되도록이면 한 쌍으로 구성하는 것을 권장
 : 하나의 View Model이 여러 View를 관리할 경우 비대해질 경우가 있음.

- 단, 여러 View에서 동일한 Context가 공유 될 경우 View Model을 Singleton이나 만들어 사용 하는 것도 방법이다.

- View Model이 List로 구성되거나 중첩된 control/view로 구성된다면 View Model간 연동이 고민인데
 : View Model간 notification을 줄 수 있는 interface를 구현하거나
 : Xamarin의 Messaging Service를 사용하는 것도 방법이다. (편리하지만 남용은 금물이다.)

- View Model은 View의 초기화 시점에 새로운 instance로 만들어 진다.
 : 같은 View가 여러 번 보여지더라도 생성되는 View Model은 각기 다른 instance임.

- View Model의 Property들은 당연히 public 접근자를 가져야 함.

- OnPropertyChanged 구현 시 Property name의 default 값을 CallerMemberName으로 지정하는 것이 바람직함.
 : Property 이름 오타로 인한 notification 누락을 방지할 수 있음.

protected void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
      var changed = PropertyChanged;
      if (changed == null)
          return;

      changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

- Property 값을 private 변수로 만들어 두고 Property setter에 OnPropertyChanged를 호출하게 하는 것도 방법임.

private string displayText;
public string DisplayText
{
     get
     {
         return displayText;
      }
      protected set
      {
          if (displayText != value)
          {
              displayText = value;
              OnPropertyChanged();
          }
      }
}

- Two-way binding 시 View에서 Property 변경 시 다른 작업을 수행하려면
   BindablableObject의 PropertyChangedEventHandler를 사용하면 된다.

2017년 3월 27일 월요일

[Link] Xamarin MessagingCenter, publish/subscribe 기반으로 coupling 간단한 message 전달 방법

View model과 다른 component간에 message를 사용하여 연동될 수 있도록 하는 방법이고 message에 대해서 subscribe, publish 하여 사용하므로 coupling을 줄일 수 있다.


: https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/messaging-center/
 

Simple String Message

The simplest message contains just a string in the message parameter. A Subscribe method that listens for a simple string message is shown below - notice the generic type specifying the sender is expected to be of type MainPage. Any classes in the solution can subscribe to the message using this syntax:
MessagingCenter.Subscribe<MainPage> (this, "Hi", (sender) => {
    // do something whenever the "Hi" message is sent
});
In the MainPage class the following code sends the message. The this parameter is an instance of MainPage.
MessagingCenter.Send<MainPage> (this, "Hi");
The string doesn't change - it indicates the message type and is used for determining which subscribers to notify. This sort of message is used to indicate that some event occurred, such as "upload completed", where no further information is required.

하지만 뭐든지 남용하는 건 안될 것 같다.
다음은 MessagingCenter를 사용하지 않아도 될 부분들을 정리해 놓은 내용이다.

Misuses Of MessagingCenter
: https://xamarinhelp.com/common-misuse-messagingcenter/

2017년 1월 9일 월요일

[Links] UnitTest in Visual Studio, C#, Xamarin.

개발하는 app의 Unit Test를 생각하던차에 Visual Studio에서 제공해주는 기능을 사용해 봄.
솔직히 개발하는 측면에서는 MS에서 제공하는 tool들은 정말 넘사벽인듯..

Visual Studio 2015에서 제공하는 Unit Test project를 사용하는 방법임.

다른 Framework을 사용하지 않은 C#으로 구현된 로직의 검증을 위해서 사용가능하여
Xamarin 같은 framework을 사용하는 코드는 이 Unit Test project를 사용해서 검증하기는 어려울 것 같다. (ex Xamarin init이 되어야 Xamarin asset 사용 가능)

Unit Test Basics
: https://msdn.microsoft.com/en-us/library/hh694602.aspx

간단히 말하면 unit test용 project를 solution에 추가한뒤
Unit Test Class를 구현하여 Text Explorer(테스트 탐색기, 테스트 메뉴 > 창 에 있음)를
에서 실행하여 결과를 확인함.


간략히 정리된 Workthrough 문서
Visual Studio Unit Test Case 만들기 Workthrough
: https://msdn.microsoft.com/en-us/library/ms182532.aspx


Testing 시 필수적인 Testing Coverage를 확인할 수 있는 방법이 있지만..
Visual Studio Enterprise에서만 된다..

Using Code Coverage to Determine How Much Code is being Tested
: https://msdn.microsoft.com/en-us/library/hh694602.aspx


찾다 보니 Xamarin에서는 UI Test를 지원하던데. 좀 더 봐야 겠음.
: https://developer.xamarin.com/guides/testcloud/uitest/intro-to-uitest/

2016년 12월 11일 일요일

Xamarin Android 개발 시 에러(Method 'Android.Support.V4.Widget.DrawerLayout.AddDrawerListener' not found.) 발생 시

Xamarin.form으로 Android app 개발 시 아래 exception이 발생 되면

 System.MissingMethodException: Method 'Android.Support.V4.Widget.DrawerLayout.AddDrawerListener' not found.

1. Nuget manager에서 업데이트가 필요한 package가 있다면 업데이트

모두 업데이트 해보고 clean & build 후 설치된 기존 앱을 지우고 다시 설치해보고
excpetion이 다시 발생한다면 2번 방법을 사용

2. 이미 모두 업데이트 되어 있다면 아래 링크의 방법을 사용해서 특정 버전으로 설치

: https://forums.xamarin.com/discussion/77924/missingmethodexception-android-support-v4-widget-drawerlayout-adddrawerlistener-not-found

임시 방편인것 같지만 일단 사용하니 정상 동작함. 답변만 첨부하자면


JamesMontemagnoJamesMontemagnoJames Montemagno ANSWER ✓
So I just reproduced your issue and I am sorry that you and other ran into this. It looks like there was an issue in the templates and I am working with the team to quickly resolve this.
To fix:
  • Simply right click on the android project and select "Manage Nuget Packages"
  • Go to Updates
  • Find Xamarin.Android.Support.v7.AppCompat
  • Select version 23.3.0 from the version picker and hit Install
  • You additionally will need to install the RecyclerView 23.3.0 NuGet Package. Go to Browse and search for: Xamarin.Android.Support.v7.RecyclerView and make sure to pick 23.3.0 to install for the correct version
  • Now do a full clean/rebuild and it should work
  • This should install the correct versions of everything you need for Android
 
3. 설치된 앱을 삭제하고 재 설치

답변에서 빠진게 있었는데 기존 설치되었다면 해당 app을 지우고 다시 설치해야 한다.
안그러면 다른 exception이 발생된다.


2016년 10월 26일 수요일

Xamarin.Forms XAML Basics Part 5, From Data Bindings to MVVM

Xamarin.Forms에서의 XAML 사용이 궁금해서 아래 링크를 보며 대충 필요한 것만 정리함.
https://developer.xamarin.com/guides/xamarin-forms/xaml/

Bindings to MVVM

: https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/

Model-View-ViewModel(MVVM) architectural pattern은 XAML과 함께 정의 되었음.
MVVM pattern은 XAML UI (the View)를 data (the Model)에서 분리하고 중간 매개 역할의 View and Model (the ViewModel)을 통해 접근하는 방법임.
View와 ViewModel은 XAML에 정의된 data binding을 통해서 연결되고 일반적으로 View의 BindingContext는 ViewModel의 instance로 볼 수 있다.

A Simple ViewModel
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/#A_Simple_ViewModel

먼저 System namespace를 사용하기 위해 다음과 같이 XML namespace를 정의 함.
xmlns:sys="clr-namespace:System;assembly=mscorlib"
현재 날짜와 시간을 위해 staic DateTime.Now property를 binding한다.
<StackLayout BindingContext="{x:Static sys:DateTime.Now}">
BindingContext는 좀 특별한 property라서 하위 자식들이 상속되어 StackLayout의 하위 자식들이 동일한 BindingContext를 사용할 수 있음.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             x:Class="XamlSamples.OneShotDateTimePage"
             Title="One-Shot DateTime Page">

  <StackLayout BindingContext="{x:Static sys:DateTime.Now}"
               HorizontalOptions="Center"
               VerticalOptions="Center">

    <Label Text="{Binding Year, StringFormat='The year is {0}'}" />
    <Label Text="{Binding StringFormat='The month is {0:MMMM}'}" />
    <Label Text="{Binding Day, StringFormat='The day is {0}'}" />
    <Label Text="{Binding StringFormat='The time is {0:T}'}" />

  </StackLayout>
</ContentPage>
MVVM 단어를 생각해 보면 Model과 ViewModel은 class이므로 전반적으로 code로 짜여야 할 것 같아 보이고 View는 주로 XAML 파일로 구성되어 ViewModel의 property를 data-binding으로 참조할 것으로 유추할 수 있다.

잘 작성된 Model은 ViewModel를 모르고 잘 작성된 ViewModel은 View를 알 수 없어야 한다. 하지만 대부분의 개발자들은 ViewMode에서 data type을 노출하고 해당 data type은 UI와 밀접한 관게를 가지도록 구성한다. 예를 들면 Model은 ASCII 문자열을 가지고 있는 Database를 접근하고 ViewModel에서는 이 ASCII string을 UI에서 필요한 Unicode string으로 변환해야하는 상황을 들 수 있다. (??)

아래 예제에서는 Model이 없이 View와 ViewModel간의 data binding을 보여줄 것이다.
using System;
using System.ComponentModel;
using Xamarin.Forms;

namespace XamlSamples
{
    class ClockViewModel : INotifyPropertyChanged
    {
        DateTime dateTime;

        public event PropertyChangedEventHandler PropertyChanged;

        public ClockViewModel()
        {
            this.DateTime = DateTime.Now;

            Device.StartTimer(TimeSpan.FromSeconds(1), () =>
                {
                    this.DateTime = DateTime.Now;
                    return true;
                });
        }

        public DateTime DateTime
        {
            set
            {
                if (dateTime != value)
                {
                    dateTime = value;

                    if (PropertyChanged != null)
                    {
                        PropertyChanged(this,
                            new PropertyChangedEventArgs("DateTime"));
                    }
                }
            }
            get
            {
                return dateTime;
            }
        }
    }
}
ViewModel은 일반적으로 INotifyPropertyChanged interface를 상속 받는다. 그리고 class에서는 property가 변경 될 때 마다 PropertyChanged event를 발생 시킨다. 그럼 Xamarin.Forms의 data binding 은 해당 event를 위해 handler를 붙여 변경사항을 추적하여 새로운 값으로 업데이트되도록 한다.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
             x:Class="XamlSamples.ClockPage"
             Title="Clock Page">

  <Label Text="{Binding DateTime,
                        StringFormat='{0:T}'}"
         FontSize="Large"
         HorizontalOptions="Center"
         VerticalOptions="Center">
    <Label.BindingContext>
      <local:ClockViewModel />
    </Label.BindingContext>
  </Label>
</ContentPage>
Lavel에서 BindingContext로 ClockViewModel을 지정하고 있다. 다른 방법으로 ClockViewModel을 Resource collection으로 명시 하여 StaticResource markup extension을 사용하여 BindingContext로 지정할 수 있고 아니면 code-behind file이 VieModel을 정의 할 수 도 있다.


Interactive MVVM
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/#Interactive_MVVM

MVVM은 주로 interactive view를 위해 two-way data binding과 함께 사용된다.
using System;
using System.ComponentModel;
using Xamarin.Forms;

namespace XamlSamples
{
    public class HslViewModel : INotifyPropertyChanged
    {
        double hue, saturation, luminosity;
        Color color;

        public event PropertyChangedEventHandler PropertyChanged;

        public double Hue
        {
            set
            {
                if (hue != value)
                {
                    hue = value;
                    OnPropertyChanged("Hue");
                    SetNewColor();
                }
            }
            get
            {
                return hue;
            }
        }

        public double Saturation
        {
            set
            {
                if (saturation != value)
                {
                    saturation = value;
                    OnPropertyChanged("Saturation");
                    SetNewColor();
                }
            }
            get
            {
                return saturation;
            }
        }

        public double Luminosity
        {
            set
            {
                if (luminosity != value)
                {
                    luminosity = value;
                    OnPropertyChanged("Luminosity");
                    SetNewColor();
                }
            }
            get
            {
                return luminosity;
            }
        }

        public Color Color
        {
            set
            {
                if (color != value)
                {
                    color = value;
                    OnPropertyChanged("Color");

                    this.Hue = value.Hue;
                    this.Saturation = value.Saturation;
                    this.Luminosity = value.Luminosity;
                }
            }
            get
            {
                return color;
            }
        }

        void SetNewColor()
        {
            this.Color = Color.FromHsla(this.Hue,
                                        this.Saturation,
                                        this.Luminosity);
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                    new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
             x:Class="XamlSamples.HslColorScrollPage"
             Title="HSL Color Scroll Page">
  <ContentPage.BindingContext>
    <local:HslViewModel Color="Aqua" />
  </ContentPage.BindingContext>

  <StackLayout Padding="10, 0">
    <BoxView Color="{Binding Color}"
             VerticalOptions="FillAndExpand" />

    <Label Text="{Binding Hue,
                      StringFormat='Hue = {0:F2}'}"
           HorizontalOptions="Center" />

    <Slider Value="{Binding Hue, Mode=TwoWay}" />

    <Label Text="{Binding Saturation,
                      StringFormat='Saturation = {0:F2}'}"
           HorizontalOptions="Center" />

    <Slider Value="{Binding Saturation, Mode=TwoWay}" />

    <Label Text="{Binding Luminosity,
                      StringFormat='Luminosity = {0:F2}'}"
           HorizontalOptions="Center" />

    <Slider Value="{Binding Luminosity, Mode=TwoWay}" />
  </StackLayout>
</ContentPage>
Label들은 값을 표시하기 때문에 기본적으로 binding 방법은 OneWay이다. 하지만 Slider는 초기값에 따라 Slider가 설정되어야 하므로 TwoWay binding을 해야 한다.


Commanding with ViewModels
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/#Commanding_with_ViewModels

많은 경우 MVVM pattern은 ViewModel의 View parallel data object이고 UI object인 data item을 직접 조작하는 것을 제한하고 있다.
종종 View는 다양한 동작들을 처리할 수 있는 button을 ViewModel의 button을 필요로할 수 있지만 ViewModel은 특정 UI와 강결합 될 수 있는 Clicked handler들을 포함해서는 안된다.
ViewModel에서 특정 UI object와 독립적이고 ViewModel에서 호출 할 수 있도록 하는 방법은 command interface를 사용하는 것이다. 이 command interface를 지원하는 Xamarin.Forms element들은 다음과 같다.


  • Button
  • MenuItem
  • ToolbarItem
  • SearchBar
  • TextCell (and hence also ImageCell )
  • ListView
  • TapGestureRecognizer

SearchBar, ListView element는 예외적으로 다음 두가지 property를 가진다.


  • Command of type System.Windows.Input.ICommand
  • CommandParameter of type Object

비슷하게 SearchBar는 SearchCommand와 SearchCommandParameter property들을 정의하고 있고 ListView는 ICommand type의 RefershCommand property를 정의하고 있다.

ICommand interface는 다음 두개의 method와 한개의 event를 정의하고 있다.


  • void Execute(object arg)
  • bool CanExecute(object arg)
  • event EventHandler CanExecuteChanged


ViewModel은 하나 이상의 ICommand type의 property들을 가지고 있다. 이 property들은 각 Button의 Command property에 바인딩되어 있다. CommandParameter property는 부가적으로 ViewModel property에 바인딩된 Button을 확인하기 위해 사용된다. Button은 사용자가 Button을 터치할 때 마다 CommandParameter와 함께 Execute를 호출한다.

CanExecute method와 CanExecuteChanged event는 Button이 사용가능한지 아닌지 확인할 때 사용된다. 초기에 Command property가 설정되고 CanExecuteChanged event가 발생될 때 Button은 CanExecute method를 호출한다.

ViewModel에 command를 추가할 때 ICommand: Command와 Command<T>를 구현해야 한다.These two classes define a bunch of constructors plus a ChangeCanExecute method that the ViewModel can call to force the Command object to fire the CanExecuteChanged event.

다음은 전화번호를 입력하기 위한 simple keypad의 ViewModel이다. 생성자에서 Execute와 CanExecute가 lambda 함수로 정의 된것을 볼 수 있다.
using System;
using System.ComponentModel;
using System.Windows.Input;
using Xamarin.Forms;

namespace XamlSamples
{
    class KeypadViewModel : INotifyPropertyChanged
    {
        string inputString = "";
        string displayText = "";
        char[] specialChars = { '*', '#' };

        public event PropertyChangedEventHandler PropertyChanged;

        // Constructor
        public KeypadViewModel()
        {
            this.AddCharCommand = new Command<string>((key) =>
                {
                    // Add the key to the input string.
                    this.InputString += key;
                });

            this.DeleteCharCommand = new Command((nothing) =>
                {
                    // Strip a character from the input string.
                    this.InputString = this.InputString.Substring(0,
                                        this.InputString.Length - 1);
                },
                (nothing) =>
                {
                    // Return true if there's something to delete.
                    return this.InputString.Length > 0;
                });
        }

        // Public properties
        public string InputString
        {
            protected set
            {
                if (inputString != value)
                {
                    inputString = value;
                    OnPropertyChanged("InputString");
                    this.DisplayText = FormatText(inputString);

                    // Perhaps the delete button must be enabled/disabled.
                    ((Command)this.DeleteCharCommand).ChangeCanExecute();
                }
            }

            get { return inputString; }
        }

        public string DisplayText
        {
            protected set
            {
                if (displayText != value)
                {
                    displayText = value;
                    OnPropertyChanged("DisplayText");
                }
            }
            get { return displayText; }
        }

        // ICommand implementations
        public ICommand AddCharCommand { protected set; get; }

        public ICommand DeleteCharCommand { protected set; get; }

        string FormatText(string str)
        {
            bool hasNonNumbers = str.IndexOfAny(specialChars) != -1;
            string formatted = str;

            if (hasNonNumbers || str.Length < 4 || str.Length > 10)
            {
            }
            else if (str.Length < 8)
            {
                formatted = String.Format("{0}-{1}",
                                          str.Substring(0, 3),
                                          str.Substring(3));
            }
            else
            {
                formatted = String.Format("({0}) {1}-{2}",
                                          str.Substring(0, 3),
                                          str.Substring(3, 3),
                                          str.Substring(6));
            }
            return formatted;
        }

        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this,
                    new PropertyChangedEventArgs(propertyName));
        }
    }
}

ViewModel은 AddCharCommand property가 여러 버튼의 Command propert로 binding되었고 각각이 CommandParameter로 구분됨을 알 수 있다. 이 버튼들은 displayText property 에 보여지는 전화번호 문자열인 InputString property에 문자를 추가한다.

두번째로 ICommand 형식인 DeleteCharCommand property가 있는데 한 문자씩 지우는 버튼이고 문자열이 없는 경우 disable 되어야 한다.

다음은 keypad를 구성하는 XAML이다.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
             x:Class="XamlSamples.KeypadPage"
             Title="Keypad Page">

    <Grid HorizontalOptions="Center"
          VerticalOptions="Center">
      <Grid.BindingContext>
        <local:KeypadViewModel />
      </Grid.BindingContext>

      <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
      </Grid.RowDefinitions>

      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
      </Grid.ColumnDefinitions>

      <!-- Internal Grid for top row of items -->
      <Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="*" />
          <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <Frame Grid.Column="0"
               OutlineColor="Accent">
          <Label Text="{Binding DisplayText}" />
        </Frame>

        <Button Text="⇦"
                Command="{Binding DeleteCharCommand}"
                Grid.Column="1"
                BorderWidth="0" />
      </Grid>

      <Button Text="1"
              Command="{Binding AddCharCommand}"
              CommandParameter="1"
              Grid.Row="1" Grid.Column="0" />

      <Button Text="2"
              Command="{Binding AddCharCommand}"
              CommandParameter="2"
              Grid.Row="1" Grid.Column="1" />

      <Button Text="3"
              Command="{Binding AddCharCommand}"
              CommandParameter="3"
              Grid.Row="1" Grid.Column="2" />

      <Button Text="4"
              Command="{Binding AddCharCommand}"
              CommandParameter="4"
              Grid.Row="2" Grid.Column="0" />

      <Button Text="5"
              Command="{Binding AddCharCommand}"
              CommandParameter="5"
              Grid.Row="2" Grid.Column="1" />

      <Button Text="6"
              Command="{Binding AddCharCommand}"
              CommandParameter="6"
              Grid.Row="2" Grid.Column="2" />

      <Button Text="7"
              Command="{Binding AddCharCommand}"
              CommandParameter="7"
              Grid.Row="3" Grid.Column="0" />

      <Button Text="8"
              Command="{Binding AddCharCommand}"
              CommandParameter="8"
              Grid.Row="3" Grid.Column="1" />

      <Button Text="9"
              Command="{Binding AddCharCommand}"
              CommandParameter="9"
              Grid.Row="3" Grid.Column="2" />

      <Button Text="*"
              Command="{Binding AddCharCommand}"
              CommandParameter="*"
              Grid.Row="4" Grid.Column="0" />

      <Button Text="0"
              Command="{Binding AddCharCommand}"
              CommandParameter="0"
              Grid.Row="4" Grid.Column="1" />

      <Button Text="#"
              Command="{Binding AddCharCommand}"
              CommandParameter="#"
              Grid.Row="4" Grid.Column="2" />
    </Grid>
</ContentPage>

첫 button의 Command property는 DeleteCharCommand에 바인딩되어 있고 나머지는 AddCharCommand에 각 버튼의 Text와 같은 CommandParameter과 함께 바인딩되어 있다.




Invoking Asynchronous Methods


Command들도 비동기적으로 호출 될 수 있다. 이는 Execute method를 정의할 때 async와 await를 사용해서 처리할 수 있다.
DownloadCommand = new Command (async () => await DownloadAsync ());

이는 DownloadAsync method가 Task이고 method 실행이 일시 정지 될수 있음(await)을 알려준다.
async Task DownloadAsync ()
{
  await Task.Run (() => Download ());
}

void Download ()
{
  ...
}