他の製品からコードを拝借したXamarin.Formsのサンプル

題材を探しを目的に他の記事を読む

今回は記事として公開されているコードを拝借して別の記事を書きます。
なんとなくグレーな行為ではありますが、それが弊社製品ものであれば問題にはならないでしょう。

Excelライクな表計算グリッドコンポーネントで、グレープシティの主力製品でもある「SPREAD(スプレッド)」。

1994年に初期バージョンが登場して以来、時代の流れとともに対応する環境こそ変わりましたが、多くの方にご愛顧いただいている製品です。その .NET Framework用の最新版は、「SPREAD for WPF 2.0J(以下、SPREADの表記はこのバージョンを意味します)」で、2017年7月にリリースしました。

そのSPREADの利用方法を解説する記事の公開が、題材探しをしていた自分の目に止まりました。これは使えるかも。 

 

サンプルを作る場合に、表示用のデータを用意するのは面倒です。 オープンデータを探したり、Web APIから引っ張ってきたりと工夫しますが、手間がかかります。
公開済みのサンプルからちょっと拝借すればその手間を省けることは間違いありません。

今とりかかっているこの記事は「ComponentOne Studio」のXamarin用コンポーネントComponentOne Studio for Xamarin」がテーマです。

前述のSPREADの記事には「MVVM」の文字がありました。
そうなれば、サンプルからModel(モデル)とViewModel(ビューモデル)を転用して、View(ビュー)をXamarin.Formsで作れば、簡単にサンプルができあがることが期待できます。

すべての要素があつまり、拝借する土壌が整いました。

サンプル作成開始

SPREADのサンプルを見ると、グリッド形式にデータを表示し、コンボボックスで変更した値に応じて、データの書式を変更しています。

SPREADサンプルの実行画面

今回はデータの借用が目的なので、このグリッド部分が表示できれば十分です。 しかしここはXamarinの記事ですので、AndroidiOS、UWPの3種類のアプリで、データグリッドを表示することをゴールにします。

まずVisual StudioでXamarin.Forms用のソリューションを作成して開始します。
このあたりは、Xamarinの環境が整っていればさほど難しくありませんし、解説文書も多く公開されていますので割愛します。

Xamarin クロス プラットフォーム開発 – Visual Studio

次に「ComponentOne Studio for Xamarin(以下、C1 Xamarin)」の設定です。
作成したソリューションに、NuGetサーバーからパッケージを追加します。コンポーネントを組み込む際に必要な手順です。

プロジェクトは以下の4つがありますが、図のようにソリューションを対象としてNuGetパッケージを追加し、すべてのプロジェクトに「C1.Xamarin.Forms.Grid」を追加してください。
Xamarin.Forms用のコンポーネントを追加すると、自動的にAndroidiOSプロジェクトには、それぞれに適したコンポーネントが追加されます。

f:id:ComponentOne_JP:20170912180036p:plain

NuGetの追加が完了したあと、C1 Xamarinのライセンス設定です。ビルド以降の作業で必要になります。
C1 Xamarinを利用する場合のライセンス設定手順や詳細はWebサイトに説明があります。
トライアル版や製品版を使ってお手元で試す場合は、こちらをご参照ください。

ComponentOne Studio for Xamarinのライセンス

ひとつ注意事項です。
Xamarin.Formsでコンポーネントを使う場合、iOSとUWPのプロジェクトに初期化処理を記述する必要があります。iOSプロジェクトのAppDelegate.csとUWPプロジェクトのApp.xaml.csに以下の行を追加することを忘れないようにします。記述場所は、Xamarin.Formsの初期化処理を目印に探してください。Androidプロジェクトでは不要です。

// iOS 用 AppDelegate.cs 
global::Xamarin.Forms.Forms.Init();
C1.Xamarin.Forms.Grid.Platform.iOS.FlexGridRenderer.Init();
// UWP 用 App.xaml.cs 
Xamarin.Forms.Forms.Init(e);
C1.Xamarin.Forms.Grid.Platform.UWP.FlexGridRenderer.Init();

