kenichiro246’s blog

主にC#での仕事をしてます。今はXamarinに飛び込みハマり中。基本的に作ることが好き。

[Xamarin]PCLを含めたTemplateの作り方

概要

タイトルのとおりVisual Studioで作成したソリューションを元にテンプレートを作成する方法です。
携わっているシステムの環境に合わせたテンプレートをさらっと作ることができれば、その環境に合わせてちょっと試したいことをすぐに試せるので便利ですよね。

当エントリーではXamarin.Formsを用いたソリューションをテンプレート化して利用できるようにしますが、最低限の作り方しかまとめていません。
そのため、完璧なテンプレートを利用したい場合は、@ytabuchi さんの Visual Studio 2017 用の PCL を使った Xamarin.Forms プロジェクトテンプレートを作りました をご利用ください。

ゴール

以下のスクリーンショットのとおり、自分で作成したテンプレートを表示させ、このテンプレートを用いてソリューションを作成後、エミュレーターで実行できるところまでをゴールとします。 f:id:kenichiro246:20180615140036p:plain

テンプレートの仕様

今回はVisual Studio Community 2017で以下のソリューションを作成し、テンプレートを作っていきます。

  • Xamarin.Forms 2.5.1
  • .NET Standard 2.0

参考

サンプル

GitHubにサンプルを置いておきます。

  • src : テンプレートの元となっているソリューション
  • template : 当エントリーに沿って作成したテンプレート

作成方法

ここからテンプレートを作成していく手順となります。

各プロジェクトファイルのテンプレート作成

テンプレート化したいソリューションを作成した後、ツールバー>プロジェクト(P)>テンプレートのエクスポート(E) をクリックし、テンプレートのエクスポートウィザードを表示させます。

ウィザード画面では「プロジェクト テンプレート(R)」を選択したままにし、「テンプレートの作成元のプロジェクトを指定してください(W)」でAndroidを選択してください。(実際にはiOSでもPCLでもOK)
f:id:kenichiro246:20180615143158p:plain

「次へ(N)」をクリックすると、オプションの選択画面が表示されますが、「テンプレートを自動的にVisual Studio にインポート(A)」のチェックを外し、完了ボタンをクリックします。

完了ボタンをクリックすると、エクスプローラーが開くのでプロジェクトのZIPファイルを解凍しましょう。
また、同じようにウィザードでiOSとPCLのプロジェクトも作っていきます。 最終的には以下のようにZIPとフォルダがそれぞれ出来上がります。 f:id:kenichiro246:20180615145324p:plain

VSTemplateの作成

エクスポート先のフォルダ(ここでは「My Exported Templates」フォルダ)にテキストファイルを作成後、MyTemplate.vstemplateへリネームし、以下の内容を書き込みます。

<VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="ProjectGroup">
  <TemplateData>
    <Name>My Xamarin.Forms Template</Name>
    <Description>My Xamarin.Forms Template</Description>
    <ProjectType>CSharp</ProjectType>
    <ProjectSubType>
    </ProjectSubType>
    <SortOrder>1000</SortOrder>
    <CreateNewFolder>true</CreateNewFolder>
    <DefaultName>App</DefaultName>
    <ProvideDefaultName>true</ProvideDefaultName>
    <LocationField>Enabled</LocationField>
    <EnableLocationBrowseButton>true</EnableLocationBrowseButton>
    <CreateInPlace>true</CreateInPlace>
    <Icon>__TemplateIcon.ico</Icon>
    <PreviewImage></PreviewImage> 
  </TemplateData>
  <TemplateContent>
    <ProjectCollection>
        <ProjectTemplateLink ProjectName="$safeprojectname$">MyXamarinFormsTemplate\MyTemplate.vstemplate</ProjectTemplateLink>
        <ProjectTemplateLink ProjectName="$safeprojectname$.Android" CopyParameters="true">MyXamarinFormsTemplate.Android\MyTemplate.vstemplate</ProjectTemplateLink>
        <ProjectTemplateLink ProjectName="$safeprojectname$.iOS" CopyParameters="true">MyXamarinFormsTemplate.iOS\MyTemplate.vstemplate</ProjectTemplateLink>
    </ProjectCollection>
  </TemplateContent>
</VSTemplate>

VSTemplate(PCL,Android,iOS用) の変更

