XAMLとコンパイルについて(Xamarin Forms)
あけましておめでとうございます。
今年も「自分の知識の整理」と「技術者としてのフェアの精神(ネットで教えてもらうだけでなく、自ら知り得た知識を共有する精神)」でブログを書いていこうと思います。
また、今年は勉強会とかにも積極的に顔を出してみようかな・・・とか思いつつ・・・
ということで、早速今回はXamarin Formsにおける小ネタを・・・
XAMLとコンパイル
Xamarin Formsで開発を行う場合、UI定義はXAMLで行います。
UI定義以外のロジック(これはコードビハインドにベタ書きするものから、Prismなどのフレームワークを使ってOOに責務を分担したコードまで含めて)は C#言語 で記述します。
XAML と コードを記述し、ビルドメニューを選択するとコンパイル作業が行われ 各DLL が出力されます。このDLLはアセンブリと呼ばれる、.NETにおけるIL(Intermidiate Language)モジュールになります。この辺りは、.NETについてある程度精通している方ならご存知かと思います。C#実装は当然 IL に変換されます。
一方、XAMLはデフォルトでは「リソース」としてDLL内に埋め込まれます。そして実行時にXAML→ILへのコンパイルが行われます(補足として、実行する上では、最終的にはネイティブコードに変換)。
XamlCompilation属性
結論としては、「XamlCompilation属性」というものを用いるとXAMLをビルド時にコンパイルすることが可能になります。
以下、順を追って説明いたします。
コンパイル後のXAMLを確認する
ではまずは、デフォルトの「ビルド時にはXAMLをコンパイルせず、実行時にXAMLがコンパイルされる方式」について確認してみます。
①ソリューションの準備
以下のようなウィザードが生成した単純なXamarin Formsソリューションを用意します。
Xamarin FormsのPCLプロジェクトは「CompExample1」です。
CompExample1Page.xamlの定義は以下とします。Labelを2つ配置した単純な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:CompExample1" x:Class="CompExample1.CompExample1Page"> <Label Text="Welcome to Xamarin Forms!" VerticalOptions="Center" HorizontalOptions="Center" /> <Label Text="test" /> </ContentPage>
②ビルド
ビルドを行います。
CompExample1/bin/Release/CompExample1.dll が出力されます。
これを ILSpy で逆コンパイルしてみましょう。
結果は以下です。
Resources配下に 対象XAMLがそのまま埋め込まれていることが確認できます。
また、フォームのコードビハインドコード内の InitializeComponent() 中で LoadFromXaml() によりXAMLリソースをロードしています。
XAMLを開発時にコンパイルする(XamlCompilation属性)
次に XamlCompilation属性 を使用してみます。
ソリューションは先程の CompExample1 を使用します。
①XamlCompilation属性の付加
XamlCompilation属性は「アセンブリ(アプリケーション)単位」もしくは「フォーム単位」で指定することができます。
アセンブリ単位の指定には、以下のように名前空間に対して「[assembly: XamlCompilation(XamlCompilationOptions.Compile)]」属性を付加します。
// アセンブリ単位に指定(App.xaml.cs) using Xamarin.Forms; using Xamarin.Forms.Xaml; [assembly: XamlCompilation(XamlCompilationOptions.Compile)] namespace CompExample1 { public partial class App : Application { ... } ... }
フォーム単位の指定には、以下のようにコードビハインドクラスに対して「[XamlCompilation(XamlCompilationOptions.Compile)]」属性を付加します。
// フォーム単位に指定(CompExample1Page.xaml) using Xamarin.Forms; using Xamarin.Forms.Xaml; namespace CompExample1 { [XamlCompilation(XamlCompilationOptions.Compile)] public partial class CompExample1Page : ContentPage { public CompExample1Page() { InitializeComponent(); } } }
②ビルド
ビルドを行います。
先程と同様にCompExample1/bin/Release/CompExample1.dll を ILSpy で逆コンパイルしてみましょう。
結果は以下です。
Resourcesが無くなりました。
そして、コードビハインドクラスの InitializeComponent() 内にXAMLで定義したUIを構築するためのコードが追加されています。つまり、XAMLはコードに変換され、ILとなりDLLアセンブリに格納されました。
開発時コンパイルのメリット
XAMLを開発時にコンパイルすると以下のようなメリットがあります。
1) コンパイル時にXAML構文チェックが行われる
例えば以下のようにXAML構文上のエラーがあった場合、XamlCompilation属性を使用していないとビルド(コンパイル)は正常終了してしまいます。そして実行時にエラーとなります。
開発ビルド時に構文チェックが行われることで、開発生産性の向上が見込まれます。
... 省略 <Label Content="test" /> ← WPFは Content でOKでしたが、Xamarin Formsでは Text ですね。 ... 省略
2) 実行時のパフォーマンスが高くなる
Xamarin Formsのデータバインディングを整理する(2)
前回の続き・・・
前回の投稿では、Xamarin Formsにおけるデータバインディングの基本(?)について説明しました。それらは単一コントロール(ターゲット)へのデータバインディングでした。
今回は「コレクションデータ」(つまり繰り返し要素)のデータバインディングについて説明したいと思います。
コレクションデータのデータバインディング(ListView)
「コレクションデータのデータバインディング」という表現をすると、堅苦しく分かりにくいのですが、要するに具体的なコントロールとしては「ListViewコントロール」です。
つまり一覧表示形式です。
一覧表示を行う、またその一覧からユーザーに選択させる、といったUIはよく利用されるものであると思います。
ListViewコントロールを使う
では早速、ListViewコントロールをとりあえず使ってみたいと思います。
比較的シンプルな使い方のサンプルを紹介します。
で、その後で、個人的に気になる部分を掘り下げて探索していこうと思います。
以下のような画面のアプリを作成します。
フルーツクラス(Fruit)オブジェクトのコレクション(配列)を「ソース」とし、ListViewにデータバインディングして、名称(Name)とカロリー(Calory)をリスト表示します。
1.Fruitクラスの作成
データバインディングのソースとなるFruitクラスの実装は以下の通りです。
単純に 名称プロパティ(Nameプロパティ)とカロリープロパティ(Caloryプロパティ)のみを持つモデルクラスです。
[リスト1] fruit.cs namespace FromExample1 { // フルーツクラス public class Fruit { // 名称を取得または設定します。 public string Name { get; set; } // 100gあたりのカロリー数を取得または設定します。 public int Calory { get; set; } } }
2.フォームへのListViewの配置
ListViewコントロールを配置した、フォーム定義はの通りです。
[リスト2] FormExample1Form.xasml <?xml version="1.0" encoding="utf-8"?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="FromExample1.FromExample1Page"> <StackLayout Margin="0,20,0,0"> <ListView x:Name="list1"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <StackLayout> <Label Text="{Binding Path=Name}" /> <Label Text="{Binding Path=Calory, StringFormat='{0} kcal/100g'}" Margin="30,0,0,0"/> </StackLayout> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </ContentPage>
ListView要素
単純にStackLayoutの子要素として ListViewコントロール を配置しています。<ListView.ItemTemplate>
ListViewでは「データバインドされたソースコレクションオブジェクトの各要素を、繰り返し表示するためのアイテムテンプレート」を定義することが出来ます。これが ItemTemplateプロパティ になります。
ItemTemplateは Xamarin.Forms.DataTemplateクラス型です。DataTemplate
配下に、リスト表示する各要素の具体的なビュー表現を定義します。
サンプルでは「ViewCellクラス要素」を使用しています。ListViewコントロールのテンプレート定義には「Xamarin.Forms.Cell派生クラス」を使用する必要があります。Binding構文
ListViewアイテムテンプレートに対して2つのデータバインディングを指定しています。
それぞれ Labelコントロール に対するものですが、1つ目は単純に「Nameプロパティ」(つまりFruit.Name)へのバインディングです。 2つ目は「Caloryプロパティ」(つまりFruit.Calory)へのバインディングです。加えて、StringFormat設定を指定しています。フォーマットの指定方式は、C#一般の {0} というプレースホルダを使用することができます。
【補足(+αなメモ)】
プロパティ構文
Xaml構文の話になりますが、ListView要素配下の <ListView.ItemTemplate> は、「プロパティ構文」と呼ばれる記述法であり、「ListViewクラスのItemTemplateプロパティを、自分の子要素で定義する」という意味になります。
ListView.ItemTemplateプロパティに設定する具象値は、直下の要素「DataTemplate要素オブジェクト」になります。コンテンツプロパティ
ViewCell要素直下にLabel要素が記述されています。これはXaml構文としては、「コンテンツプロパティ構文」と呼ばれるものになります。
Xamarinヘルプで ViewCellクラス を確認すると、以下のようにクラス属性として「ContentProperty」が定義されています。Xaml定義上、明示的なプロパティ名を指定しなかった場合に、ContentProperty属性で指定したプロパティへの値設定と認識されます。
[Xamarin.Forms.ContentProperty("View")]
public class ViewCell : Cell
つまり、ViewCellクラス要素をXamlで定義する場合、以下の2つの定義は同じ意味を持ちます。
[リスト3] 〜プロパティ名を暗黙的に指定〜 <ViewCell> <Label Text="{Binding Path=Name}" /> </ViewCell> ↑↓↑↓↑↓ 上と下は同意 ↑↓↑↓↑↓ 〜プロパティ名を明示的に指定〜 <ViewCell> <ViewCell.View> <Label Text="{Binding Path=Name}" /> </ViewCell.View> </ViewCell>
- BindinbgにおけるPath
データバインディング構文「{Binding}」の中の Path= の記述は省略することが可能です。
以下の2つの記述は同じ意味を持ちます。
どちらの構文を使用するかは各プロジェクトで取り決めておくと良いでしょう。
[リスト4] <Label Text="{Binding Path=Name}" /> ↑↓↑↓↑↓ 上と下は同意 ↑↓↑↓↑↓ <Label Text="{Binding Name}" />
3.データソースの作成とListViewへの割り当て
フォームのコードビハインドクラスでデータソースコレクションオブジェクトを作成し、ListViewコントロールにバインドします。
[リスト5] FormExample1Form.xasml.cs using System.Collections.Generic; using Xamarin.Forms; namespace FromExample1 { public partial class FromExample1Page : ContentPage { private List<Fruit> fruits; public FromExample1Page() { InitializeComponent(); // ListViewにデータバインド this.InitializeDataSource(); this.list1.ItemsSource = this.fruits; } // データソースオブジェクトを初期化します。 private void InitializeDataSource() { this.fruits = new List<Fruit>(); this.fruits.Add(new Fruit() { Name = "Apple", Calory = 54 }); this.fruits.Add(new Fruit() { Name = "Strawberry", Calory = 34 }); this.fruits.Add(new Fruit() { Name = "Peach", Calory = 40 }); this.fruits.Add(new Fruit() { Name = "Pear", Calory = 43 }); this.fruits.Add(new Fruit() { Name = "Grape", Calory = 59 }); } } }
ListViewコントロールの「ItemsSourceプロパティ」に対して、データバインディングを行う為のソースオブジェクトを設定します。
コレクション型データバインディングである為、ItemsSourceプロパティの型は「System.Collection.IEnumerable型」になります。
【補足(+αなメモ)】
- 各コレクション要素の BindingContext
サンプルにも示したように、ListViewは ItemsSourceプロパティ にデータソースオブジェクトを設定することで、各アイテム要素に対するデータバインディングが行われます。
Labelコントロール等に対する単一データバインディングを行う場合、ターゲットとなる各コントロール要素の BindingContextプロパティ にソースオブジェクトを設定することで、データバインディングを実現しました。
ではListViewにおいて、各アイテム要素へのデータバインディングはどのようになっているのでしょうか?
ということで、ちょっと探索を・・・
シンプルにListViewに対するデータバインディングを実装した後、デバッグ実行を行い、適当なところでブレークポイントを貼ります(Buttonを配置してClickイベントをハンドルした辺りとか)。で、ウォッチを使って「list -> TemplatedItems[0]」の値を確認します。サンプルでは ViewCell型であり、その BindingContextプロパティ には Fruitオブジェクト が設定されていることが分かります。
各繰り返し要素のコントロールに対して、ListView内部でコレクションデータ内の該当するデータ項目が BindingContext に設定されているということであり、データバインディングの基本概念である「ターゲットのDataContextプロパティに、ソースオブジェクトを設定する」の動作が行われています。
以上が、最も基本的なListViewコントロールの使用方法です。
ListView と ItemsView< T > と Cell
前述のサンプルでは、コレクションの各要素を表示する為の DataTemplate定義 に「< ViewCell >」を使用しました。ViewCellの具体的な実装は「Xamarin.Forms.ViewCellクラス」になります。
ListViewコントロールでは、DataTemplate定義に「Xamarin.Forms.Cellクラスの派生クラス」を使用する必要があります(ViewCellクラスは、Cellクラスの派生クラスです)。これは、ListViewコントロールの仕様になります。また、Cellクラス自体は抽象クラスなので、そのまま利用することはできません。
なぜ Xamarin.Forms.Cell 派生クラスを指定する必要があるのか?
ビルドインのコントロールを使用する上では、まあ、そういう仕様だから、ということなのですが。
少しだけXamarin Formsの内部仕様に入り込みたいと思います。
ListViewコントロールクラスは、Xamarinヘルプを見れば分かる通り、ItemsView< T >クラスの派生クラスです。
具体的な定義は以下になります。
public class ListView : ItemsSource< Cell >
また、ItemsView
< T >は、データバインドされたソース要素を表示する為の型になります。
つまり、ListViewコントロールクラスは「ソースアイテムを表示するビジュアル要素として Cellクラス を使う仕様とした」ということです。また、Cellクラスは Elementクラス の派生クラスであり、Elementクラスはすべてのフォーム要素の基本クラスとなっているクラスになります。
Built-inのCell派生クラス達
ListViewコントロールの各アイテム要素表示には「Cell派生クラス」が利用できることが分かりました。
先程のサンプルでは ViewCellクラス を使用しましたが、これ以外に以下のビルドインCell派生クラスを使用することができます。
- TextCell
- ImageCell
※ ViewCellは、Viewクラス をコンテントプロパティとする、開発者が自由なレイアウトを構成する為のCellになります。
※ 開発者が定義したカスタムCellも利用可能です。
TextCellの使用方法
TextCellクラスは、リストアイテムを「Textプロパティ(表示テキスト)」と「Detailプロパティ(詳細テキスト)」によって表示します。
以下がXaml定義の例です。(データソースは前述のサンプルと同様にFruitコレクションを想定しています)
[リスト6] TextCellの定義(xaml) <ListView x:Name="list1"> <ListView.ItemTemplate> <DataTemplate> <TextCell Text="{Binding Path=Name}" Detail="{Binding Path=Calory, StringFormat='{0}kcal/100g'}" TextColor="Red" DetailColor="Green" /> </DataTemplate> </ListView.ItemTemplate> </ListView>
Text(string) / Detail(string) / TextColor(Color) / DetailColor(Color) の各プロパティを指定することができます(それぞれの意味は省略・・・)。
1つ特徴として「TextCellは実行時にはネイティブコントロールを使用する為、パフォーマンスに優れている」という点があります(以下Xamarinヘルプより引用)。
TextCells are rendered as native controls at runtime, so performance is very good compared to a custom ViewCell.
そして、実行画面は以下。
ImageCellの使用方法
ImageCellクラスは、TextCellに加えて画像を表示する機能を持ちます。
早速サンプルですが、画像を表示するために Fruitクラス に「ImageSource型のImageプロパティ」を追加します。
[リスト7] Fruit.cs(Imageプロパティを追加) using Xamarin.Forms; namespace FromExample1 { public class Fruit { public string Name { get; set; } public int Calory { get; set; } // 画像プロパティを追加 public ImageSource Image { get; set; } } }
Xamlの定義は以下の通りです。先程のTextCellの使用とほぼ同様です。
テンプレート定義にImageCell要素を使用し、ImageSourceプロパティにはImageプロパティ(ソースオブジェクトである Fruitオブジェクト の Imageプロパティ)をバインド定義します。
[リスト8] ImageCellを使用する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" x:Class="FromExample1.UseImageCellPage"> <StackLayout Margin="0,20,0,0"> <ListView x:Name="list1"> <ListView.ItemTemplate> <DataTemplate> <ImageCell Text="{Binding Path=Name}" Detail="{Binding Path=Calory, StringFormat='{0}kcal/100g'}" TextColor="Red" DetailColor="Green" ImageSource="{Binding Path=Image}"/> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </ContentPage>
コードビハインドクラスの定義は以下の通りです。
画像ファイル(.png)は、Xamarin Formsプロジェクトにリソースとして登録しています。
[リスト9] using System; using System.Collections.Generic; using Xamarin.Forms; namespace FromExample1 { public partial class UseImageCellPage : ContentPage { private List<Fruit> fruits; public UseImageCellPage() { InitializeComponent(); this.InitializeDataSource(); this.list1.ItemsSource = this.fruits; } private void InitializeDataSource() { this.fruits = new List<Fruit>(); this.fruits.Add(new Fruit() { Name = "Apple", Calory = 54, Image = ImageSource.FromResource("FromExample1.Resources.apple.png") }); this.fruits.Add(new Fruit() { Name = "Strawberry", Calory = 34, Image = ImageSource.FromResource("FromExample1.Resources.strawberry.png")}); this.fruits.Add(new Fruit() { Name = "Peach", Calory = 40, Image = ImageSource.FromResource("FromExample1.Resources.peach.png") }); this.fruits.Add(new Fruit() { Name = "Pear", Calory = 43, Image = ImageSource.FromResource("FromExample1.Resources.pear.png") }); this.fruits.Add(new Fruit() { Name = "Grape", Calory = 59, Image = ImageSource.FromResource("FromExample1.Resources.grape.png") }); } } }
画像リソース
画像リソースについて若干の補足をしておきます。
上記サンプルでは画像(png形式)をプロジェクト内に「リソース」として埋め込み登録して利用しています。
つまりビルドにより生成されたアセンブリ(dll)に画像データがリソースとして埋め込んでいます。
サンプルのソリューション構成のキャプチャは以下の通りです。
Xamarin Formsプロジェクトに「Resources」フォルダを作成し、各画像ファイルを追加しています。
また追加した各画像ファイルのビルドアクションは「EmbeddedResource」を指定しています。
上図プロパティウィンドウは、ソリューションツリーから対象pngファイルを選択し、マウス右ボタンメニュー「プロパティ」で表示されます(Xamarin StudioでもVisual Studio For Macでもほぼ同様のUI)。
プロパティウィンドウの中に「リソースID」という項目があります。このIDによりプログラムから対象リソースを読み込むことができます。
つまり、以下のコードで「リソースIDが FromExample1.Resources.apple.png のリソースをImageSourceとして読み込む」ことができるのです。
[リスト10] ImageSource.FromResource("FromExample1.Resources.apple.png")
で、ImageCellを使用したサンプルの実行画面は以下です。
- SwitchCell と EntryCell
あと「SwitchCell」と「EntryCell」というCell派生クラスがあるのですが、これはTableView用に用意されたものなのでここでは省略。
コードでテンプレートを定義する
ここまでXaml構文を利用してListViewへのデータバインディング・テンプレート定義を行なってきましたが、これらは全て
コードで実装することができます。
一般的にはXAMLで定義可能な箇所は、あえてコードではなくXAMLで定義することの方が、可読性なメンテナンス性が高いと思われます。
リスト2 / 5 をコードで実装すると以下のようになります。
[リスト11] using System.Collections.Generic; using Xamarin.Forms; namespace FromExample1 { public partial class FromExample1Page : ContentPage { private List<Fruit> fruits; public FromExample1Page() { InitializeComponent(); // テンプレートを初期化 this.InitializeTemplate(); // ListViewにデータバインド this.InitializeDataSource(); this.list1.ItemsSource = this.fruits; } // テンプレートを初期化します。 private void InitializeTemplate() { DataTemplate dataTemplate = new DataTemplate(() => { StackLayout stackLayout = new StackLayout(); Label nameLabel = new Label(); nameLabel.SetBinding( Label.TextProperty, "Name", BindingMode.OneWay, null, null); Label caloryLabel = new Label() { Margin = new Thickness(30, 0, 0, 0) }; caloryLabel.SetBinding( Label.TextProperty, "Calory", BindingMode.OneWay, null, "{0}kcal/100g"); stackLayout.Children.Add(nameLabel); stackLayout.Children.Add(caloryLabel); return new ViewCell() { View = stackLayout }; }); this.list1.ItemTemplate = dataTemplate; } // データソースオブジェクトを初期化します。 private void InitializeDataSource() { this.fruits = new List<Fruit>(); this.fruits.Add(new Fruit() { Name = "Apple", Calory = 54 }); this.fruits.Add(new Fruit() { Name = "Strawberry", Calory = 34 }); this.fruits.Add(new Fruit() { Name = "Peach", Calory = 40 }); this.fruits.Add(new Fruit() { Name = "Pear", Calory = 43 }); this.fruits.Add(new Fruit() { Name = "Grape", Calory = 59 }); } } }
DataTemplateをインスタンス化する際のコンストラクタ引数に「テンプレートを作成する為の Func< object >匿名関数」を渡します。
DataTemplateコンストラクタ引数Func< T >自体のジェネリック型は object ですが、ListViewが扱える 項目表示要素は Cell派生クラス である為、これに従います(Cell派生クラス以外をreturnするとコンパイルは通りが実行時エラーとなります)。
まとめ
ListViewはよく使われるメジャーなUIなのですが、こうしてまとめようと思うと何気に多くの技術要素が詰まっているように思いました。
HeaderTemplate とか FooterTemplate についても本投稿では触れていませんし、アイテム選択時の「ItemSelectedイベント」についても触れていません。
同様の記事はたくさんあると思いますが、これを読んで頂いた皆さんにとって、1つでもヒントになるようなことがあれば幸いかと。
Xamarin Formsのデータバインディングを整理する(1)
昨今の開発言語やフレームワークにおいて「データバインディング」は非常に重要で有益な技術となっています。
Xamarin Formsにおいても データバインディング がサポートされており、これは WPF / Silverlight などから引き継がれた技術になります(といっても、詳細についてはWPF / Silverlight / Xamarinでは実装内容・実装レベルが異なりますが・・・WPFではDependencyPropertyであったものが Xamarin FormsではBindablePropertyであったり・・・)。
データバインディングは、Xamarin Formsアプリを開発するすべてのエンジニアが利用する技術だと思います。
そして、手軽に利用することもできるし、結構深い技術であるとも思っています。
自分自身の知識の整理も含めて、これから何回かに渡って「Xamarin Formsのデータバインディング」について書いていこうと思います。
※あくまでブログであり、書籍とかじゃないから体系的なまとめはあまり気にしないで、基本+自分の興味の赴くままの技術探求をしていこうかと思います。
データバインディングの基本(ソース と ターゲット)
概念として、データバインディングにおける「元データのオブジェクト側」=「ソース」、「データバインドされる側」 = 「ターゲット」と呼びます。
データバインディングを行う上では、「ソースオブジェクト」と「ターゲットオブジェクト」の2つを結びつける必要があります。その具体的な方法は以下になります。
- ターゲットオブジェクトの「BindingContextプロパティ」に「ソースオブジェクト」を設定する
シンプルなデータバインディング例
早速、以下にシンプルなデータバインディングの例を示します。
ソースは「Personオブジェクト」、ターゲットはフォームに配置した「Labelコントロール」です。
Personは単純なPOCOです。
PersonオブジェクトのNameプロパティの値を、LabelコントロールのTextプロパティにデータバインドします。
Personクラスの定義
ソースとなるPersonクラスの定義です。
// リスト1 person.cs namespace Example1 { public class Person { public string Name { get; set; } } }
フォームの定義
ターゲットとするLabelコントロールを配置したフォームの定義です。
// リスト2 Example1Page.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" x:Class="Example1.Example1Page"> <StackLayout Margin="0,20,0,0"> <Label x:Name="Label1" Text="{Binding Path=Name}" /> </StackLayout> </ContentPage>
LabelコントロールのTextプロパティに対してデータバインドを行なっています。
「 {Binding Path=Name} 」がXAMLにおけるバインディング構文になります。
ターゲットオブジェクトに対するソースオブジェクトの設定は、この後に紹介するフォームのコードビハインドクラス上で行なっています。
ここでは、以下の2点を {Bindng} 構文によって定義しています。
- ターゲットオブジェクトであるLabelの「Textプロパティ」に対してデータバインドを行う
- Path=Nameによって、ソースオブジェクトのNameプロパティ値をバインドすることとする(つまりPersonオブジェクトのNameプロパティ値)
ソースとターゲットの結び付け定義
ソースとなるPersonオブジェクトを作成して、ターゲットとなるLabelコントロールのBindingContextプロパティに設定します。
// リスト3 Example1Page.xaml.cs using Xamarin.Forms; namespace Example1 { public partial class Example1Page : ContentPage { private Person person; public Example1Page() { InitializeComponent(); // Personオブジェクト作成 this.person = new Person(); person.Name = "ryuichi daigo"; // ターゲットのラベルコントロールにソースを設定 this.Label1.BindingContext = person; } } }
実行結果画面は以下の通りです。
BindingContextプロパティ
前述のように「ソースとターゲットの結び付け」は、「ターゲットオブジェクトのBindingContextプロパティにソースオブジェクトを設定する」事で行います。
BindingContextプロパティをXamarinヘルプで確認すると、「Xamain.Forms.BindableObjectクラスで定義されたObject型のプロパティ
」であることが分かります。
つまり、データバインディングにおけるターゲットとなりうるオブジェクトとは、BindableObjectを継承したクラスであると言う事が出来ます。
そして、Labelなどの多くのUIコントロールクラスは Viewクラス を継承しています。さらにViewクラスから継承関係を辿ると、以下の図のようなクラス継承関係となっています。
このことから、Xamarin Formsにおける殆どのUIコントロールが、データバインディングにおける「ターゲット」となる事ができると言う事、及びその意味が分かります。
BindableProperty
ターゲットオブジェクトに対してソースオブジェクトをデータバインドする事が分かりました。
ただ、上述の例でも示したように、ターゲット自体ではなくターゲットオブジェクトのプロパティ(LabelコントロールクラスのTextプロパティ)に対して、ソースオブジェクトのプロパティ(PersonクラスのNameプロパティ)をデータバインドします。
これはごく当たり前の自然なお話ですね。
ただし、ここにも技術的仕組み上の あるルール が存在します。
ターゲットオブジェクトの中で「データバインドの対象とする事ができるプロパティ」とは、「BindableProperty」と呼ばれる特殊なプロパティである必要があります。
Labelクラスのヘルプページを参照すると、Textプロパティの項目には以下のような記述があるはずです(https://developer.xamarin.com/api/type/Xamarin.Forms.Label/)。
String. Gets or sets the text for the Label. This is a bindable property.
そしてBindablePropertyは少し特殊な定義の仕方をする必要があり、例えばstring型のTextプロパティをBindablePropertyとして定義する場合、以下のような実装を行うことになります。
// リスト4 BindablePropertyの実装方法(カスタムコントロール実装時などに使う) public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(Example1Page), default(string), propertyChanged: (bindable, oldValue, newValue) => ((Example1Page)bindable).Text = (string)newValue); public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } }
そう、以下のような単純なプロパティ定義では、データバインド可能なプロパティにはなりません。
// リスト5 こんな定義ではデータバインド可能なプロパティとはならない・・・ public string Text { get; set; }
この辺りの詳細は、カスタムコントロールを実装する際に必要になります。
ひとまず、提供されているコントロールはこのような実装を行なっている、そしてBindablePropertyがデータバインド可能なプロパティである、と理解しておけばまずは良いのではないかと思います。
バインディングの方向
データバインディングとは「ターゲットオブジェクトとソースオブジェクト」の結び付けです。
そして、バインディングには方向の概念があります。
以下の3つの方向の方式があります。これは、Xamarin.Forms.BindingMode列挙体で定義されています(WPFと異なりOneTimeという設定がなくなっています)。
OneWay
「ソース → ターゲット」の単方向に対してデータバインディングが行われます。
プログラムロジック上でソースとなるモデルオブジェクトの値を設定・変更し、そのソース値をターゲット(UIコントロール等)にバインドする事で画面表示を切り替えるような用途が想定されます。OneWayToSource
「ターゲット → ソース」の単方向に対してデータバインディングが行われます。
UI上の入力項目(例えばEntryコントロール)をターゲットとし、画面上で入力された値をソースオブジェクトにバインディングさせるような用途が考えられます。TwoWay
「ターゲット ←→ ソース」の両方向に対してデータバインディングが行われます。
プログラムロジック上でのソースオブジェクトの変更をターゲットであるUIに反映させる、また、逆にターゲットであるUI上での値変更をソースオブジェクトに反映させる、という相互でのデータバインディングが行われます。
3つのバインディングモードを使用したサンプル
3つのバインディングモードの動作の違いを確認できるサンプルプログラムを作成します。
ソースオブジェクト
データバインディングのソースオブジェクトとして ValueModelクラス を用意します。
単純に Value1 / Value2 / Value3 というstring型のプロパティを3つ持ちます。
いくつか補足が必要な実装が含まれていますが、ソース下部で説明を行います。
// リスト6 ValueModel.cs using System; using System.ComponentModel; namespace Example2 { /// <summary> /// ソースオブジェクトとして利用するオブジェクト /// </summary> public class ValueModel : INotifyPropertyChanged { /// <summary> /// INotifyPropertyChangedインターフェイスの実装 /// </summary> public event PropertyChangedEventHandler PropertyChanged; // データを保持するフィールド private string value1; private string value2; private string value3; // Value1プロパティ public string Value1 { get { return this.value1; } set { if (this.value1 != value) { this.value1 = value; OnPropertyChanged("Value1"); } } } // Value2プロパティ public string Value2 { get { return this.value2; } set { if (this.value2 != value) { this.value2 = value; OnPropertyChanged("Value2"); } } } // Value3プロパティ public string Value3 { get { return this.value3; } set { if (this.value3 != value) { this.value3 = value; OnPropertyChanged("Value3"); } } } // プロパティ値の変更を通知します protected virtual void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } }
上記 ValueModelクラス の実装に関して「INotifyPropertyChangedインターフェイスの実装」について補足説明を行う必要があります。
INotifyPropertyChangedインターフェイスとは、その名称の通り「プロパティの変更を通知する」インターフェイスです。
INotifyPropertyChangedインターフェイスを実装する事で、「ソースオブジェクトのプロパティ値に変更があった際に、ターゲットオブジェクトへの通知」を行う事ができます。
つまり、OneWay / TwoWayモードのデータバインディングにおいてソースオブジェクトの変更がリアルタイムにターゲットに伝達されます。
フォームコントロール(ターゲット)の定義
フォーム上に3つの Entryコントロール を配置します。それぞれ Value1へのOneWayモードバインディング / Value2へのOneWayToSourceモードバインディング / Value3へのTwoWayモードバインディング とします。
また、3つのボタンを用意し、それぞれ、クリックされたらプログラム側で Value1 / Value2 / VAlue3 の値を変更します。各ソース値の変更が、ターゲット(Entryコントロール)に反映される様子を確認します。
もう1つ、「show data」ボタンを配置します。これはクリックすると、Value1 / Value2 / Value3 の値をアラートダイアログで表示します。これにより、ターゲット(Entryコントロール)からソースの値への変更の反映を確認します。
// リスト7 Example2Page.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" x:Class="Example2.Example2Page"> <StackLayout x:Name="stack1" Margin="0,20,0,0"> <Label Text="OneWay(ソース→ターゲット)" /> <Entry x:Name="entry1" Text="{Binding Path=Value1,Mode=OneWay}"/> <Label Text="OneWayToSource(ターゲット→ソース)" /> <Entry x:Name="entry2" Text="{Binding Path=Value2,Mode=OneWayToSource}"/> <Label Text="TwoWay(ソース←→ターゲット)" /> <Entry x:Name="entry3" Text="{Binding Path=Value3,Mode=TwoWay}"/> <Button Clicked="onClickedModifyValue1" Text="ソースのValue1値を変更"/> <Button Clicked="onClickedModifyValue2" Text="ソースのValue2値を変更"/> <Button Clicked="onClickedModifyValue3" Text="ソースのValue3値を変更"/> <Button Clicked="onClickedShowData" Text="show data"/> </StackLayout> </ContentPage>
以下がフォームのコードビハインドクラスになります。
ソースオブジェクトは適当な値で生成しています。
stack1(StackLayoutコントロール)の BindingContextプロパティ にソースオブジェクトを設定しています。
実際にデータバインディングを行うのは「StackLayoutの子コントロールである 3つのEntryコントロール」です。しかし、親であるStackLayoutコントロールにソースオブジェクトを設定しています。これは「BindingContextによるデータソースのバインド情報は、子要素に継承される」というデータバインディングの特徴を生かしています。
// リスト8 Example2Page.xaml.cs using Xamarin.Forms; namespace Example2 { public partial class Example2Page : ContentPage { // ソースオブジェクト private ValueModel valueModel; public Example2Page() { InitializeComponent(); // ソースオブジェクト(Valuemodel)を生成します this.valueModel = new ValueModel(){ Value1 = "default value 1", Value2 = "default value 2", Value3 = "default value 3" }; // データバインディングのターゲットオブジェクトとソースオブジェクトを紐付けます this.stack1.BindingContext = this.valueModel; } public void onClickedModifyValue1(object sender, System.EventArgs e) { // Value1データ値をプログラムロジックで変更します this.valueModel.Value1 = "Modify1:" + System.DateTime.Now.ToString("yyyy/MM/dd"); } public void onClickedModifyValue2(object sender, System.EventArgs e) { // Value2データ値をプログラムロジックで変更します this.valueModel.Value2 = "Modify2:" + System.DateTime.Now.ToString("yyyy/MM/dd"); } public void onClickedModifyValue3(object sender, System.EventArgs e) { // Value3データ値をプログラムロジックで変更します this.valueModel.Value3 = "Modify3:" + System.DateTime.Now.ToString("yyyy/MM/dd"); } public async void onClickedShowData(object sender, System.EventArgs e) { // ソースオブジェクトの値をポップアップ表示します await DisplayAlert( "info", string.Format("Value1: {0} /" + "Value2: {1} /" + "Value3: {2}", this.valueModel.Value1, this.valueModel.Value2, this.valueModel.Value3), "close"); } } }
実行してみる
ここではiOSシミュレータで実行します。
①実行
実行直後の画面が以下です。1つ目のEntry(OneWay) および 3つ目のEntry(TwoWay)にはソースの値が反映されています。2つ目のEntryはOneWayToSourceモードである為、ソース→ターゲットへのデータバインディングは行われません。
②次に、3つのEntryにUI上から「変更」の文字を追記します。
③ここで、「show data」ボタンをクリックし、ソースオブジェクトの値を確認します。
Value2 / Value3にはEntryコントロールUI上での「変更」文字の追記が反映されました。この2つはそれぞれ、OneWayToSource / TwoWayモードでデータバインディングされている為、「ターゲット→ソース」へのデータ反映が行われました。
④次に「ソースのValue1値を変更」「ソースのValue2値を変更」「ソースのValue3値を変更」ボタンをクリックします。
これにより、プログラム上で Value1 / Value2 / Value3 の値が変更されます。
以下がその結果画面です。1つ目、3つ目のEntryコントロールの値が変更されました。OneWay / TwoWayモードデータバインディングである為、「ソース→ターゲット」へのデータ変更反映が行われました。
1つ重要な点は、ソース→ターゲットの変更反映を行う為に、ソースオブジェクト(ここではValueModelクラス)に対してINotifyPropertyChangedインターフェイス の実装が必要であるということです。
⑤最後にもう1度「show data」ボタンをクリックします。
④での操作により、ターゲットであるEntryコントロールへのデータ変更の反映の有無に関わらず、ソースオブジェクトの値自体は変更されていることを確認することができます。
コードでデータバインディングを定義
実際には多くの場合、XAMLのデータバインド構文を利用することになりますが、同じことをコードから行う事が出来ます。
先程のリスト1 / リスト2を、コードによるデータバインディング定義で書き換えると次のようになります。
(Xamlへの {Binding Path=Name} の書き換えになります)
// リスト8 「リスト2 Example1Page.xaml」をコードで書き換えると this.Label1.BindingContext = person; this.Label1.SetBinding( Label.TextProperty, "Name", BindingMode.OneWay, null, null);
SetBinding()メソッドは「BindableObjectの拡張メソッド」として定義されています。
BindableObjectとは、つまりデータバインディングのターゲットとなりうる(多くの)UIコントロールが該当します。
Label1の SetBinding() メソッドを呼び出します。
第1引数は、バインディング対象の BindableProperty を指定します。前述でバインド可能なプロパティは「通常のプロパティではなくBindablePropertyという特殊なプロパティである」と説明しました。Xaml定義上では「暗黙的にTextプロパティへの {Binding} 定義」を行いますが、コード上では「明示的にLabel.TextPropertyというBindableProperty型」を指定します。
第2引数は、Pathの指定になります。ソースはオブジェクト Person の Nameプロパティ がバインド対象なので「"Name"」となります。
まとめ & つづく・・・
ということで、Xamarin Formsにおけるデータバインディングの基本をまとめてみました。
基本といっても、まだまだ情報不足な部分もありますが、それでも結構長くなってしまったので、本投稿ではここまでとします。
今後のポストではコレクションコントロールへのデータバインディングとかにも触れていこうと思っています。
【追記】続きはこちら↓↓↓
C# 7 のタプル機能(そろそろC# 7について考えたのです)
C# 7 ではいくつかの機能追加が行われています。
というか、言語仕様レベルでの機能追加がまだ行われるとか、どんどん複雑化していくような危惧も持ってしまいますが・・・「デコンストラクタ」とか本当に必要なのかなぁ・・・とか思っちゃったりもするけど・・・
(技術に興味のない新卒エンジニアへの教育とか考えると、言語仕様のベースはシンプルで良い気もしちゃうので・・・)
ちなみに C# 7 っていうのは、VSでいうと「Visual Studio 2017」ってことになります。
まあ、それはさておき、今回フォーカスするのは「タプル」!
個人的には他言語(主に動的型言語勢)が先行して、.NETが後を追いかけた感のある機能ですが・・・これについてC# 7で進化するようなので、ちょっと探索してみたレポートです。
タプルとは?
タプルとは「複数のオブジェクトを1まとめとして扱うもの」です。
考え方によっては、「厳格な型定義」を軸とすると”邪道な方法論”と言われてもおかしくないけれど、柔軟な考え方をすれば「便利」というものかもしれないかなぁ、と。
あと匿名型とかDictionaryとか、似たような機能も存在するし使い分けとベストプラクティスが難しいけれど・・・
.NET 4で導入されたタプル(System.Tuple)
タプル自体は .NET 4 で「System.Tupleクラス」の導入により.NETにも取り入れられていました。
Tupleクラスの使い方は以下のような形です。
// 3つの値を持つタプルを定義 Tuple<int, string, string> emp = new Tuple<int, string, string>(101, "ryuichi", "daigo"); // タプルを参照 Console.WriteLine(emp.Item1); Console.WriteLine(emp.Item2); Console.WriteLine(emp.Item3); ~実行出力結果~ 101 ryuichi daigo
んー、Item1、Item2、Item3と・・・まあ、そりゃそうだけど、「厳格な開発コーディング規約チーム(そのような組織がある会社であれば)」から、Tupleの使用は 承認を得にくいような仕様の機能ですよね・・・。
C# 7で導入されたタプル
で、C# 7.0におけるタプルの特徴は「名称(ラベル)に対して値を設定するタプル」を定義できることであると思います。
(これは、Swift言語なんかと同じテイストですね)
具体的な実装方法は以下の通り。
// タプルの定義 (string FirstName, string LastName, DateTime Birth) person = (FirstName: "ryuichi", LastName: "daigo", Birth: new DateTime(1990, 1, 10)); // タプル値の参照 Console.WriteLine( string.Format("{0} {1} {2}", person.FirstName, person.LastName, person.Birth.ToString("yyyy/MM/dd")));
(【型名】 名称, 【型名】 名称, 【型名】 名称・・・) といった形式でタプルオブジェクトを宣言し、値は (【名称】: 【値】, 【名称】: 【値】, 【名称】: 【値】, ・・・)と定義します。
利用する際は「person.FirstName」「person.LastName」「person.Birth」のように厳密な名称で参照します。仮にFirstName / LastName / Birthに該当する名称が間違っていた場合、コンパイルエラーが発生します。
また、もちろんインテリセンスも働きます。
使い方はこんな感じなのですが、私のような人間は
「これは言語仕様なのか?コンパイル後のILはどのようになっているのか?」
と気になってしまう訳で・・・
以下がコンパイル後のDLL(アセンブリ)を ILSPY で逆コンパイルしたコードになります。
// ビルド後のDLLをILSPYで逆コンパイルしたコード ValueTuple<string, string, DateTime> person = new ValueTuple<string, string, DateTime>("ryuichi", "daigo", new DateTime(1990, 1, 10)); Console.WriteLine( string.Format("{0} {1} {2}", person.Item1, person.Item2, person.Item3.ToString("yyyy/MM/dd")));
タプル変数定義は「ValueTuple
そして、各プロパティも Item1 / Item2 / Item3 と展開されています。
つまり、タプル内メンバーへの名称付きアクセスは C# 7 言語レベルでのサポート(お気遣い)であり、ILレベル / CLR レベルではタプルオブジェクトは ValueTuple型のオブジェクト、メンバーは Item 1 / Item2 / Item3・・・として扱われます。
また、ValueTuple
勿論この場合、Item1 / Item2 ・・・というようなメンバーアクセサを利用することになりますが。
まとめ
「まとめ」ってことではないですが・・・
えーと、タプルの使いどころのベストプラクティスは私自身堂々と記述できるほど確立していないというのが事実で、「シンプル」と「高機能」のバランスは難しいなあ・・・と思っております。
タプルに限らず、得られる結果への道筋・選択肢が複数ある事が最近は多く、バランスの良い規約や方向性を持つこと、技術的本質を見切ることが重要ではないかなぁ、と思っているのです。
という、モヤモヤした終わり方をするのです(笑)
では、またあ。
※2017/3/15追記
以下もご参考に
ryuichi111std.hatenablog.com
Xamarin + Prism 超入門(とりあえず動かしてみよう!)
[2017/8/3追記] Visual Studio 2017をターゲットとした焼き直し記事を書きました。
本エントリーはVisual Studio 2015をベースとしています。
Visual Studio 2017での作業手順は上記「<2017年8月版>Xamarin + Prism 超入門(とりあえず動かしてみよう!)」が最新となります。
今回はXamarinで本格的な(?)開発を行う際の「gettting started」的な記事を書きたいと思います。
要点としては「Xamarin + Prism」で開発する為の「はじめの一歩」の記事となります。
「Macで Xamarin Studio を利用するケース」、「Windowsで Visual Studio を利用するケース」毎に、誰にでも分かる様に画面キャプチャ付きで手順を説明したいと思います。
同様のブログがすでにいくつも書かれていると思いますが、自分自身の整理と、本記事がどなたかのお役に立てればと思います。
Prismって何?
Xamarinに限らず、ある程度しっかりしたシステムの開発を行う際には、レイヤー化アーキテクチャが導入されます。
Web開発だと「MVC (Model-View-Controller」や「MVP (Model-View-Presenter」などがありますね。
Wikipediaによると、MVCに関する最初の論文が発表されたのが1988年のことだそうです。そう、このようなソフトウェアアーキテクチャは、遥か昔から存在しています。
で、Xamarinにおけるメジャーなレイヤー化アーキテクチャは「MVVM (Model-View-ViewMode)」となります。
まあ、Xamarinというか、XAML関連技術においてMVVMが標準技術として利用されてきました。
Xamlの始まりはWPFですね。続いてSilverlight、Windowsストアアプリ、UWP、とXaml技術は継続的進化を果たしています(私の様なベテランエンジニアに対しては、優しい、積み上げ型の知識が非常に役に立つ正常進化です)。
これらにMVVMアーキテクチャを適用するためのライブラリ(フレームワーク)がいくつか世の中には存在します。
代表的な(そしてXamarinに対応している)フレームワークとして以下のようなものがあります。
- Prism
- MVVM Light
今回紹介するのは Prism になります。
ネット上の情報を見る限り Prism がディファクトスタンダードとなりつつあるように感じます。
Xamarinの神様(Miguel de Icaza氏)が、Prism使おうよって仰ってますしね。
Prism公式サイトは以下です。
何故 Prism を使うのか?
それは Prism 公式サイトの以下の言葉に集約されています。
Prism is a framework for building loosely coupled, maintainable, and testable XAML applications in WPF, Windows 10 UWP, and Xamarin Forms.
ここでは、これ以上深く踏み込まず、次に進みます・・・
Xamarin Studio で Prism を使おう(Mac)
(1) Prism Template Pack のインストール
Xamarin Studioを起動します。
メニュー「Xamarin Studio Community→アドイン」を選択します。
以下の「アドインマネージャー」ウィンドウが表示されます。
「ギャラリー」タブを選択、右上の検索テキストボックスに「Prism」と入力、アドインリストから「Prism Template Pck」を選択し、右下の「インストール」ボタンをクリックします。
[2016/12/31 追記]
アドインマネージャーで検索して Prism Template Pack が表示されない場合は、Prism Template Packから直接アドインをダウンロードして、アドインマネージャーウィンドウ左下の「ファイルからのインストール」ボタンからインストールできます。
(2) Prismプロジェクトの作成
メニュー「ファイル→新しいソリューション」を選択します。
プロジェクトテンプレートとして「Xamarin.Forms→Prism Unity App」を選択を選択して、「次へ」ボタンをクリックします。
App Name等を適当に設定します。ここではApp Nameは PrismExample、Organization Identifierは jp.co.knowlbo としました。
続いてプロジェクトの構成を設定します。プロジェクト名・ソリューション名は共に PrismExample としました。
しばらく待っているとソリューション・プロジェクトが構成されます。
作成されたソリューションは以下の様になります。
通常のXamarin Formsと基本的には似た構成になります。
- PCLとしてのXamarin Formsプロジェクトである「PrismExampleプロジェクト」。
- Android固有実装用プロジェクトである「PrismExample.Droidプロジェクト」。
- iOS固有実装用プロジェクトである「PrismExample.iOSプロジェクト」。
1つ大きく異なるのはPrismExampleプロジェクトに ViewModels / Views フォルダがあることです。
まあ、PrismはMVVMフレームワークですからこれがキモですね。
(3) ビルドエラーが出るから修正
自動生成されたソリューションは、いわゆる HelloWorld 的な実装が出力されています。
とりあえずビルドして実行してみよう!ということで、メニュー「ビルド→すべてリビルド」を選択します。
が・・・2016/12/11段階のPrism Template Packにおいては、ビルドエラーにその道を阻まれます(泣)
ビルドエラーは以下の通りです。
エラーメッセージは以下ですね。
CS0246: The type or namespace name `IPlatformInitializer' could not be found. Are you missing `Prism.Unity' using directive? CS0246: The type or namespace name `IUnityContainer' could not be found. Are you missing `Microsoft.Practices.Unity' using directive?
「型か名前空間が見つからないんだけど・・・usingディレクティブ足りてないんじゃね?」って言ってますね。
なのでエラーが出ている「PrismExample.Droid¥MainActivity.cs」と「PrismExample.iOS¥AppDelegate.cs」に、以下の using を追加します。
using Prism.Unity; using Microsoft.Practices.Unity;
ビルドは無事通るはずです。
※これはそもそも提供されているPrism Templata Packの問題かな?皆さんが利用する際には治っているかも・・・
(4) 実行してみる
メニュー「実行→デバッグなしで開始」を選択します。デフォルトだと iOS emulator での実行が構成されています。
実行結果は以下の通りです。
(5) せっかくなので、ちょっといじろうか・・・
ただ実行するだけでは、あまりにもつまらないので、以下の様な実装を付け加えようと思います。
以下の様な入力フォームとします。
名前を入れて・・・
決定ボタンを押すと、メッセージが表示されます。
実装コードは以下の通りです。
--- MainPage.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:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms" prism:ViewModelLocator.AutowireViewModel="True" x:Class="PrismExample.Views.MainPage" Title="MainPage"> <StackLayout HorizontalOptions="Center" VerticalOptions="Center"> <Label Text="あなたの名前" /> <Entry Text="{Binding YourName}" /> <Button Text="決定" Command="{Binding DecisionCommand}" /> <Label Text="{Binding Message}" /> </StackLayout> </ContentPage>
--- MainPageViewModel.cs --- using Prism.Commands; using Prism.Mvvm; using Prism.Navigation; using System; using System.Collections.Generic; using System.Linq; namespace PrismExample.ViewModels { public class MainPageViewModel : BindableBase, INavigationAware { // 名前入力Entry項目にバインドします private string _yourName; public string YourName { get { return _yourName; } set { SetProperty(ref _yourName, value); } } // メッセージ表示Label項目にバインドします private string _message; public string Message { get { return _message; } set { SetProperty(ref _message, value); } } // 決定ButtonのCommandにバインドします。 private DelegateCommand _decisionCommand; public DelegateCommand DecisionCommand { get { return this._decisionCommand = this._decisionCommand ?? new DelegateCommand(DecisionCommandExecute); } } public MainPageViewModel() { } private void DecisionCommandExecute() { this.Message = string.Format("{0}さん こんにちは", this.YourName); } public void OnNavigatedFrom(NavigationParameters parameters) { } public void OnNavigatedTo(NavigationParameters parameters) { } } }
実装をずらずらと載せてしまいましたが、要点は以下になります。
(すみません、実装を載せておきながら、なのですが、中身の説明は本投稿の軸となる趣旨からはずれるので、要点のみの概略で、PrismやXamarin Formsの詳細技術は別投稿で取り上げさせていただきたいと思います。m( _ _ )m)
MainPage.xamlに 名前入力用 Entry 要素を追加
EntryコントロールのTextプロパティの値、つまり入力された値は {Binding} 構文により MainPageViewModelクラス の YourNameプロパティ にデータバインディングします。MainPage.xamlに 決定 Button 要素を追加
Buttonコントロールの Commandプロパティ に、MainPageViewModelクラスの DecisionCommandプロパティ をデータバインドします。
Button.Clickedイベントではなく、Commandにバインドする理由は「BindablePropertyが・・・・」などの技術的バックボーンがありますが、このお話はまた別のところで・・・MainPage.xamlに メッセージ Label 要素を追加
LabelコントロールのTextプロパティの値に、MainPageViewModeクラスの Messageプロパティ をデータバインディングします。
MainPageViewModel側でMessageプロパティを変更すると、自動的にUIの表示も切り替わります。MainPageViewModelにDelegateCommandプロパティを追加
MainPageViewModelクラスに、DelegateCommand型のDecisionCommandプロパティを追加し、イベントハンドラとしてDecisionCommandExecute()メソッドを実装します。
以下の様にMessageプロパティ・YourNameプロパティを扱うことでロジックを処理しています。xaml側とのデータバインディング処理によってViewModelクラス上でのロジック処理結果がUIに自動的に反映されます。
--- MainPageViewModel.csのコードスニペット --- private void DecisionCommandExecute() { this.Message = string.Format("{0}さん こんにちは", this.YourName); }
こんな感じでView - ViewModelが疎結合に実装されました。
Visual Studio 2015 で Prism を使おう(Windows)
(1) Prism Template Pack のダウンロード & インストール
Visual Studio を起動し、メニュー「ツール→拡張機能と更新プログラム」を選択します。
以下の「拡張機能と更新プログラム」ウィンドウが表示されます。
左のツリーから「オンライン」を選択し、右上の検索テキストボックスに「Prism」と入力します。中央のリストに「Prism Template Pack」が表示されるので「ダウンロード」ボタンをクリックします。
ダウンロードが完了すると以下のインストールウィンドウが表示されるので「インストール」ボタンをクリックします。
インストールが完了すると、Visual Studioの再起動を求められますので、再起動を行います。
(2) Prismプロジェクトの作成
メニュー「ファイル→新規作成→プロジェクト」を選択します。
「新プロジェクト」ウィンドウが表示されるので、プロジェクトテンプレートとして「Prism→Prism Unity App(Xamarin.Forms)」を選択します。
プロジェクト名は「PrismExample」としました。
プロジェクトに含めるプラットフォーム選択ウィンドウが表示されます。今回は「ANDROID / iOS / UWP」を選択することにします。
MacのXamarin studioでは作成する事は出来なかった「UWP / STORE 8.1 / PHONE 8.1」が選択可能です。逆にiOSアプリはMacに接続しないとデバッグ・実行する事が出来ません。
プロジェクトの用意が完了するとMacへの接続情報設定ウィンドウが表示されますが、ここではそのまま設定せずに進みます。
次にUWPのターゲットバージョン選択ウィンドウが表示されます。ここでは、デフォルトのままOKをクリックする事とします。
以上でプロジェクトの生成が完了しました。
(3) ビルド
メニュー「ビルド→ソリューションのリビルド」を選択します。
しばらく時間がかかると思いますが、ビルドは正常終了するはずです。
(4) 実行
せっかくWindows環境なので、UWPアプリケーションとして実行してみましょう。
ソリューションエクスプローラ上で、「PrismExample.UWP」をマウス右クリックし、「スタートアッププロジェクトに設定」を選択します。
実行する前にアプリケーションを配置する必要があります。
メニュー「ビルド→PrismExample.UWPの配置」を選択します。
対象のPCが開発者モードになっていなかった場合、以下のウィンドウが表示されます。
UWPとして実行する場合、Windows上での実行になりますので、Windows設定を開発者モードにする必要があります。
「開発者モード」を選択しましょう。
改めて、メニュー「ビルド→PrismExample.UWPの配置」を選択します。
配置が完了したら、ツールバーから「▶︎ローカルコンピューター」をクリックしましょう。
デバッグモードで PrismExample.UWP が実行されました。
まとめ
Xamarinに限らずアーキテクチャを考慮せず、ただただコードビハインドにロジックを書き連ねるような実装方法が行われる事があります。
レイヤー化アーキテクチャに限らず、testableである事、maintainableである事、適切な責務の分離が行われている事などは非常に重要です。
XamarinにおけるPrismの導入に限らず、システムソリューションを構築する際は、一度立ち止まってベストプラクティスなアーキテクチャを検討したいものです。
今回は Xamarin + Prism の入り口の説明でしたので、今後はより技術的に踏み込んだトピックも取り上げていければと思います。
Azure DocumentDB Emulatorを使ってみた。で、.NET Coreから操作した話。
技術者としての尊敬の対象である Scott Hanselman 氏のブログで「Azure DocumentDB Emulator」についての記事が書かれていたので、自分でも使ってみました。
こんなことをやった
「Azure DocumentDB」は、もはや、広く知られた Azureが提供する NoSQL データベースです。
いわゆる「ちょー早くて、ちょースケーラブルで、RDBの概念は捨ててから使ってね」っていうデータベースですね。
NoSQLは、その登場以来、どこにどう使うべきか?という部分についての議論・検討が行われ、まだまだ一般化していないように思います。
私自身も実は会社の業務としてNoSQLを採用したプロジェクトに関わった事はありません・・・
まあ、それはさておき(また、本投稿はNoSQLの本質を議論する趣旨ではないので・・・)、今回は以下のことを試してみたので、それらをまとめておこうと思います。
- 「Azure DocumentDB Emulator」をインストールしてみた
- 「.NET Core Consoleアプリ」から「Azure DocumentDB Emulator」に接続してデータ操作してみた
- 「Azure DocumentDB(クラウド)」から「Azure DocumentDB Emulator(ローカル)」にデータをエクスポート(リストア)してみた
- データのコンソール出力にはSerilogを使った
「Azure DocumentDB Emulator」のインストール
さあ、今回の主役である「Azure DocumentDB Emulator」をインストールします。
この「Azure DocumentDB Emulator」は、2016/11にリリースされました。
これは クラウドのAzure DocumentDB をローカルPC上でエミュレートするソフトウェアです。オフライン状態でも、Azure DocumentDB開発が可能になります。
以下のリンクページ 上部の「Download the Emulator」をクリックするとインストーラをダウンロードする事が出来ます。
(ちなみにこれはWindows版のみでMac版は残念ながらありません)
ダウンロードした msi ファイルは、ダブルクリックし「Next」や「OK」や、指示通りにクリックしていればインストール完了します。
インストールが完了すると、自動的に起動し、タスクトレイに以下のようなアイコンが表示されます(スタートメニューから「DocumentDB Emulator」を選択しても明示的に起動する事が出来ます)。
また、このアイコンをマウス右クリックすると以下のようなメニューが表示されます。
「Open Data Explorer...」をクリックすると以下のような管理画面が表示されます(インストール直後は、この画面も自動で表示されるはずです)。
見てわかるように「https://localhost:8081/_explorer/index.html#」という8081ポートでホストされた管理コンソール画面となります。
「.NET / .NET Core / Java...」等のタブが有り、ここに表示されている「Code Samples」をクリックすると、何も考えなくても「ビルド→実行」が可能なサンプルプロジェクトをダウンロード可能です。
画面上部の「Explorer」タブをクリックすると、DocumentDB Emulatorに保存されているデータベース内容を確認する事が出来ます(以下の画面)。
初期状態では 空 になります。
「.NET Core Consoleアプリ」+「Azure DocumentDB Emulator」でデータ保存・読み込み
では、せっかくなので最新の Visual Studio 2017 RC + .NET Core を使って DocumentDB Emulator に接続してみます。
①プロジェクトの作成
Visual Studio 2017 RCを起動し、メニュー「ファイル→新規作成→プロジェクト」を選択します。
ここではシンプルに、テンプレートとして「Console App(.NET Core)」を選択し、プロジェクト名は「DocDbExampleCoreConsole」としました。
②Nuget参照の追加
次にNugetで必要なライブラリへの参照を追加します。
メニュー「プロジェクト→Nuget パッケージの管理」を選択します。
まず必要なのは「Microsoft.Azure.DocumentDB.Core」になります。
さらに、これは本投稿の本質ではありませんが、コンソールへのデータログ出力用に「Serilog」および、そのコンソールSinkである「Serilog.Sinks.Console」も追加します。
③モデルクラスの作成
DocumentDBに保存するモデルクラスを作成します。
ブログの投稿内容を想定したモデルを作成することとします。
メニュー「プロジェクト→クラスの追加」を選択し、表示された「新しい項目の追加」ウィンドウで「コード→クラス」を選択し、名前は「BlogPost.cs」とします。
実装は以下のとおりであり、ブログ投稿を表す「BlogPostクラス」、ブログ投稿者を表す「Userクラス」、ブログ投稿へのコメントを表す「Commentクラス」から構成されます。
// BlogPost.cs using System.Collections.Generic; using Newtonsoft.Json; namespace DocDbExampleCoreConsole { /// <summary> /// ブログポスト(1投稿)を表すクラスです。 /// </summary> public class BlogPost { [JsonProperty(PropertyName = "id")] public string Id { get; set; } [JsonProperty(PropertyName = "title")] public string Title { get; set; } [JsonProperty(PropertyName = "contents")] public string Contents { get; set; } [JsonProperty(PropertyName = "author")] public User Author { get; set; } [JsonProperty(PropertyName = "comments")] public List<Comment> Comments { get; set; } } /// <summary> /// ブログ投稿者を表すクラスです。 /// </summary> public class User { [JsonProperty(PropertyName = "id")] public string Id { get; set; } [JsonProperty(PropertyName = "name")] public string Name { get; set; } } /// <summary> /// ブログ投稿へのコメントを表すクラスです。 /// </summary> public class Comment { [JsonProperty(PropertyName = "id")] public string Id { get; set; } [JsonProperty(PropertyName = "text")] public string Text { get; set; } } }
④リポジトリクラスの作成
モデルクラスをDocumentDBに出し入れするためのデータベースアクセス用のリポジトリクラスを作成します。
メニュー「プロジェクト→クラスの追加」を選択し、表示された「新しい項目の追加」ウィンドウで「コード→クラス」を選択し、名前は「BlogPostRepository.cs」とします。
実装は以下の通りです。
// BlogPostRepository.cs using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; using Microsoft.Azure.Documents.Linq; namespace DocDbExampleCoreConsole { public class BlogPostRepository { /// <summary> /// 任意のデータベースID /// </summary> private static readonly string DatabaseId = "Blog"; /// <summary> /// 任意のコレクションID /// </summary> private static readonly string CollectionId = "BlogPost"; /// <summary> /// エンドポイント /// </summary> private static readonly string EndPoint = "https://localhost:8081/"; /// <summary> /// 認証キー(固定) /// </summary> private static readonly string AuthKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="; private static DocumentClient client; /// <summary> /// ブログポストデータを作成します。 /// </summary> /// <param name="blogPost"></param> /// <returns></returns> public static async Task<Document> CreateBlobPostAsync(BlogPost blogPost) { return await client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), blogPost); } /// <summary> /// すべてのブログポストデータを取得します。 /// </summary> /// <returns></returns> public static async Task<IEnumerable<BlogPost>> GetAllBlogPostsAsync() { IDocumentQuery<BlogPost> query = client.CreateDocumentQuery<BlogPost>( UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), new FeedOptions { MaxItemCount = -1 }) .AsDocumentQuery(); List<BlogPost> results = new List<BlogPost>(); while (query.HasMoreResults) { results.AddRange(await query.ExecuteNextAsync<BlogPost>()); } return results; } /// <summary> /// データベース・コレクションの初期化を行います。 /// </summary> public static void Initialize() { client = new DocumentClient(new Uri(EndPoint), AuthKey, new ConnectionPolicy { EnableEndpointDiscovery = false }); CreateDatabaseIfNotExistsAsync().Wait(); CreateCollectionIfNotExistsAsync().Wait(); } /// <summary> /// 存在しなければデータベースを作成します。 /// </summary> /// <returns></returns> private static async Task CreateDatabaseIfNotExistsAsync() { try { await client.ReadDatabaseAsync(UriFactory.CreateDatabaseUri(DatabaseId)); } catch (DocumentClientException e) { if (e.StatusCode == System.Net.HttpStatusCode.NotFound) { await client.CreateDatabaseAsync(new Database { Id = DatabaseId }); } else { throw; } } } /// <summary> /// 存在しなければコレクションを作成します。 /// </summary> /// <returns></returns> private static async Task CreateCollectionIfNotExistsAsync() { try { await client.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId)); } catch (DocumentClientException e) { if (e.StatusCode == System.Net.HttpStatusCode.NotFound) { await client.CreateDocumentCollectionAsync( UriFactory.CreateDatabaseUri(DatabaseId), new DocumentCollection { Id = CollectionId }, new RequestOptions { OfferThroughput = 1000 }); } else { throw; } } } } }
リポジトリクラス実装のポイントは以下の通りです。
DatabaseId / CollectionId
これらは任意の名称をつけます。ここでは「Blog」「BlogPost」としました。EndPoint
エンドポイントはデフォルトでは「https://localhost:8081/」となります。AuthKey
Azure DocumentDB Emulator では認証キーは固定で「C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==」と決められています。Microsoft.Azure.Documents.Client.DocumentClient
DocumentDBへの接続クライアント(繋ぎ役)は「DocumentClientクラス」となります。本サンプルでは Initialize() メソッド内で初期化を行っています。Initialize() / CreateDatabaseIfNotExistsAsync() / CreateCollectionIfNotExistsAsync()
データベースは、データ操作を行う前に初期作成を行う必要があります(RDBでいうところのCREATE DATABASE / CREATE TABLE)。これに該当する処理をCreateDatabaseIfNotExistsAsync() / CreateCollectionIfNotExistsAsync()で実装しています。両メソッドは Initialize() から呼び出されています。つまり、初期化処理として BlogPostRepository.Initialize() を呼び出す必要があります。CreateBlobPostAsync(BlogPost blogPost)
ブログポスト(ドキュメント)をデータベース コレクション」に追加します(RDBのINSERT)。GetAllBlogPostsAsync()
登録されたすべてのブログポスト(ドキュメント)を取得しています(RDBのSELECT)。
⑤main()の実装(リポジトリクラスの呼び出し)
用意したリポジトリクラス(BlogPostRepository)を利用して、BlogPostモデルクラスのデータベースへの出し入れ処理を実装します。
以下がコンソールアプリケーションのmain処理の実装になります。
// Program.cs using System.Threading.Tasks; using System.Collections.Generic; using Serilog; namespace DocDbExampleCoreConsole { class Program { private IEnumerable<BlogPost> blogPosts = null; static void Main(string[] args) { Program program = new Program(); // データベースを初期化 program.InitializeDatabase(); // データを挿入 program.InitializeData().Wait(); // データを抽出 program.GetAllBlogPosts().Wait(); // 抽出したデータをコンソール出力 Log.Logger = new LoggerConfiguration() .WriteTo.Console() .CreateLogger(); foreach (var blogPost in program.blogPosts) { Log.Information("{@BlogPost}", blogPost); } System.Console.ReadLine(); } /// <summary> /// データベース・コレクションを初期化します。 /// </summary> private void InitializeDatabase() { // データベース・コレクションを作成 BlogPostRepository.Initialize(); } /// <summary> /// BlogPostサンプルデータを作成します。 /// </summary> /// <returns></returns> private async Task InitializeData() { // 50件、テスト投稿を作成 for (int i = 0; i < 50; i++) { BlogPost blogPost = new BlogPost() { Title = string.Format("Title {0}", i), Contents = string.Format("Hi! This contents no is '{0}'.", i), Author = new User() { Name = "nanasi san" }, }; blogPost.Comments = new List<Comment>(); blogPost.Comments.Add(new Comment() { Text = "Good job!!" }); await BlogPostRepository.CreateBlobPostAsync(blogPost); } } /// <summary> /// すべてのBlogPostデータを取得します。 /// </summary> /// <returns></returns> private async Task GetAllBlogPosts() { this.blogPosts = await BlogPostRepository.GetAllBlogPostsAsync(); } } }
mainクラス実装のポイントは以下の通りです。
program.InitializeDatabase();
リポジトリークラスの呼び出しにより、データベース・コレクションを作成しています。program.InitializeData().Wait();
リポジトリークラスの呼び出しにより、BlogPostサンプルデータを50件作成しています。program.GetAllBlogPosts().Wait();
リポジトリークラスの呼び出しにより、データベースコレクションに登録された全てのBlogPostデータ抽出しています。ログ出力
抽出の結果として得られたデータは「IEnumerable<BlogPost>」型です。
個別データである BlogPost オブジェクトをSerilogでコンソール出力しています。
Serilogを使用する理由は、オブジェクトのログ出力に際し、子プロパティを含めた形で自動でJSON形式出力してくれる機能を活用する為です。
以下がDocDbExampleCoreConsoleコンソールアプリケーションの実行結果となります。
2016-12-08 02:37:27 [Information] BlogPost { Id: "43eb6293-c532-46dd-969d-e6e4258bcd7d", Title: "Title 0", Contents: "Hi・・This contents no is '0'.", Author: User { Id: null, Name: "nanasi san" }, Comments: [Comment { Id: null, Text: "Good job!!" }] } 2016-12-08 02:37:27 [Information] BlogPost { Id: "86380c10-c45e-46e5-9320-a432be8e838f", Title: "Title 1", Contents: "Hi・・This contents no is '1'.", Author: User { Id: null, Name: "nanasi san" }, Comments: [Comment { Id: null, Text: "Good job!!" }] } 2016-12-08 02:37:27 [Information] BlogPost { Id: "77350d85-6e11-49ac-9f52-d14729cb41b4", Title: "Title 2", Contents: "Hi・・This contents no is '2'.", Author: User { Id: null, Name: "nanasi san" }, Comments: [Comment { Id: null, Text: "Good job!!" }] } 2016-12-08 02:37:27 [Information] BlogPost { Id: "e151fef2-74d8-4735-a89a-971603169c36", Title: "Title 3", Contents: "Hi・・This contents no is '3'.", Author: User { Id: null, Name: "nanasi san" }, Comments: [Comment { Id: null, Text: "Good job!!" }] } 2016-12-08 02:37:27 [Information] BlogPost { Id: "580291de-c99a-4893-b416-48954847413a", Title: "Title 4", Contents: "Hi・・This contents no is '4'.", Author: User { Id: null, Name: "nanasi san" }, Comments: [Comment { Id: null, Text: "Good job!!" }] } 2016-12-08 02:37:27 [Information] BlogPost { Id: "a80815e6-5d03-49e9-b12e-a2892f504dbd", Title: "Title 5", Contents: "Hi・・This contents no is '5'.", Author: User { Id: null, Name: "nanasi san" }, Comments: [Comment { Id: null, Text: "Good job!!" }] } ...以下省略
⑥「Azure DocumentDB Emulator」のExplorerからデータを確認
「Azure DocumentDB Emulator」の管理画面である「https://localhost:8081/_explorer/index.html#」から投入されたBlogPostデータを参照してみます。
以下の画面のようにデータを確認する事が出来ます。
ちなみにタスクトレイアイコン マウス右ボタン「Reset Data...」をクリックすると、簡単にきれいさっぱりデータが消去されるのでお気を付けください。
「Azure DocumentDB」から「Azure DocumentDB Emulator」にデータを(エクスポート)リストア
最後にクラウド上の Azure DocumentDB から Azure DocumentDB Emulator にデータリストアする手順を説明しておきます。
「本来の実装はクラウドのAzure DocumentDBで行っているが、出先等で一時的にローカルエミュレータ開発をしたい」
「今までEmulatorが無かったから、常にクラウドに接続していたけれど、クラウドとエミュレータを平行利用開発したい」
等々の場合に有効かと・・・
以下のページから「Azure DocumentDB Data Migration Tool」をダウンロードします。
Download Azure DocumentDB Data Migration Tool from Official Microsoft Download Center
ダウンロードしたzipファイルを任意のフォルダに解凍し dtui.exe を起動します。
「Nextボタン」をクリックします。
表示された以下のウィンドウにて、Import from は「DocumentDB」、ConnectionString は「Azure上のDocumentDBへの接続文字列(Database=xxx も忘れずに)」、Collection は「インポート対象のコレクション名」を入力して「Nextボタン」をクリックします。
以下の画面において、export先(つまり、ここではローカルの Azure DocumentDB Emulator)の設定を行います。
ConnectionString は、EndPoint=「https://localhost:8081」・AccountLey=「固定値」Database=「任意(ここではBlog)」となります。
入力したら「Nextボタン」をクリックします。
以下のウィンドウが表示されたら「Next」ボタンをクリックします(必要であれば「エラーログファイル」の設定も行って)。
設定内容の最終確認を行う以下の画面が表示されいます。
問題なければ「Next」ボタンをクリックします。
以上で「クラウド Azure DocumentDB」→「ローカル Azure DocumentDB Emulator」へのデータのリストアが完了しました。
まとめ
NoSQLが登場したのはいつだったでしょうか・・・RedisやCassandra、memcashedに興奮し、迷走し・・・Azure黎明期にも当時のNoSQL(DoucmentDBという名称ではなかったと思う・・・)に興奮した記憶があります。
ビジネスアプリケーションにおいては、まだまだNoSQLの導入は遅れているように思います。同時にNoSQLがRDBにとって代わる技術では無い事も事実です。
RDBの堅牢なデータ管理とNoSQLのパフォーマンスを適材適所に組み合わせたシステムの開発、という提案を開発者として行っていきたいものです。
で、まあ新しい技術への探求は相変わらず楽しいのですよね^^
Visual Studio for Mac + Visual Studio Team Servicesでソース管理する
仕事レベルのプログラム開発の場合、ソース管理は必ず必要となります。
しかし、今時は個人開発であってもソース管理は必要ですし、また、サンプルの実装をGithubで公開するといった事も日常的な事でしょう。
今回は開発環境として「Visual Studio for Mac」を、ソース管理に「Visual Studio Team Services(以下VSTS)」を使用する手順について説明します(ソース管理はGitです)。
VSTSにプロジェクトを構成する
[Step 1] プロジェクトを作成
ブラウザで自分のVSTSサイトにアクセスします。
ダッシュボード上の「Recent projects & teams」下の「New」をクリックします。
「Create team project」ウィンドウが表示されますので、必要項目を入力し「Create project」ボタンをクリックします。Version ControlはGitにします。
しばらく待つとプロエジェクトの作成が完了します。「Navigate to project」ボタンをクリックしてプロジェクトポータルに移動しましょう。
[Step 2] セキュリティ設定を実施
右上のユーザーアイコンをクリックし、表示されたポップアプメニューから「Security」をクリックします。
Personal access tokenの「Add」ボタンをクリックします。(Personal access tokenの詳細は後述します)
Descriptionを任意で入力します。アクセストークンの有効期限や認可範囲の細かな設定が可能ですが、ここではひとまずデフォルト値の90日間の有効期限、フルスコープ権限とします。画面下部の「Create token」ボタンをクリックします。
トークンが発行されますので、これをコピーして手元に保存しておきます。
[Step 3] GitのURLを取得(確認)
左上のプロジェクト選択UIから今回作成した「VsMacAndVsts」を選択します。
「Code」タブを選択します。
GitのURLをコピーします。
以上で VSTS 側の設定準備は整いました。
Personal access tokenとは
「Personal access token」は Visual Studio for Mac からVSTSへGitアクセスするために必要となるトークンです。
以下のMicrosoft公式サイトに記述があるように「Microsoftアカウント・Azure AD認証によるネイティブ認証に対応していないクライアントからの資格情報認証の為のトークン」となります。
具体的には、パーソナルアクセストークンはVSTSにアクセスする際の「ユーザー名に対応するパスワード」となります。
Visual Studio for Macプロジェクトの作成
では、作成したVSTSでソース管理を行うプロジェクトをVisual Studio for Macで作成します。
[Step 4] VS for Macプロジェクトを作成
Visual Studio for Macを起動して「New project...」ボタンをクリックします。
ここでは .NET Core Console アプリケーションを作成することとします。
プロジェクト名や保存場所を設定します。ここではプロジェクト名は「HelloVstsConsole」としました。
また、「バージョン コントロールに Git を使用します。」にチェックを付けます。
[Step 5] プロジェクトに対してリモートリポジトリ(VSTS)を設定
プロジェクトが作成できたら、メニュー「バージョン管理→ブランチとリモートを管理する」を選択します。
「Git リポジトリの構成」ウィンドウが表示されるので、「リモート ソース」タブを選択し「追加」ボタンをクリックします。
「リモート ソース」ウィンドウが表示されたら、任意の名前を設定し、「URL」項目にGITリポジトリのURLを設定します。
このURLは上述「VSTSにプロジェクトを構成する」の「Step 3」で取得(確認)したURLになります。
ここまでで、Visual Studio for Macにおけるプロジェクトに対する下準備が完了しました。
[Step 6] ローカルリポジトリへコミット
では、作成したHelloVstsConsoleプロジェクトのGitソース管理へのコミット作業を行いましょう。
メニュー「バージョン管理→ソリューションを確認してコミット」を選択します。
追加されたソースの一覧を表示されますので、「コミット」ボタンをクリックします。
「ファイルをコミットする」ウィンドウが表示されます。
「コミットメッセージ」を入力し、「コミット」ボタンをクリックします。
この操作により、「ローカルリポジトリ」に対するコミットが完了します。(つまり、まだVSTSのソースは更新されていません。)
[Step 7] VSTSへプッシュ
次にVSTSにソースをアップしましょう。
メニュー「バージョン管理→変更をプッシュ」を選択します。
「リポジトリへプッシュ」ウィンドウが表示されます。
「プッシュ先」は先程設定した「VsMacAndVsts」を選択し、「変更をプッシュ」ボタンをクリックします。
「Git 資格情報」ウィンドウが表示されます。
「ユーザー名」には、VSTSへのログインIDを設定します。
「パスワード」には、「VSTSにプロジェクトを構成する」のStep 2で得た「パーソナルアクセストークン」を設定します。
以上で「ローカルリポジトリへのコミット」→「VSTSへのプッシュ」が完了しました。
プッシュされたソースを確認する
では、再度VSTSサイトにアクセスし、Visual Studio for Macで作成したプロジェクトコードがアップされたか確認してみましょう。
以下が VsMacAndVsts プロジェクトのCodeの表示です。Visual Studio for Macで作成したプロジェクトを確認することができます。
ソースを変更して再コミット&プッシュ
ではProgram.csを以下のように修正してみます。
(元々は「Console.WriteLine("Hello World!");」でした。)
using System; class Program { static void Main(string[] args) { Console.WriteLine("変更したよ"); } }
再びローカルリポジトリにコミットします。(メニュー「バージョン管理→ソリューションを確認してコミット」→コミットボタン)
続いてVSTSリモートリポジトリに変更をプッシュします。(メニュー「バージョン管理→変更をプッシュ」)
VSTSサイトを確認すると変更が正しくプッシュされたことを確認することができます。
まとめ
はい。という事で Visual Studio for Mac + Visual Studio Team Services によるソース管理(バージョン管理)の設定手順の説明となりました。
VSS / SVN / TFS / GIT・・・と多種多様なソース管理(バージョン管理)システム、そして、Eclipse / Visual Studio / Visual Studio for Mac / Visual Studio Code / Web Storm 等々の多くの開発環境が乱立しています。
各種技術を学び、適切なツール・適切なソリューションを選択して、幸せな開発生活を送りたいですね。