コードを移植する

ここまでで準備が整いました。次に、SPREADのサンプルからコードを移植します。
移植するコードは、すべて共有ライブラリ(PCL)に記述します。ソリューションエクスプローラーで(移植可能)と記載されているプロジェクトです。

f:id:ComponentOne_JP:20170912175021p:plain

ここに移植するためのファイルを追加します。
区別しやすいよう、以下のようなフォルダ構造にしました。

  • PCLプロジェクト
    • Models
      • CustomerRequestModel.cs
    • Views
      • CustomerRequestView.xaml
        • CustomerRequestView.xaml.cs
    • ViewModels
      • CustomerRequestViewModel.cs

そのままコピーする

結論から言うと、ModelとViewModelに関しては、名前空間namespace)以外のコードをコピーして、そのまま貼り付けるだけです。
Xamarin.Formsは .NET Frameworkと同様の機能を提供していますので、usingで参照する部分もそのまま参照設定されます。

以下が実際に貼り付けたModelのコードです。名前空間以外は、前述のSPREADの記事と同じです。データモデルの定義しているC#で書かれたクラスなので、互換性があるのは当然です。
また、このサンプルではデータをこのクラスで作成して提供しています。表示用データとしてそのまま使えそうです。

using System;
using System.Collections.ObjectModel;
namespace c1x_FlexGrid
{
public class CustomerRequest
{
public string ID { get; set; }
public string Title { get; set; }
public DateTime OpenDate { get; set; }
public string Note { get; set; }
}
public class CustomerRequestCollection
{
private ObservableCollection<CustomerRequest> _requests;
public CustomerRequestCollection()
{
_requests = new ObservableCollection<CustomerRequest>
{
new CustomerRequest(){Title="新機能の使用方法についてご質問",OpenDate=DateTime.Today.AddDays(0) },
// 記事では省略:データの生成部分
};
}
public ObservableCollection<CustomerRequest> GetCustomerRequests()
{
return _requests;
}
}
}

ViewModelのコードCustomerRequestViewModel.csも同様にコピーします。ただし、今回はデータを表示するだけなので、利用しないコンボボックスに関わる部分は削除します。

SPREADのサンプルではコンバータークラスの作成に、Xamarin.Formsでは機能が提供されていないIMultiValueConverterインタフェースを利用しています。そのため、その部分は別途作り直す必要があります。