次に、ZIPを解凍したフォルダ内にあるPCL,Android,iOSのVSTemplateファイルを開き、TemplateDataに以下のCreateInPlaceを追記します。
追記しない場合、共有プロジェクトを参照できず、プロジェクトの作成に失敗する原因となります。

<CreateInPlace>true</CreateInPlace>

csproj の変更

Android,iOSの各csprojファイルを変更します。 以下の通り変更することで各プロジェクトでPCLプロジェクトが参照されるようになります。
$safeprojectname$ ではなく $ext_safeprojectname$ であることがポイント。

変更前

  <ItemGroup>
    <ProjectReference Include="..\MyXamarinFormsTemplate\MyXamarinFormsTemplate.csproj">
      <Project>{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}</Project>
      <Name>MyXamarinFormsTemplate</Name>
    </ProjectReference>
  </ItemGroup>

変更後

  <ItemGroup>
    <ProjectReference Include="..\$ext_safeprojectname$\$ext_safeprojectname$.csproj">
      <Project>{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}</Project>
      <Name>$ext_safeprojectname$</Name>
    </ProjectReference>
  </ItemGroup>

各テンプレートをまとめる

ここまで作成・変更したファイル(ZIPを除く)を全て1つのZIPファイルへ圧縮します。
※ここではMyTemplate.zipとしてZIPファイルを作成。 f:id:kenichiro246:20180615153836p:plain

Visual Studio への反映方法

MyTemplate.zipを ツール>オプション>プロジェクトおよびソリューション>場所>ユーザー プロジェクト テンプレートの場所 のパスに保存し、Visual Studioを起動または再起動します。
その後、プロジェクトの新規作成の画面を開くと、以下のとおり自分で作成したテンプレートが選択できるようになります。
f:id:kenichiro246:20180615152315p:plain

※ツリー内の「Cross-Platform」などでも表示させたい場合は、上記のテンプレートのパスに「Cross-Platform」などのフォルダを作成し、テンプレートのZIPファイルを保存すると以下のように選択できるようになります。 f:id:kenichiro246:20180615140036p:plain

テンプレートから実行

あとはテンプレートを選択し、OKボタンを押した後、実際にビルドしてみましょう。
設定が問題なければ、テンプレート化する前のソリューションの状態と同じように動くはずです。

Templateの削除方法

テンプレートを作成できるようになりましたが、削除も合わせて覚えておきましょう。
覚えると言っても配置したZIPファイルを削除するだけですが。
「ユーザー プロジェクト テンプレートの場所」に保存したZIPファイルを削除するだけで、新しいプロジェクトから選択できないようになります。

以上がPCLを含めたTemplateの作り方となります。
実際にはインストーラーまで作りたいのですが今回はここまで。
少し余裕が出てきたらインストーラーまで作ってみます。

当エントリーが良かったと思ったらスターのクリックをお願いします。

(続き)[Xamarin.Forms]Plugin.BLEを使用してペリフェラルの情報を取得する方法

このエントリーは、2017年のQiita アドベントカレンダー(22日)で書いたエントリーの続きです。
今更ですが。。。

概要

今回は、前回組み込んでいなかったDisconnectDeviceAsyncを追加します。
また、接続中のデバイスを切断するだけではネタが少なすぎるので、接続中のデバイスを取得するロジックも組み込んでみましょう。

ソースコード

public PeripheralsPage()
{
    InitializeComponent();

    _BluetoothLe = Plugin.BLE.CrossBluetoothLE.Current;
    _Adapter = _BluetoothLe.Adapter;
    _Adapter.ScanTimeout = 5000;
    _Adapter.DeviceDiscovered += (s, e) =>
    {
        _deviceList.Add(e.Device);
    };
    _Adapter.ScanTimeoutElapsed += (s, e) =>
    {
        foreach (var device in _Adapter.ConnectedDevices)
        {
            _deviceList.Add(device);
        }
    };
    ListView1.ItemsSource = _deviceList;
    AddSearchButton();
}

protected override async void OnAppearing()
{
    base.OnAppearing();

    var devices = _Adapter.ConnectedDevices;
    foreach (var device in devices)
    {
        await _Adapter.DisconnectDeviceAsync(device);
    }
}

