必要な環境
Windowsの場合
- Windows 7以上
- Visual Studio 2015またはVisual Studio 2017
- Xamarin
macOSの場合
- OS X Mountain Lion以上
- Xamarin StudioまたはVisual Studio for Mac
筆者の環境の都合上、本記事ではmacOS SierraとXamarin Studio 6.3を使用して解説します。
Xuniとは
「Xuni(ズーニー)」は、モバイルアプリケーション向けのコントロールセットで、チャート、ゲージ、グリッド、カレンダーなどの高機能なコントロールが含まれます。
今回紹介するのは、Xamarin.Forms向けのライブラリですが、Xuniは他にも次のようなプラットフォームに対応しています。
- Android-Java
- iOS-SwiftまたはObjective−C
- Xamarin.Android
- Xamarin.iOS
Xamarin、Xamarin.Formsとは
今更説明は不要と思いますが、Xamarinとは、Android・iOS・Windowsなど向けのアプリケーションを共通の言語(C#やF#)で開発できるクロスプラットフォーム開発ツールです。
AndroidのAPIをC#から呼び出せるようにした「Xamarin.Android」、同じくiOSのAPIをC#から呼び出せるようにした「Xamarin.iOS」を中心に、.NETの技術を利用してクロスプラットフォームアプリケーションを開発するためのライブラリや各種ツールキットを提供しています。
Xamarin.Formsは、XAMLやデータバインディングなどの技術を活用して、画面もクロスプラットフォームで共通化できるフレームワークです。
プラットフォーム固有の機能をフルに活用したアプリの開発には向きませんが、Android・iOSに加え Universal Windows Platform(UWP)やmacOSにも対応が始まっており、Xamarinの中でも最も注目度の高い要素になっています。
Xuniのインストール
XuniはNuGetパッケージマネージャからインストールしますが、NuGetパッケージ公開情報に説明があるように、日本語版のパッケージは http://nuget.c1.grapecity.com/nuget/ から提供されており、Visual StudioまたはXamarin StudioのNuGetパッケージマネージャのソースに、このURLを追加する必要があります。
一方、グレープシティのサイトからXuniのトライアル版をダウンロードすることができます(要ユーザー登録)。
このトライアル版には、全てのプラットフォーム向けのサンプルプログラムが含まれているので、まずはこちらをダウンロードしてインストールすることをお勧めします。
macOSでは、インストールすると「書類」のディレクトリの中に「XuniJp」というディレクトリが作成され、各プラットフォーム向けのサンプルプログラムが格納されます。Xamarin.Forms向けはXamarin/Fomrsの中です。
Xamarin/NugetPackagesディレクトリには、このサンプルが動作可能なNuGetパッケージ群が格納されています。オンラインのNuGetパッケージで正常に動作しないようであれば、このディレクトリをNuGetのソースに設定するとよいでしょう。図2はXamarin StudioでこのディレクトリをNuGetソースに設定した画面です。
FlexGridのサンプルを動かしてみよう
それでは実際に、Xuniのインストーラに含まれるFlexGridのサンプルをAndroidとiOSそれぞれで動かしてみましょう。
/Users/<ユーザー名>/Documents/XuniJp/Xamarin/Samples/Forms/FlexGrid101/FlexGrid101.slnをXamarin Studioで開きます。
Androidでサンプルを動かしてみる
ソリューション内の「FlexGrid101.Android」をスタートアッププロジェクトにしてAndroid端末またはエミュレータで実行します。
サンプルが起動すると、メニューの画面になり(図3の左画像)、更に一番上の「Getting Started」をタップするとFlexGridの標準的な使用例が表示されます。
メニューの他の項目では、FlexGridのさまざまな機能が確認できますので、一通り動かしてみましょう。
iOSでサンプルを動かしてみる
ソリューション内の「FlexGrid101.iOS」をスタートアッププロジェクトにしてiPhone端末またはiOSシミュレータで実行します。
iPhone端末またはiOSシミュレータの言語設定を「日本語」にすると、図4のように、アプリケーションも日本語表示されます(以降の画像は言語設定「英語」を使用しているため、Android/iOSともに英語<"FlexGridの基本機能"→"Getting Started">となります)。
プロジェクトのビルド時に「'Icon-xxxx.png' not found on disk (should be at '…/FlexGrid101.iOS/Resources/Icon-xxxx.png') (FlexGrid101.iOS)」というエラーが出る場合は、FlexGrid101.iOS/ResourcesディレクトリにあるIcon-xxxx.pngファイル群をFlexGrid101.iOS/Resources/Images.xcassets/AppIcons.appiconsetディレクトリにコピーします。
iOSシミュレータでの実行時、「"FlexGrid 101" Needs to Be Updated」と表示される場合は、FlexGrid101.iOSプロジェクトの設定→iOS Buildで、「サポートされるアーキテクチャ」を「x84_64」に変更します(図5参照)。
iOSでもAndroidと同じ機能が使用できます。Xamarin.Forms向けのコンポーネントなので、AndroidやiOS固有のコードは一切必要なく、共通なコードのみでAndroid/iOSで動作可能なグリッドを使用できます。
気象庁の「最新の気象データ」をグリッドに表示してみよう(1)
ではここからは、実際のデータを使用して、FlexGridのさまざまな機能を紹介します。
使用するデータは、気象庁が提供している「最新の気象データ」のCSVです。
このデータは、気温・降水量・風速などがCSV形式でリアルタイムに提供されており、利用規約に基いて使用できます。
最新の最高気温CSVをグリッドに表示してみよう
最新の最高気温のCSVは、次のURLで取得できます。
http://www.data.jma.go.jp/obd/stats/data/mdrr/tem_rct/alltable/mxtemsadext00_rct.csv
これで得られるCSVデータを、FlexGridに表示してみましょう。
FlexGrid101.slnのソリューションを改造して、気象庁のCSVデータを表示できるようにします。
CSVデータ用のクラスを作る
まず、CSVデータの1行を示すクラスを、FlexGrid101.XamarinプロジェクトにHighestTemperature.csという名称で作成します。
元のCSVは項目数が多いので、サンプルで使用する項目だけをプロパティとして用意します。
作成するHighestTemperatureは次のようになります。
// FlexGrid101.Xamarin プロジェクトに作成する
public class HighestTemperature
{
public string PlaceNo { get; set; } // 観測所番号
public string Pref { get; set; } // 都道府県
public string Point { get; set; } // 地点
public int NowYear { get; set; } // 現在時刻(年)
public int NowMonth { get; set; } // 現在時刻(月)
public int NowDay { get; set; } // 現在時刻(日)
public int NowHour { get; set; } // 現在時刻(時)
public int NowMinute { get; set; } // 現在時刻(分)
public float TodayHighestTemperature { get; set; } // 今日の最高気温(℃)
public float DiffToAverageYear{ get; set; } // 平年差(℃)
}
CSV読み込みライブラリを追加する
次にCSVデータの読み込みは、CsvHelperというオープンソースライブラリを使用して行います。
そのために、FlexGrid101.Xamarin、FlexGrid101.Android、FlexGrid101.iOSそれぞれのプロジェクトのメニュー→プロジェクト→NuGetパッケージの追加で、「csvhelper」で検索し、表示される「CsvHelper」をプロジェクトに追加します。
CSVのURLからデータを取得する
まず通信を行う権限をAndroidプロジェクトに付与します。
FlexGrid101.Androidプロジェクトの設定 → Android Applicationで「必要なアクセス許可」の項目群から「Internet」にチェックを入れます。
また、FlexGrid101.iOSプロジェクトの設定 → iOS Buildで、コードセットの「cjk」にチェックを入れます。これはShift-JISのCSVファイルを取り扱うのに必要な設定です。
次に、CSVファイルをダウンロードして読み込む処理をHighestTemperatureクラスに次のように実装します。
public static async Task<IList<HighestTemperature>> Read()
{
var client = new HttpClient();
var records = new List<HighestTemperature>();
var encoding = Encoding.GetEncoding("Shift-jis");
using (var stream = await client.GetStreamAsync(
@"http://www.data.jma.go.jp/obd/stats/data/mdrr/tem_rct/alltable/mxtemsadext00_rct.csv"))
using (var csvReader = new CsvReader(new StreamReader(stream, encoding)))
{
while (csvReader.Read())
{
var record = new HighestTemperature();
record.PlaceNo = csvReader.GetField<string>(0); // 観測所番号
record.Pref = csvReader.GetField<string>(1); // 都道府県
record.Point = csvReader.GetField<string>(2);
record.NowYear = csvReader.GetField<int>(4); // 現在時刻(年)
record.NowMonth = csvReader.GetField<int>(5); // 現在時刻(月)
record.NowDay = csvReader.GetField<int>(6); // 現在時刻(日)
record.NowHour = csvReader.GetField<int>(7); // 現在時刻(時)
record.NowMinute = csvReader.GetField<int>(8); // 現在時刻(分)
record.TodayHighestTemperature = csvReader.GetField<float>(9); // 今日の最高気温(℃)
var diff = csvReader.GetField<float?>(14); // // 平年差(℃)
record.DiffToAverageYear = diff ?? 0f;
records.Add(record);
}
}
return records;
}
このコードは、HttpClientを使用して気象データCSVをダウンロードし、CsvHelperを使用してデータをパースする処理です。元CSVは項目数が多いので本サンプルに必要な項目のみを取り出しています。尚、取り出しは項目の並び順に依存しているため、データの仕様が変わるとこのコードは動作しなくなる可能性があることに注意してください。
FlexGridにデータを表示する
上記までで取り出したデータをFlexGridに設定して表示させます。
Getting Startedの画面を改造して使用します。
GettingStarted.xaml.csの既存のコードを全て削除し、次のように記述します。
public partial class GettingStarted : ContentPage
{
private XuniCollectionView<HighestTemperature> _collView;
public GettingStarted()
{
InitializeComponent();
this.Title = AppResources.GettingStartedTitle;
InitializeData();
}
private async void InitializeData()
{
var data = await HighestTemperature.Read();
_collView = new XuniCollectionView<HighestTemperature>(data);
grid.ItemsSource = _collView;
}
}
InitializeDataメソッドで、CSVからデータを取得し、グリッドのItemsSourceに設定することでデータを表示させています。
これをAndroid,iOSそれぞれで実行すると、次のようになります。
気象庁の「最新の気象データ」をグリッドに表示してみよう(2)
データを気温の低い順に並び替えてみよう
次に、データの並び替えをしてみましょう。FlexGrid標準の機能でヘッダー列をタップすれば並び替え順を昇順/降順で切り替えることができますが、コードからも行えます。同時に先頭列の固定と、列幅の自動調整もしてみましょう。
GettingStarted.xaml.csのInitializeDataに次のように追記します。
async void InitializeData()
{
var data = await HighestTemperature.Read();
_collView = new XuniCollectionView<HighestTemperature>(data);
grid.ItemsSource = _collView;
// 追記ここから
grid.FrozenColumns = 2; // 先頭列の固定
await _collView.SortAsync(x => x.TodayHighestTemperature,
SortDirection.Ascending); // 最高気温の昇順にソート
grid.AutoSizeColumns(0, grid.Columns.Count - 1); // 列幅自動調整
}
先頭列の固定はXAMLにFrozenColumns="2"と記述することもできます。
このコードを実行すると、次の図のようになります。
平年より気温が高い地域のセルを着色してみよう
次は、セルの背景色を値に応じて変えてみましょう。
先ほどと同じくGettingStarted.xaml.csのInitializeDataに次のように追記します。
public partial class GettingStarted : ContentPage
{
<省略>
async void InitializeData()
{
var data = await HighestTemperature.Read();
_collView = new XuniCollectionView<HighestTemperature>(data);
grid.ItemsSource = _collView;
grid.FrozenColumns = 2;
await _collView.SortAsync(x => x.TodayHighestTemperature, SortDirection.Ascending);
grid.AutoSizeColumns(0, grid.Columns.Count - 1);
// 追記ここから
grid.CellFactory = new CustomCellFactory();
}
}
class CustomCellFactory : GridCellFactory
{
public override GridCellView CreateCell(GridCellType cellType, GridCellRange range)
{
var cell = base.CreateCell(cellType, range);
if (cellType == GridCellType.Cell && range.Column == 8)
{
var cellValue = Grid[range.Row, range.Column] as float?;
if (cellValue.HasValue)
{
cell.BackgroundColor = cellValue <= 0.0 ? Color.Aqua : Color.Pink;
}
}
return cell;
}
public override void BindCellContent(GridCellType cellType, GridCellRange range, View cellContent)
{
var label = cellContent as Label;
if (label != null && cellType == GridCellType.Cell && range.Column == 8)
{
var cellValue = Grid[range.Row, range.Column] as float?;
if (cellValue.HasValue)
{
label.BackgroundColor = cellValue <= 0.0 ? Color.Aqua : Color.Pink;
}
}
}
}
セルをカスタマイズする方法のひとつはGridCellFactoryを拡張することです。
ここではGridCellFactoryを拡張してCustomCellFactoryクラスを作成し、列インデックスが8(平年差)の時に、その値が0以下だったらセル背景色を水色に、0より大きかったらピンク色に設定します。
そしてCustomCellFactoryクラスをFlexGridのCellFactoryプロパティに設定します。
このコードを実行すると次の図のようになります。
気象庁の「最新の気象データ」をグリッドに表示してみよう(3)
地域でグループ化してみよう
更に、行の値を使ってグループ化を行い、グリッドを展開できるようにしてみましょう。
InitializeDataを次のように修正します。
async void InitializeData()
{
var data = await HighestTemperature.Read();
_collView = new XuniCollectionView<HighestTemperature>(data);
grid.ItemsSource = _collView;
grid.FrozenColumns = 2;
await _collView.SortAsync(x => x.TodayHighestTemperature, SortDirection.Ascending);
grid.AutoSizeColumns(0, grid.Columns.Count - 1);
// 追記ここから
await _collView.GroupAsync(x => x.Pref);
//grid.CellFactory = new CustomCellFactory();
}
_collView.GroupAsync(x => x.Pref)でデータの地域を対象にグループ化を行っています(グループ化とCellFactoryは併用できない?ようなので、ここではコメントアウトしています)。
このコードを実行すると、次のようになります。
気温の高低を画像で表してみよう
最後に、気温の値を数値ではなく画像で表してみましょう。
FlexGridには様々なGauge(ゲージ)が用意されているので、これを使用すると用意にグラフィカルな表現が可能になります。
ゲージの設定はXAMLで行ってみましょう。
まず、InitializeDataを次のように修正します。
async void InitializeData()
{
var data = await HighestTemperature.Read();
_collView = new XuniCollectionView<HighestTemperature>(data);
grid.ItemsSource = _collView;
// 以下をコメントアウト
//grid.FrozenColumns = 2;
//await _collView.SortAsync(x => x.TodayHighestTemperature, SortDirection.Ascending);
//grid.AutoSizeColumns(0, grid.Columns.Count - 1);
//await _collView.GroupAsync(x => x.Pref);
//grid.CellFactory = new CustomCellFactory();
}
grid.ItemsSource = _collView;より下の処理をコメントアウトします。
次に、GettingStarted.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="FlexGrid101.GettingStarted"
x:Name="page"
xmlns:gauge="clr-namespace:Xuni.Forms.Gauge;assembly=Xuni.Forms.Gauge" ←追記
xmlns:xuni="clr-namespace:Xuni.Forms.FlexGrid;assembly=Xuni.Forms.FlexGrid">
<中略>
<!-- FlexGridの定義を以下のように修正 -->
<xuni:FlexGrid x:Name="grid" Grid.Row="1" AutoGenerateColumns="false">
<xuni:FlexGrid.Columns>
<xuni:GridColumn Binding="Pref" Header="都道府県" Width="*" />
<xuni:GridColumn Binding="Point" Header="地点" Width="*" />
<xuni:GridColumn Binding="TodayHighestTemperature" Header="最高気温" Width="*">
<xuni:GridColumn.CellTemplate>
<DataTemplate>
<gauge:XuniLinearGauge IsAnimated="False" Value="{Binding TodayHighestTemperature}"
Min="0" Max="30" ShowText="Value" ShowRanges="False" WidthRequest="50" HeightRequest="50">
<gauge:XuniLinearGauge.Ranges>
<gauge:GaugeRange Min="0" Max="10" Color="Aqua" />
<gauge:GaugeRange Min="10" Max="20" Color="Green" />
<gauge:GaugeRange Min="20" Max="30" Color="Fuchsia" />
</gauge:XuniLinearGauge.Ranges>
</gauge:XuniLinearGauge>
</DataTemplate>
</xuni:GridColumn.CellTemplate>
</xuni:GridColumn>
</xuni:FlexGrid.Columns>
</xuni:FlexGrid>
</Grid>
</ContentPage>
まず、<xuni:FlexGrid.Columns>に<xuni:GridColumn>を3つ定義します。これとAutoGenerateColumns="false"を設定することによって、グリッドには定義した3列しか表示されないようになります。
そして、3つ目の<xuni:GridColumn>で<gauge:XuniLinearGauge>を使用して、セル内にゲージを表示します。Min="0" Max="30"がゲージの最小・最大値を示し、<gauge:XuniLinearGauge.Ranges>の設定で、「最高気温が0〜10度は水色、10〜20度は緑、20〜30度はピンク」と定義しています。
このコードを実行すると、次のようになります。
まとめ
このように、FlexGridを使用すると、業務アプリケーションでよく見られる表形式の画面を容易に作成することができます。
Xamarin.Formsに対応しているおかげで、共通のコードでAndroid・iOSで動作可能ですし、XAMLやデータバインディングもフルに活用することができます。
本記事で作成したサンプルプログラムは こちらからダウンロードでき、Visual Studio for Mac/Visual Studio 2015、2017で動作します。
Xuniは、FlexGridコントロール以外にも、カレンダー・チャート・オートコンプリート対応入力コントロール群などが含まれています。この記事でインストールしたサンプルプログラムには、これらのサンプルも含まれていますので、是非試してみてください。