今回使わない部分を削除したViewModelのコードは以下です。MVVMパターンの特徴であるINotifyPropertyChangedインタフェースを実装したシンプルなクラスです。

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace c1x_FlexGrid
{
public class CustomerRequestViewModel : INotifyPropertyChanged
{
// 問合せのコレクション
private ObservableCollection<CustomerRequest> _requests;
public CustomerRequestViewModel()
{
_requests = (new CustomerRequestCollection()).GetCustomerRequests();
}
public ObservableCollection<CustomerRequest> CustomerRequests
{
get { return _requests; }
set
{
if (_requests != value)
{
_requests = value;
NotifyPropertyChanged("CustomerRequests");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

View(ビュー)を作成

これで表示するデータが揃いましたのでビューを作成します。
XAMLで定義する共通のビューを、前述のようにPCLプロジェクトのViewsフォルダの下に新規に追加します。

そのContentPageタグの中に、データを表示するFlexGridを定義して設定します。
FlexGridを使うための名前空間の定義を行い、あわせてViewModel用の名前空間も定義します。

xmlns:c1="clr-namespace:C1.Xamarin.Forms.Grid;assembly=C1.Xamarin.Forms.Grid"
xmlns:vm="clr-namespace:c1x_FlexGrid"

次に、ViewModelをContentPageにバインドします。
WPFでは’DataContext’ですが、Xamarin.FormsではBindingContextであることに注意してください。

<ContentPage.BindingContext>
<vm:CustomerRequestViewModel />
</ContentPage.BindingContext>

最後にFlexGridの内容です。
データグリッドに表示するデータのソースは、BindingContextに指定したViewModelにあります。
ItemsSourceプロパティに、’ObservableCollection’クラスのリストデータセットであるCustomerRequestsを接続します。
データの構造に合わせて列を自動的に作ることもできますが、ヘッダー部分に任意の文字列を表示した場合は、各列(GridColumnクラス)を定義して、データ項目をBindingに記述します。

グリッドの幅は、デバイスごとに表示される画面に最大化されるようにHorizontalOptions="FillAndExpand"を指定しています。
また各列の幅は以下です。スターサイズ機能を利用してデータグリッド全体を表示し、表示領域の幅に応じて各列の幅を比率で自動調整しています。

列名 Width指定 内容
件名 * 表示幅を自動計算
受付日 0.5* 件名列の半分の幅
備考 Auto セル内容に応じて幅を算出

以下のXAMLコードは、FlexGridに3つの列を定義した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:c1="clr-namespace:C1.Xamarin.Forms.Grid;assembly=C1.Xamarin.Forms.Grid"
xmlns:vm="clr-namespace:c1x_FlexGrid"
x:Class="c1x_FlexGrid.CustomerRequestView">
<ContentPage.BindingContext>
<vm:CustomerRequestViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<Grid VerticalOptions="FillAndExpand">
<c1:FlexGrid x:Name="flexgrid"
ItemsSource="{Binding CustomerRequests}"
HeadersVisibility="Column"
AutoGenerateColumns="False"
AutoSizeMode="Both"
HorizontalOptions="FillAndExpand">
<c1:FlexGrid.Columns>
<c1:GridColumn Header="件名" Binding="Title"
AllowResizing="True"
Width="*"/>
<c1:GridColumn Header="受付日" Binding="OpenDate"
Format="yyyy/MM/dd"
Width="0.5*"/>
<c1:GridColumn Header="備考" Binding="Note"
Width="Auto"/>
</c1:FlexGrid.Columns>
</c1:FlexGrid>
</Grid>
</ContentPage.Content>
</ContentPage>

なお、このXAMLに対応するコードビハインドに記載は不要です。

最後に、作成したビューをアプリのエントリーポイントとするために、PCLプロジェクトのApp.xaml.csに記述します。

        public App()
{
InitializeComponent();
// ライセンス設定
C1.Xamarin.Forms.Core.LicenseManager.Key = License.Key;
// NavigationPageの中に作成したビューを配置する
MainPage = new NavigationPage(new  c1x_FlexGrid.CustomerRequestView());
}

ViewをXAMLで作成している恩恵

以上で完成しました。
データを表示するだけですが、3種のプラットフォームで動作するサンプルの完成です。

f:id:ComponentOne_JP:20170913102006p:plain

左側がAndroid、右上段がUWPで下段がiOSです。
iOSアプリで横向き表示にした場合でも、前述のスターサイズ機能でグリッド全体を表示するように各列の比率を算出しています。

このように、MVVMデザインパターンで作成したアプリは、ビューとデータを分離できるため使いまわすことが容易です。
WPFとXamarin.Formsは、ビューは同じXAML技術をベースにしています。そのため今回は簡単にサンプルと記事を作ることができました(C#を使って記述することも可能です)。
使い回しの効く技術を習得しておくと、活躍する機会も増えることでしょう。


作成したソリューションはこちらで公開しています。
サンプルで使ったC1 Xamarin のライセンスも含まれますので、NuGetパッケージを復元すれば実行可能です *1

ダウンロード(zipファイル:321KB)


XAML技術(WPF、UWP、Xamarin)のイベント開催

WPF、UWP、Xamarinと、XAMLを利用する各アプリ開発技術のエキスパートをお迎えし、開発事例を中心としたセッション主体のイベントを行います。  

※この記事の投稿時点で、大変ありがたいことに既に満席(100名)となっています。申し訳ありませんが、参加ご希望の方はキャンセル待ちへのご登録をお願いいたします。


*1:NuGetパッケージを利用するには、GrapeCityのNuGetフィードソース( http://nuget.c1.grapecity.com/nuget/ )を、Visual Studioの環境に追加する必要があります。