解説

  • DisconnectDeviceAsync
    接続中のデバイスを切断するメソッドです。
    今回はどのタイミングで切断するか迷いましたがシンプルなサンプルにしたかったので、OnAppearingで切断するようにしました。
    OnAppearingは画面がロードされた時に発火するイベントであり、ServicesPageから戻ってきた際に、呼び出され、デバイスを切断するようにしています。

  • ConnectedDevices
    接続中のデバイスの一覧を取得するプロパティです。

  • ScanTimeoutElapsed
    StartScanningForDevicesAsync によるスキャンが終わった際に発火するイベントです。
    今回、DeviceDiscovered はアドバタイズ中のペリフェラルしか取得できないので、スキャンが完了後、ConnectedDevices で接続済のペリフェラルを表示するようにしています。
    スキャンはScanTimeoutで5,000ms(5秒)で設定しているため、5秒後に発火することになります。

結果

せっかくなので、DisconnectDeviceAsync しない場合のStatusと、する場合のStatusがどのように変わっているか比較した動きもご確認ください。
自宅のChromecastAudioに接続した場合のStatusの違いを見ていただければわかりやすいと思います。

DisconnectDeviceAsync しない場合

f:id:kenichiro246:20180120125117g:plain

DisconnectDeviceAsync する場合

f:id:kenichiro246:20180120124919g:plain

前回に引き続きBluetooth Low Energyについて書きましたがいかがでしょうか。
良ければStarやコメントをお願いします。

Visual Studio for Mac のインストーラーで、インストール中にエラーが発生した時の対処方法(System.Net.WebException)

年始早々バスケして肉離れしました。(挨拶)

Visual Studio for Macを新規インストール中にエラーが発生しまして、苦戦した結果をここに書き記そうかと。
※私のMacBookの環境で発生および解決した方法なので、もし試す場合は自己責任でお願いします。

何が起こったのか

Visual Studio for Macのインストール中にエラーが発生し、何もインストールされないという現象が発生。

環境

・Installer version::4.0.2.121
・OS:Mac OS X v10.12.6(Sierra
 ※High Sierraでもたぶん同じ事象が発生していたと思う。

エラーメッセージ

インストーラーで表示される主なエラーメッセージは以下のとおり。

Error downloading from Mono server. Mono Framework.
Message: The operation has timed out.
Exception type: System.Net.WebException
  
複数の例外により、Mono Framework をインストールできませんでした (試行回数 3)
Message: Some installation errors are present.
Exception type: System.AggregateException

実際の画面は以下の感じ。
f:id:kenichiro246:20180112000544p:plain:w300
f:id:kenichiro246:20180112000604p:plain:w300

エラー原因

このエラーメッセージからは正直わからないことが多いため、インストールログを確認したところ、インストールに必要なMono FrameworkのPackageファイルのダウンロードに失敗しているようでした。
インストールログは以下のパスに保存されており、より詳細なログが出力されています。
/Users/(UserName)/Library/Logs/XamarinInstaller/Universal

[2018-01-06 05:32:52.857] [Info] Installing software item 'Mono Framework'
[2018-01-06 05:32:52.857] [Info] Mono Framework is waiting for download to finish.
[2018-01-06 05:32:54.597] [Debug] Download size for 'https://dl.xamarin.com/MonoFrameworkMDK/Macx86/MonoFramework-MDK-5.4.1.7.macos10.xamarin.universal.pkg': 493676379
[2018-01-06 05:32:55.518] [Debug] Download size for 'https://dl.xamarin.com/VsMac/VisualStudioForMac-7.3.2.12.dmg': 426875576
[2018-01-06 05:32:56.236] [Debug] Download size for 'https://dl.xamarin.com/profiler/profiler-mac-1.6.0-25.pkg': 44832659
[2018-01-06 05:32:56.236] [Info] Downloading from 'https://dl.xamarin.com/MonoFrameworkMDK/Macx86/MonoFramework-MDK-5.4.1.7.macos10.xamarin.universal.pkg'.
[2018-01-06 05:32:56.237] [Debug] Setting download timeout for 'https://dl.xamarin.com/MonoFrameworkMDK/Macx86/MonoFramework-MDK-5.4.1.7.macos10.xamarin.universal.pkg' to 60s
[2018-01-06 06:09:44.133] [Exception] Failed to retrieve response for URL 'https://dl.xamarin.com/MonoFrameworkMDK/Macx86/MonoFramework-MDK-5.4.1.7.macos10.xamarin.universal.pkg'
[2018-01-06 06:09:44.133] [Exception] System.Net.WebException: The operation has timed out.
[2018-01-06 06:09:44.133] [Exception] at System.Net.WebConnectionStream.Read (System.Byte[] buffer, System.Int32 offset, System.Int32 size) [0x00055] in /Library/Frameworks/Xamarin.Mac.framework/Versions/2.10.0.113/src/mono/mcs/class/System/System.Net/WebConnectionStream.cs:338
[2018-01-06 06:09:44.133] [Exception] at Xamarin.Web.Installer.DownloadService.DoDownload (System.Int64 totalBytes, System.IO.Stream responseStream, System.IO.Stream outputStream, System.Object state) [0x00087] in /Users/builder/data/lanes/5758/b4a89cf8/source/installer/Xamarin.Web.Installer/DownloadService.cs:285

対処方法

上記エラーメッセージの.pkgファイルを手動でインストールします。
.pkgのURLをコピーし、ブラウザのアドレスバーなどへ貼り付けて実行することでPackageのダウンロードが開始されるため、ダウンロード後にインストールします。

私の場合、Monoのインストールが正常に完了し、再度インストーラーでインストールを進めてみたところ、今度は「VisualStudioForMac-7.3.2.12.dmg」のダウンロードでエラーが発生。
→同じように手動でインストール
→インストール完了
インストーラーで再度インストールするもまたエラー
→エラーログからxamarin.android-8.1.0-25.pkgを手動でインストール
→インストール完了
インストーラーで再度インストールするもまたエラー
→エラーログからxamarin.ios-11.6.1.2.pkgを手動でインストール
→インストール完了
と、何度も繰り返し。再度にインストーラーも正常終了したことを確認し、無事インストールが完了。
その後は特に問題なく利用できています。

結局のところ、System.Net/WebConnectionStream.csでエラーが発生していたようですが、なぜエラーが発生していたかまでは不明です。

同じような事象で困っている人の助けになれば幸いです。

[Xamarin.Forms]Plugin.BLEを使用してペリフェラルの情報を取得する方法

この記事は[初心者さん・学生さん大歓迎!] Xamarin その1 Advent Calendar 2017 - Qiitaの22日目の記事です。
昨日は@gnk263さんのXamarinで作成したiOSアプリをAppStoreで配布する手順 - Qiita でした。

qiita.com

アドベントカレンダー初参戦であり、このネタで不安ですが暖かく見守っていただければ幸いです。 ホント皆さんの記事が凄いのなんのって。

さて、今回のエントリーでは、Xamarin.FormsとPlugin.BLEを使用し、簡単なアプリを作ってみます。
簡単なアプリと言っても、タイトルのとおりアドバタイズ中のペリフェラルへ接続し、情報を取得するだけなので、 アプリというほどでも無いですが、こんな感じで取得できるのかと思ってもらえれば宜しいかと。

なぜPlugin.BLEかというと、そもそもはBluetooth Low Energy(以下、BLE)に興味があって勉強していたところ、「Essential Xamarin」でPlugin.BLEの情報が載っており、利用してみたら便利であったことから、今回のエントリーのネタとしました。

書籍はこちら。様々な情報が盛り沢山なので大変重宝しています。 prtimes.jp

BLEを学び始めた頃は、事前知識が無くてさっぱりわからなかったのですが、やっと理解出来てきたため、これからBLEを学びたい人の助けになればと思います。
ソースコードはMvvmは使わず、比較的わかりやすいように書いたつもりですが、出来が悪かったらごめんなさい。

ゴール

以下のとおり、Searchボタンをタップすると、アドバタイズ中の情報を一覧表示し、キャラクタリスティックの情報を取得するものを作ります。
f:id:kenichiro246:20171221203644g:plain

BLEの概要

もしBLEについてまったく知らない場合は以下の記事を見ていただくと参考になります。
私としては、この記事無かったら結構わからないことが多くてダメだったかも。
blog.fenrir-inc.com

また、以下の書籍も大変勉強になりました。
BLEについて、より詳細な仕組みを知りたい場合はかなり重宝する書籍かと思っています。  iOS×BLE Core Bluetoothプログラミング 単行本 – 著者:堤 修一, 松村 礼央

上記の記事のとおりBLEは様々なことを学ぶ必要がありますが、今回のこのエントリーでは以下の用語をざっくりでもいいので理解いただければ、その後の話がスムーズに理解できるように書いています。たぶん・・・。

用語

  1. ペリフェラル(Peripheral)
    タイトルにも書かれていますがペリフェラルとは、コンピュータと組み合わせて利用される各種機器のことを指しています。ここではBluetoothのマウスやキーボード、あとはApple Watchなどをイメージしてもらえれば良いかと思います。

  2. アドバタイズ(Advertise)
    アドバタイズとは接続可能なペリフェラルが機器情報を周辺に発信している状態のことを指しています。Bluetoothのマウスなどを接続する際に、Connectボタンを押した後、LEDランプが点滅している状態だと思ってください。

  3. セントラル(Central)
    周辺でアドバタイズしているペリフェラルをスキャンし、接続後にペリフェラルが提供している情報を取得したり、ペリフェラルを操作したりする機器のことを指しています。ここではiPhoneAndroidなどのスマートデバイスをイメージしてもらえれば良いかと思います。

  4. サービス(Service)
    特性およびデバイスの一部の動作をカプセル化した分類のようなもの。サービス毎に1つ以上のキャラクタリスティックを持っています。前述の記事にも記載されていますが、オブジェクト指向プログラミングで言えば、サービスがクラス、また次の用語のキャラクタリスティックがプロパティと思ってもらえるとわかりやすいかと。

  5. キャラクタリスティック(Characteristic)
    ペリフェラルが持っている情報のことを指しています。ペリフェラルの名称やバッテリーの残量、シリアルNoなど様々な情報をキャラクタリスティックを介して取得できるものとなります。

当アプリについて

当アプリは以下のように作っています。

  • PeripheralsPage
    アドバタイズ中のペリフェラルの情報を一覧表示します。表示されたペリフェラルをタップすると、実際に接続できるか確認後、問題なく接続できたらServicesPageへ遷移します。
  • ServicesPage
    接続したペリフェラルのサービス情報を一覧表示します。表示されたサービスをタップすると、CharacteristicsPageへ遷移します。
  • CharacteristicsPage
    選択したサービスのキャラクタリスティックの情報を一覧表示します。基本的にキャラクタリスティックのプロパティをそのまま表示させるだけとなっています。(なので、ものによっては良くわからない情報が表示されています。) 

環境

当エントリーは以下の環境で作っています。
* Visual Studio for Mac Community :7.2.2
* Xamarin.Forms :2.5.0.121934
* Plugin.BLE :1.3


それでは実際に作ってみましょう!




GitHub

本文にソースコードは載せていますが、詳細はGitHubをご確認ください。
github.com

ソリューション構成

今回はPCLに以下のようにPageを配置した構成としています。
f:id:kenichiro246:20171220015939p:plain

Nuget

Nugetから以下のとおり「Plugin.BLE」で検索し、各プロジェクトへ追加します。
f:id:kenichiro246:20171221060609p:plain

AndroidManifest.xmlの変更(Androidのみ)

Androidで実行する際には、マニフェストファイルの変更が必要です。
.DroidプロジェクトのProperties配下のAndroidManifest.xmlを開き、以下の4つにチェックをつけます。

  • AccessCoarseLocation
  • AccessFineLocation
  • Bluetooth
  • BluetoothAdmin

ソースコード

各PageのC#ソースコードは以下のとおりです。
XamlはListViewでプロパティの値を表示されているだけのため割愛します。

PeripheralsPage

using System.Collections.ObjectModel;
using System.Diagnostics;
using Plugin.BLE.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using Plugin.BLE.Abstractions.Exceptions;
using Xamarin.Forms;

namespace PluginBleTest
{
    public partial class PeripheralsPage : ContentPage
    {
        IBluetoothLE _BluetoothLe;
        IAdapter _Adapter;
        private ObservableCollection<IDevice> _deviceList = new ObservableCollection<IDevice>();

        public PeripheralsPage()
        {
            InitializeComponent();

            _BluetoothLe = Plugin.BLE.CrossBluetoothLE.Current;
            _Adapter = _BluetoothLe.Adapter;
            _Adapter.ScanTimeout = 5000;
            _Adapter.DeviceDiscovered += (s, e) =>
            {
                _deviceList.Add(e.Device);
            };
            ListView1.ItemsSource = _deviceList;
            AddSearchButton();
        }

        private void AddSearchButton()
        {
            var Toolbarbutton = new ToolbarItem { Text = "Search" };
            Toolbarbutton.Clicked += async (s, e) =>
            {
                if (_BluetoothLe.State == BluetoothState.Off)
                {
                    _deviceList.Clear();
                    await DisplayAlert("Bluetooth LE", "BLE states is off.", "OK");
                    return;
                }
                if (_Adapter.IsScanning)
                {
                    return;
                }
                _deviceList.Clear();
                await _Adapter.StartScanningForDevicesAsync();
            };
            ToolbarItems.Add(Toolbarbutton);
        }

        async void ListView1_ItemSelected(object sender, SelectedItemChangedEventArgs e)
        {
            var device = e.SelectedItem as IDevice;
            if (device == null)
            {
                return;
            }

            try
            {
                await _Adapter.ConnectToDeviceAsync(device);
                if (device.State == DeviceState.Disconnected)
                {
                    await DisplayAlert("PluginBleTest", "Do Not Connect", "OK");
                    return;
                }
                if (device.State == DeviceState.Connected || device.State == DeviceState.Limited)
                {
                    await Navigation.PushAsync(new ServicesPage(device));
                }
            }
            catch (DeviceConnectionException ex)
            {
                Debug.WriteLine(ex.StackTrace);
                await DisplayAlert("PluginBleTest", "Do Not Connect", "OK");
            }
            finally
            {
                ListView1.SelectedItem = null;
            }
        }
    }
}

解説

Searchボタンをタップすることで、セントラルの周辺でアドバタイズ中のペリフェラルの情報を取得し、ListViewへ設定しています。
XamlではペリフェラルのName、Id、Rssi、Stateのプロパティの値を表示させています。
また、ペリフェラルBluetoothがONになっていない場合は、検索しないくらいの仕組みは組み込んでいます。

  • StartScanningForDevicesAsync
    アドバタイズ中のペリフェラルの情報を取得するメソッドです。
    検知したペリフェラルは非同期でDeviceDiscoveredイベントが呼び出され、ペリフェラルの情報を参照することが出来ます。

  • DeviceDiscovered
    ペリフェラルを検知した際に呼び出されるイベントです。
    パラメータのDeviceがペリフェラルそのものになり、このDeviceからIDや名称などを取得することが出来ます。

  • ConnectToDeviceAsync
    選択したDeviceを実際に接続するメソッドです。
    正常に接続できれば、そのDeviceのサービスの情報を見ることが出来ます。
    もし接続できない場合は、DeviceConnectionExceptionが発生し、エラーとなります。

ServicesPage

using System.Collections.ObjectModel;
using Plugin.BLE.Abstractions.Contracts;
using Xamarin.Forms;

namespace PluginBleTest
{
    public partial class ServicesPage : ContentPage
    {
        private ObservableCollection<IService> _ServiceList = new ObservableCollection<IService>();
        private IDevice _device = null;

        public ServicesPage(IDevice device)
        {
            InitializeComponent();

            _device = device;
            ListView1.ItemsSource = _ServiceList;
        }

        protected override async void OnAppearing()
        {
            base.OnAppearing();

            _ServiceList.Clear();
            var services = await _device.GetServicesAsync();
            foreach (var service in services)
            {
                _ServiceList.Add(service);
            }
        }

        async void ListView1_ItemSelected(object sender, SelectedItemChangedEventArgs e)
        {
            var service = e.SelectedItem as IService;
            if (service == null)
            {
                return;
            }
            await Navigation.PushAsync(new CharacteristicsPage(service));
            ListView1.SelectedItem = null;
        }
    }
}

解説

全体的に特に難しいことはしていません。Deviceからペリフェラルの情報を取得し、ListViewに設定しているだけの内容としています。
XamlではサービスのName、Id、Primaryのプロパティの値を表示させています。

  • GetServicesAsync
    ペリフェラルのサービスの一覧を取得するメソッドです。
    この各サービスにはIDや名称などのプロパティが保持されています。

CharacteristicsPage

using System.Collections.ObjectModel;
using Plugin.BLE.Abstractions.Contracts;
using Xamarin.Forms;

namespace PluginBleTest
{
    public partial class CharacteristicsPage : ContentPage
    {
        private ObservableCollection<ICharacteristic> _CharacteristicList = new ObservableCollection<ICharacteristic>();
        private IService _service = null;

        public CharacteristicsPage(IService service)
        {
            InitializeComponent();

            _service = service;
            ListView1.ItemsSource = _CharacteristicList;
        }

        protected override async void OnAppearing()
        {
            base.OnAppearing();

            var characteristics = await _service.GetCharacteristicsAsync();
            foreach (var characteristic in characteristics)
            {
                if(characteristic.CanRead)
                {
                    await characteristic.ReadAsync();
                }
                _CharacteristicList.Add(characteristic);
            }
        }
    }
}

解説

ServicesPageと同じく、サービスの各キャラクタリスティックの情報をListViewへ設定しているだけとなります。
XamlではキャラクタリスティックのName、Id、Uuid、Properties、Valueなどいくつかのプロパティの値を表示させています。
Value値を取得するために一度ReadAsyncする必要があります。

  • GetCharacteristicsAsync
    サービスのキャラクタリスティックの一覧を取得するメソッドです。
    これで取得したキャラクタリスティックからペリフェラルの様々な情報が取得できます。

実行

ここまでの内容で実行してみましょう。
冒頭のとおり動くかと思います。
因みにBLEは実機でないと動かないので実機へデプロイしてお使いください。

また、ここまで書いていて何ですが、接続したペリフェラルを切断(DisconnectDeviceAsync)するの忘れてた。
このままだと接続したまま残ってしまうので良くないですね。
もし実機で試す場合は、自分でDisconnectDeviceを入れるようにしてみてください。ごめんなさい。
※後日組み込むようにしておきます。

まとめ

今回は「Essential Xamarin」の内容を元に作ってみましたが、Plugin.BLE自体、そこまで使い方が難しいものではない感じでしたので、興味があれば使ってみてください。
また、今回のエントリーではサービスなどを全て表示させるようなものにしましたが、次回あたりはUUIDを指定し、予めわかっているキャラクタリスティックの情報を取得するようなものも書いてみようかと思います。

明日は@shiita0903さんです。
宜しくお願いします。

UIKeyCommandを使ってバーコードリーダーの入力を受け取る(Xamarin版)

BluetoothiOSバイスに接続されているHIDプロファイルのキーボード入力値を取得し、画面に表示させる方法です。
通常は「Bluetooth LE Plugin for Xamarin」などのプラグインなどを使用し、バーコードなどの入力値を取得することになりますが、ここではiOSのUIKeyCommandを利用し、取得する方法を纏めました。
(とはいうものの、「Bluetooth LE Plugin for Xamarin」は全然触れておらず現在勉強中)

本来ならばSPPで値をとるのだろうけど、iOSに限ってはUIKeyCommandのAPIがあるので、これを使用し、キーボードの値を取得してみます。

参考

以下のサイトを元にしています。
細かい用途や考え方などが纏まっており、大変勉強になります。
* UIKeyCommandを使ってバーコードリーダーの入力を受け取る
* UIKeyCommandでバーコードリーダーの入力を受けとる(Swift版)

実装概要

  1. PCL側にバーコードリーダーで取得した際に呼び出されるメソッドを追記する。
  2. iOSプロジェクトにPageRendererを追加する。
  3. PageRendererにてキーボード入力を取得するためのUIKeyCommandを実装する。
  4. キーボード入力を取得後、1で作成したメソッドを呼び出す。

サンプルコード

GitHubに保存しています。
https://github.com/kenichiro246/KeyCommandSample

サンプルコードの実行イメージ

※準備中。(MacBookのOS再インストールが必要になり、イメージ画が作れず・・・)

実装

以下の通り実装することで取得したバーコードの情報を画面に表示できます。

PCL

<?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="KeyCommandSample.Page1">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness" iOS="0,20,0,0" />
    </ContentPage.Padding>
    <ContentPage.Content>
        <Label
            x:Name="Label1"
            Text="Welcome to Xamarin Forms!" 
            VerticalOptions="Center" 
            HorizontalOptions="Center"
            />
    </ContentPage.Content>
</ContentPage>
namespace KeyCommandSample
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class Page1 : ContentPage
    {
        public Page1()
        {
            InitializeComponent();
        }

        // iOSから呼び出される
        public void SetKeyboardValue(string value)
        {
            Label1.Text = value;
        }
    }
}

iOS

[assembly: ExportRenderer(typeof(KeyCommandSample.Page1), typeof(Page1Renderer))]
namespace KeyCommandSample.iOS
{
    public class Page1Renderer : PageRenderer
    {
        private string _RecvValue = string.Empty;

        public override bool CanBecomeFirstResponder
        {
            get { return true; }
        }

        protected override void OnElementChanged(VisualElementChangedEventArgs e)
        {
            base.OnElementChanged(e);
            string key = string.Empty;
            var selector = new Selector("KeyRecv:");
            for (int i = 0; i <= 127; i++)
            {
                key = ((char)i).ToString();
                UIKeyCommand accelerator1 = UIKeyCommand.Create((NSString)key, 0, selector);
                AddKeyCommand(accelerator1);
                UIKeyCommand acceleratorShift = UIKeyCommand.Create((NSString)key, UIKeyModifierFlags.Shift, selector);
                AddKeyCommand(acceleratorShift);
            }
            UIKeyCommand accelerator2 = UIKeyCommand.Create((NSString)"\n", 0, selector);
            AddKeyCommand(accelerator2);
            UIKeyCommand accelerator3 = UIKeyCommand.Create((NSString)"\r", 0, selector);
            AddKeyCommand(accelerator3);
        }

        [Export("KeyRecv:")]
        public void KeyRecv(UIKeyCommand cmd)
        {
            if (cmd == null)
                return;
            var inputValue = cmd.Input;
            if (inputValue == "\n" || inputValue == "\r")
            {
                SetKeyboardValue(_RecvValue);
                _RecvValue = string.Empty;
            }
            else
            {
                _RecvValue += inputValue;
            }
        }
        public void SetKeyboardValue(string value)
        {
            ((KeyCommandSample.Page1)Element)?.SetKeyboardValue(value);
        }
    }
}

まとめ

今回の作り方では、ページが破棄された時のロジックが未実装であり、そのあたりはもう少し工夫して作りこみたいとは思っています。 また、今回はiOSのみですが、次回はSPPでiOSAndroidも情報を取得できるようにしたいなぁなんて。

Xamarin.Androidで横向きキーボード表示時に画面が隠れないようにする方法

Android端末を横向きにしてキーボードを表示させると、以下のように現在表示されている画面の情報が見えなくなるので、これをXamarin.Androidで解消できるようにします。
Android端末のキーボード設定でも解消できますが、今回はプログラムで制御させてみます。

※キーボードの設定で変更出来ることは@ytabuchiさんから、キーボードの制御が出来ることを@Santea3173さんから教えてもらいました。ありがとうございました。

ゴールは以下の通りCustomEntryにフォーカスが当たった時の挙動になるようにします。
f:id:kenichiro246:20170617174422g:plain:w400

手順

実装する手順は以下のとおりです。実はたったこれだけ。 ポイントとしてはImeActionへの設定値は、ImeFlagsの値をImeActionへキャストしないといけないところ。恥ずかしながら実はここで結構ハマった。

  1. EntryのCustomRendererを作る
  2. EntryのImeActionプロパティを変更する

書いてみよう

では、上記手順のとおり書いてみましょう!
まずはEntryを継承したCustomRenderer用のクラスを準備します。

CustomEntry.cs

namespace KeyboardImeAction
{
    public class CustomEntry : Entry
    {
    }
}  

次にAndroid側のプロジェクトにEntryRendererを継承したクラスを準備します。

CustomEntryDroid.cs

using Android.Views.InputMethods;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(KeyboardImeAction.CustomEntry), typeof(KeyboardImeAction.Droid.CustomEntryDroid))]
namespace KeyboardImeAction.Droid
{
    class CustomEntryDroid : EntryRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);
            if (Control != null)
            {
                Control.ImeOptions = (ImeAction)ImeFlags.NoFullscreen;
            }
        }
    }
}

最後に作成したCustomEntryをContentPageに配置します。
Entryの動きを確認するため、通常のEntryとCustomEntryを配置してみます。

App.cs

public App()
{
    MainPage = new ContentPage
    {
        Content = new StackLayout
        {
            Children = {
                new Label { Text = "Welcome to Xamarin Forms !" },
                new Entry { Placeholder = "Entry" },
                new CustomEntry { Placeholder = "CustomEntry" }
            }
        }
    };
}

これで実行してみましょう。
Entryにフォーカスが当たった状態と、CustomEntryにフォーカスが当たった状態の違いが見れるかと思います。

尚、ImeFlagsはNoFullscreenを指定しており、NoExtractUiでも同じような挙動をしますが、リファレンスを読んでもイマイチよくわかっていないので、後日もう少し検証してみようかと思います。

GitHubにも上げているので良ければ参考にしてください。
https://github.com/kenichiro246/KeyboardImeAction

参考サイト:
http://android.ohwada.jp/archives/4382
https://xamarinhelp.com/accommodate-on-screen-keyboard-xamarin-forms/

ではまた。