echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【Docker】起動やドライブのマウントができない場合の対処法

※2019/3/12追記:タイトル少し変えました。

最近Laravelの動作環境としてDocker + LaraDockを導入したのですが、Dockerの[Settings] - [Shared Drives]でドライブのマウントに失敗する事例があったので、その対処法のメモ。

Windows版での内容になります。
※「個人のPCにインストールする」前提で書いてます。

Firewall Detected」メッセージが表示される

現象

ドライブのマウント時に、下記メッセージが表示され、マウントができない。
A firewall is blocking file sharing between Windows and containers. See documantation for more info.

原因

セキュリティソフトのファイアウォールが原因で、ブロックされている。

対策

セキュリティソフトのファイアウォールの設定を変更し、Dockerを許可する
※設定方法はセキュリティソフトにより異なる。

【参考】カスペルスキー インターネットセキュリティの場合

  • [設定] - [プロテクション] - [ファイアウォール]を選択
  • [ネットワーク]をクリック
  • 「(DockerNat)」と記載のあるネットワークを[編集]
  • 「ネットワークの種別」を「許可するネットワーク」にする。

Microsoft 365 Businessでログインしている場合に、マウントのチェックが消える

現象

Microsoft 365 Businessアカウント(xxx@onmicorsoft.com)でログインしているユーザーでドライブのマウントを実行すると、

  • 正しいユーザー名・パスワードを入力しても、該当ドライブにチェックが付かず、マウントできない。
  • エラーメッセージは表示されない

原因

アカウント名とセキュリティ ID の間のマッピングに失敗している。
※詳細は下記サイトの「AzureAD の場合」を参照

対策

下記手順を実行する。

  • アカウント名(AzureAD\(ユーザー名))の「ユーザー名」の部分のみのローカルアカウントを[コントロールパネル] - [ユーザーアカウント]から作成する。
  • 再起動後、Microsoft 365 Businessアカウントでログインする(ローカルアカウントではない)。
  • 作成したローカルアカウントの権限を「管理者」にする。(2019/3/12追記)
  • ドライブマウント時に聞かれるユーザー名では、「ユーザー名」の部分のみ入力する。(「AzureAD\」は不要。なおパスワードは変更なし)

起動時に「Access Denied」メッセージが表示される(2019/3/12追記)

現象

Docker起動時に下記メッセージが表示され、Dockerを起動できない。
You are not allowed to Use Docker.
You must be in the "docker-users" group

原因

該当ユーザーがPC内での「docker-users」グループに所属していない。
(インストールユーザーと別ユーザーでログインした、など)

対策

下記手順で、該当ユーザーを「docker-users」に追加する。(管理者権限ユーザーで実行する)

  • Windwosの[コンピュータの管理]を実行。([プログラム] - [管理ツール])
  • 左ツリーから[ローカルユーザーとグループ] - [グループ]をクリック
  • 右側の[docker-users]を右クリック - [プロパティ]を選択
  • [追加]をクリックして、[ユーザーの追加]画面で該当ユーザーを選択して[OK]
  • PCを再起動

ただ、dockerグループのユーザー追加は、セキュリティ上のリスクがある。
(下記サイト参照)
Windows版は上記対策しないとそもそも起動自体が出来ないのであれだけど、
今後は権限(実行可能なコマンド)設定なども可能になる?

僕の場合はこれで解決できたが、もちろん上記では解決できない現象はあるかも。

というか、Docker(やLaraDock)は便利な半面、いろいろあるみたいですね。次回は環境構築や上記以外のトラブルについてのブログを書く予定です。

【Excella】Excella Repotsで、結合セルの値の取得・設定を行う方法

前回、「単一セルのExcella Reports上で書式・スタイルの設定を行う」方法を書きました。

しかし、結合セルの場合、前回記載した

Row row = sheet.getRow(0);
Cell cell = row.getCell(0);
cell.setCellStyle(style);

のように、単純に(結合セルの一番左上の)行・列のインデックスを指定する方法では、対象の結合セルを取得できません。

そこで、結合セルの場合どうするか、というのが今回の内容です。

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.CellRangeAddress;
import org.apache.poi.ss.usermodel.Workbook;
import org.bbreak.excella.core.SheetData;
import org.bbreak.excella.core.SheetParser;
import org.bbreak.excella.core.exception.ParseException;
import org.bbreak.excella.reports.listener.ReportProcessListener;
import org.bbreak.excella.reports.model.ReportBook;
import org.bbreak.excella.reports.model.ReportSheet;

public class SheetFormatSetter implements ReportProcessListener {
    
    public SheetFormatSetter() {
    }
    
    @Override
    public void postParse(Sheet sheet, SheetParser sheetParser, SheetData sheetData) throws ParseException {

        // sheetは、処理対象のシート。
        final int mergedCellCount = sheet.getNumMergedRegions();
        
        for(int i = 0; i < mergedCellCount; i++) {

            // シート内の各結合セルについて、左上のセルの行・列のインデックスを取得する。
            final CellRangeAddress range = sheet.GetMergedRegion(i);
            final int firstRow = range.getFirstRow();
            final int firstCol = range.getFirstColumn();

            // 結合セルの一番左上のセルを取得する。(値は一番左上のセルに設定されてるから)
            Row row = sheet.getRow(firstRow);
            Cell cell = row.getCell(firstCol);

            // セルの値の取得&設定。(ここまでくれば、あとは前回の内容と同じ)
            String value = cell.getStringCellValue();
            cell.setCellValue("new Value");
        }

        return;
    }

説明

ソースを見て、なんとなくわかった人も多いと思いますが、

  • (行・列のインデックス指定による結合セルの直接取得はでき)ないです。
  • (煩雑ですが、シート内の全ての結合セルを取得後、各結合セルの左上のセルを取得するという方法しか)ないです。
  • 結合セルの数は、SheetのgetNumMergedRegions()メソッドで取得できます。

という、ちょっと残念な結果になってしまいました。

注意点

  • 「特定のセル」を取得する際は、getFirstRow()やgetFirstColumn()の値に対して、ifなどでチェックをする必要があります。
  • GetMergedRegion(index)で取得できるセルは、indexを0, 1, 2...のように指定しても、「行の昇順、列の昇順」のように決まった順序で取得できるわけではないので注意です。*1

ということで、今回もExcella Reportsの記事でした。


実は、3月は完全にお休み...なので、1ヶ月まるまる休みです。でもいざ休みとなると、嬉しい反面、やっぱり不安もあったり。

...まあ、3月どころか、4月以降も全く未定なんですけどね...(お仕事のお話がありましたら、TwitterのDMなどで連絡をお願いします。)

*1:実際、ログにgetFirstRow()やgetFirstColumn()の値を記載してみましたが、てんでバラバラでした

【Excella】Excella Reportsで書式・スタイル設定を行う方法

現在、仕事で「Excella Reports」という、JavaからExcel/PDFを出力するライブラリを使用してます。

Excella Reports

便利なのですが、中の人(?)も言っている通り、書式やスタイル設定はちょっと弱いところのようです。

なので、分かる範囲で書式・スタイル設定についてまとめてみました

ソースコード

import org.bbreak.excella.core.SheetData;
import org.bbreak.excella.core.SheetParser;
import org.bbreak.excella.core.exception.ParseException;
import org.bbreak.excella.reports.listener.ReportProcessListener;
import org.bbreak.excella.reports.model.ReportBook;
import org.bbreak.excella.reports.model.ReportSheet;

public class Main {

    public static void main(String[] args) throws Exception {

        // テンプレートファイルの指定
        String templateFilePath = "SampleTemplate.xls";
        String outputFileName = "Sample";
        String outputFileDir = "Sample/Temp";
        String outputFilePath = outputFileDir.concat(outputFileName);

       // ReportBookの作成
        ReportBook outputBook = new ReportBook(templateFilePath,
                outputFilePath, ExcelExporter.FORMAT_TYPE);

        // シートの作成
        ReportSheet outputSheet = new ReportSheet("TemplateSheet", "Sheet1");
        outputBook.addReportSheet(outputSheet);

        // シートのセルに値を設定する。
        int data = 1.23;
        outputSheet.addParam(SingleParamParser.DEFAULT_TAG, "DATA1", data1);

        // ReportProcessListenerの定義&ブックの出力
        ReportProcessor reportProcessor = new ReportProcessor();
        reportProcessor.addReportProcessListener(new SheetFormatSetter());
        reportProcessor.process(outputBook);
    }
}
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.DataFormat;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.bbreak.excella.core.SheetData;
import org.bbreak.excella.core.SheetParser;
import org.bbreak.excella.core.exception.ParseException;
import org.bbreak.excella.reports.listener.ReportProcessListener;
import org.bbreak.excella.reports.model.ReportBook;
import org.bbreak.excella.reports.model.ReportSheet;

public class SheetFormatSetterimplements ReportProcessListener {

    public SheetFormatSetter() {
    }

    @Override
    public void postParse(Sheet sheet, SheetParser sheetParser, SheetData sheetData) throws ParseException {
        
        // WorkbookはsheetのgetWorkBook()メソッドから取得。
        Workbook wb = sheet.getWorkBook();

        CellStyle style = wb.createCellStyle();

        // 表示形式、フォントの設定
        DataFormat format = wb.createDataFormat();
        style.setDataFormat(format.getFormat("#,##0.###");       
       
        Font font = wb.createFont();
        font.setFontName("MS 明朝");
        font.setFontHeightInPoints((short)10);
        style.setFont(font);

        // 縦横位置&上枠線の設定
        style.setAlignment(Cellstyle.ALIGN_RIGHT);
        style.setVerticalAlignment(Cellstyle.VERTICAL_CENTER); 
        style.setBorderTop(CellStyle.BORDER_THICK);
       
        // 対象セル(A1)を取得し、スタイルを設定
        Row row = sheet.getRow(0);
        Cell cell = row.getCell(0);

        cell.setCellStyle(style);
    
        return;
    }
}

基本的にはリファレンスにも書いてある通り、ReportProcessListenerのメソッド(preXX(), postXX())から、Apache POIのAPIを直接呼び出すことになります。

ブック&スタイルの作成

 Workbook wb = sheet.getWorkBook();
 CellStyle style = wb.createCellStyle();

Apache POIだと、CellStyleをHSSFWorkBookクラスのcreateCellStyle()メソッドで作成しますが、Excella ReportsのReportBookクラスには、それに該当するメソッドはありません。

ただし、ReportProcessListenerのメソッドの引数sheetのgetWorkBook()メソッドでWorkBookが取得できますので、そのcreateCellStyle()メソッドからCellStyleを取得できます。

スタイルの各書式の設定

// 上から順に、位置揃え(横)、位置揃え(縦)、セルの上枠線
style.setAlignment(Cellstyle.ALIGN_RIGHT);
style.setVerticalAlignment(Cellstyle.VERTICAL_CENTER); 
style.setBorderTop(CellStyle.BORDER_THICK);

スタイルの各プロパティの設定は、プロパティごとに設定用のメソッドが用意されているので、それを使用します。

上記のように、あらかじめ用意されている定数をそのまま使用する場合は、該当の定数を引数に指定します。

// 表示形式、およびフォント
DataFormat format = wb.createDataFormat();
style.setDataFormat(format.getFormat("#,##0.###");       
       
Font font = wb.createFont();
font.setFontName("MS 明朝");
font.setFontHeightInPoints((short)10);
style.setFont(font);

定数をそのまま使用するのではなく、自分で色々カスタマイズする場合、上記のようにWorkBookのcreateXX()メソッドで該当のインターフェースを取得後、

そのsetXX()メソッドで各種プロパティを設定し、最後にCellStyleの設定用メソッドの引数として該当のインターフェースのインスタンスを渡します。

実際にセルに設定する

Row row = sheet.getRow(0);
Cell cell = row.getCell(0);

cell.setCellStyle(style);

最後に、SheetのgetRow()メソッド、及びRowのgetCell()メソッドで対象のセルを取得し、そのsetCellStyle()メソッドの引数に先程のCellStyleのインスタンスを渡し、実際に書式・スタイルを適用します。

なお、長くなるので省略していますが、実際にはrowやcellのnullチェックを忘れないようにしてください。(値が設定されていない場合、nullが返ります。)

また、今回は値を決め打ちしたので不要でしたが、実際に表示形式など、値の内容に依存する書式を設定する際は、事前にCellのgetCellType()メソッドで、セルの値の型を調べるのを忘れないようにしてください。

参考サイト


前回の投稿から2ヶ月...確かに新年入ってから、すごくメンタルの調子が悪かったのです。

...でも、もう少し更新頻度は上げていきたいです。

愛知県知多半島に住むフリーランスエンジニアのお話

これは「IT地方エンジニア Advent Calendar 2018」 12/24日分の記事です。

知多半島」ってどこよ?

愛知県南西部、2つある半島のうち、「く」の字型の方の半島です。

f:id:Makky12:20181223192936p:plain
知多半島半田市

  • 夏はマリンレジャーや花火大会で盛り上がります。
  • 「知多娘」という萌えキャラで、半島一丸で町(?)おこしをしています。

知多娘。公式WEBサイト

また僕が住んでいるのは「半田市」という、知多半島の中心というべき市で、

  • 「蔵」や「山車」が有名。 また「ごんぎつね」の作者、新見南吉の出身地。
  • 山車が市内だけで31台もあり、5年に1回「はんだ山車まつり」という、市内31台の山車が一堂に集まる祭りがある。
  • 「酢」で有名な「ミツカン酢」の本社があるのが、この半田市

ただ実際に僕が住んでる区域は、半田市でもかなり西の場所なので、むしろ常滑焼や中部国際空港(セントレア)で有名な、常滑市のほうが近いんですけどね。(セントレアまで車で20分)

何やってる人なの?

そんな「都会」とは対極の場所に住んでいながら、(10年間のうつ病パニック障害との闘病を経て)現在はフリーランスエンジニアをしています。

主に業務用のWebシステム開発プロジェクトに参画しております。
それ以外にも、ECサイト開発、スマホゲームアプリ、デスクトップアプリ開発などもこなす、 割と「オールラウンダー」なフリーランスです。
またプログラミング以外にも、要件定義~運用まで、一通り全てこなせます。

言語としては

  • サーバー:Java(Springとか)、およびPHP(Laravel, Cake, Fuel)
  • クライアント:Javascript、TypeScript、Vue.js
  • その他:.net、WPF、Xamarin、Kotlin、swift

などが中心です。

あと、サーバー・クライアントなどに関係なく、C#大好き人間です。
それ以外にも、シェルスクリプト(bash)なんかも扱ったりします。

またOSは、WindowsLinuxが中心です。(次点がAndroid, Macあたり)
DBは、SQL ServerOracleMySQLPostgreSQLなんかを扱えます。

ちなみに、未経験の分野でも機会があれば、ガンガン挑戦していくタイプのエンジニアです。
(近方・遠方限らず、お仕事の連絡、お待ちしてます。リモートワーク可能だと、すごく嬉しいです。)

地方ならではの問題

中心地まで遠い

愛知県といえば、やはり大半の就業先は名古屋になります。
(一部三河地方にもありますが、少なくとも知多半島にはほぼ皆無です)

ドアツードアで30分」とか、そういう恵まれた環境なら良いですが、電車の乗車時間だけで40分、ドアツードアなら余裕で1時間オーバーです。

その結果、以下のような「時間の弊害」が発生します。

  • 定時終業でも、家に帰ったら結構遅い時間なので、なかなかスキルアップの時間(どころか、プライベートな時間すら)が取れない。
  • 勉強会や懇親会などに参加しても、生活サイクルや終電の関係で、不参加 or 早めに切り上げざるを得ないので、人脈形成や情報収集が十分にできない。

(昔ながらの)保守的な考えの企業(SI屋?)が多い

愛知県は、冗談で「ト〇タ県」と言われるほど、製造業(自動車関連)が盛んです。
自動車業界は、特にセキュリティには敏感な業界ですが、その影響か「リモートワーク不可」「フレックス勤務不可」の企業が多いです。 (もちろん例外もあります)

なにが嫌かって、「わざわざ生活時間帯を合わせなきゃいけない」点。

リモートワーク可能ならば全て解決なのですが、せめてフレックスぐらい可能にして、帰宅後の時間を増やしてくれ、と。
ただでさえ、通勤時間でかなり時間を無駄にしてますから。
少しでも時間を有効に使えるようにしたいです。

てか、TwitterのTLで流れてくる東京の情報を見ると、ちょっとうらやましくなります。

解決策

解決策ですが、下記のようなものでしょうか。

  • 東京の人とつながりを持ち、その人経由で案件をもらう
  • 時間を確保できる働き方ができる案件を、自分で(営業して)見つける。
  • 東京に移住する

東京の人とつながりを持つ

個人的に、興味のある技術の勉強会などで、わりと東京に行っています。(ほぼ品川か渋谷ですが...)

少しずつ活動はしていますが、まだまだ成果が出るのは先でしょうね...
そもそも、お金の関係もあり、ホイホイと気軽に行けるわけでもないですし。

それでも、今後もそういう活動を続けないと、と思います。
だって、単価も案件も一番魅力的なのは、やはり東京ですし。

自分で営業する

これが出来ると良いんですが。
てか自分でも「いずれは出来るようにならないと」と思ってます。

そのためにも、

  • 地元の人(技術者やIT業界の関係者)とつながりを持つ
  • ポートフォリオサイトなど、営業ツールを作成する。
  • いろんな会合に、積極的に参加し、人脈&交流を広める。

をしなくては、と思ってます。

そして「リモート可」「週4可能」のような、自分に時間のゆとりが持てる働き方を手に入れなければ、と思ってます。
そして、その時間を、さらに自分のスキルアップや営業力アップに充てられれば、いい正のスパイラルができるなあ、と。

てか友人の女性にも「行動しないと、仕事も奥さんも手に入らないよ!」と言われましたし...)

東京に移住する

これが出来れば、一番問題ないんでしょうけどね。

ただ残念ながら、最初に少し書いた「うつ病パニック障害」の関係で、移住(てか、それ以前に一人暮らしが)NGなのです...

※通常勤務自体は問題ないんですが、先述の病気の関係で、周囲の生活音の影響が大きい一人暮らしは、医師や相談医から「絶対NG!」と強く言われております。
そのため、未だに実家住まい(実家から出られない)です...

※てか、「東京に移住」が出来るなら、こんな長文ブログ、書く必要ないもんな...

最後に

来年以降、こんなことやりたいなあ、なんて思ってます。

  • 上に書いたことを、少しずつでよいから、続けていく
  • LT登壇
  • フリーエンジニアの人と、もっと交流を持って、いろんな情報を共有する(分野、地方問わず)
  • 懇親会にも出来るだけ参加して、いろいろな人と関わっていく
  • ITイベントの運営(特に地方の市町村で行われるような「地元密着型」イベント)
    ※今年「ブイアールサンダー」という、豊橋市二川で行われたイベントに参加させて頂いたのですが、あんな感じのイベントの運営に携わりたいなあ、と。

以上、ものすごい長文になってしまいましたが、最後まで読んでいただき、本当にありがとうございました。

それでは、よいクリスマスイブを!

明日12/25、大トリの日記は、taikiixさんです。

【2018/12/22】NGK2018Bに参加した感想

「NGK2018B」とは?

名古屋最大級のIT系イベント(LT発表大会)です。
今年は170名近くが参加されたようです。(僕も含め)


どんなLTがあったの?

詳細は上記リンク(昼の部)を参照。ただ

  • IT系
  • 非IT系
  • IT系イベント、勉強会告知

など、IT系・非IT系問わず、いろいろありました。(門戸が広い!)
なので、それを通して「色々な技術や分野があるんだなあ」と改めて知ることが出来ました。

感じたこと

  • そもそもLTできる人は、LTネタにできる技術に(業務・プライベート関わらず) 携わってる。
  • そして、どんな些細なな事でも「インプット→アウトプット」という流れを実践されている。
  • やはりやる人は、忙しい中でも時間を見つけて、スキルアップにつながる作業に割いていることをひしひしと実感。
  • 優秀な人は、みんな東京や海外に行っちゃうんだなあ。(当たり前か)

思ったこと

  • 「業務が終わると、帰ったら疲れて何もできない」なんて「言い訳」なのかも。
  • 少ない時間でも、そういう活動をする時間を作らないといけない
  • でか、それができるように就業スタイルを変えていかないといけない。(そういうやりかたこそ、勉強しなければならない)
  • そもそも、いろいろ行動して、色々な事に携わらなければ、いつまでたってもLTネタなんかできるわけがない。
  • てか、それってプライベートでも同じだよね?

と、反省しきりでした。

気になったこと

  • 「アンチ」や「ヘイト」ほどでもないですが、disり系?のLTが一部あった。
  • 僕はそこまで気にならなかったが、相当気になった人もいたらしく、実際タイムラインにも「〇〇がなければ××なのに…このTLだけはマジで不愉快!」みたいなツイートもあった。
  • やたらに縛る必要はないと思うが、ある程度の線引きは必要なのかも...ね。

夜の部(懇親会)について

  • 参加予定だったが、家庭の事情で参加できず...(いろいろ情報交換・情報収集したかったなあ。)
  • LT大会がメインだから仕方ないけど、規模が大きいから、どうしても開始&終了が遅くなってしまいますね。(だから帰りも遅くなってしまう。遠方の田舎民にはつらいっす)
  • そういうこともあり、「LT大会」と「懇親会」を分ける案も出ているそうですね。個人的にはいい考えだと思います。(開始&終了が早ければ、僕のような終電が近い田舎民にも安心♪)

で、結論

来年は、下記のどちらかを達成したい。

  • LT登壇
  • 運営側に回って、何か力になりたい。(できれば大きなものではなく、地元密着型のような、自分も愛着がある場所での勉強会&懇親会で)

とりあえず、そんな感じでした。

【Office】Office365 Businessを申し込む方法や注意点

経緯

  • 今までOffce365 &Office Personal Premiun&永続版Excel&Word(2013)で使用していたが、所持PCが多くなり、賄えなくなってきた。
  • Office365 Soloを検討したが、Office365 Businessのほうがメリットが多そうだった。(値段・機能など)
  • Office365 Businessは、個人事業主でもOKみたいだった。

上記のこともあり、今回の更新から、Office365 Businessに変えることにした。

公式サイト:Microsoft Office 全製品の比較 | Microsoft Office

SoloとBusinessの違い

細かいところは色々ありますが、大きなところでは、下記あたりでしょうか。

項目 Solo Business
値段 12,744円/年 11,664円/年
Skype ×
OneDrive OneDrive OneDrive For Business
インストール可能台数 無制限 PC×5、スマホ×5、タブレット×5
同時使用可能台数 5台まで(機種問わず) 制限なし
個人アカウントとの連携 ×
  • 公式サイトを見るとBusinessは「900円/月(10800円/年)」に見えるが、あれば「税抜」価格(Soloは「税込」価格)。税込にすると上記の通り、90円/月(1080円/年)しか違わないので、値段は決定的な違いにはならない(と思う)。
  • むしろ他の要素の方が重要。(例えば、Businessは個人アカウントとの連携不可なので、OneDriveのデータは自分で移行しなければならない、ということ。)
  • 最大の違いは「インストール可能台数」と「同時使用可能台数」。2018/10/2よりSoloは「インストール台数は無制限だが、同時使用は機種を問わず5台まで」になった。
  • 一方Businessは「インストール台数は有限だが、同時使用台数は無制限」となっている。(てか公式サイトの記載、あれじゃ全然分からない...)

単純に「値段が安いから」で選ぶと、かえって面倒なことになったりするので、よく考えて決めましょう。

登録方法

下記のページを参照に、登録を行った。

注意点としては、下記の所でしょうか。 (またスクショなくて、申し訳ないです...)

■手順1(本人情報入力)

  • 「電子メール」は、個人アカウントのものと同一ではダメかも(「事業用」のものが必要?ちゃんと分けるのが無難だと思う。)
  • そのほかの情報(住所、電話番号など)は個人アカウントと同じで問題なかった。(携帯番号でもOK)

■手順2(ユーザーID作成)

  • ユーザーIDは、「【個人ID】@【会社ID】.onmicrosoft.com」 で固定(例えば、makky12@hatenablog.onmicrosoft.comなど)。
  • 【個人ID】:メアドの@の前の部分、【会社ID】:メアドのドメイン部分、と考えればいいかも。
  • 右上に「既存のサブスクリプションに追加しますか?」と表示されているが、個人のアカウントには紐づけ不可なので注意(法人用の既存アカウントのみ)。

■手順3(注文の確認)&手順4(注文)

  • 人数を聞かれるが、個人利用なら「1人」で全く問題ない。
  • マイクロソフトのOffice365 Businessページだと月払いしかできない感じだが、年払いも選択可能。
  • (参考ページにも書いてあるけど)ここでOffice365 Businessページの値段が「税抜」価格であるとわかる。(そう書いといてよ...と思う、確かに)

登録完了後

あとは、先述の「【個人ID】@【会社ID】.onmicrosoft.com」で専用ページにログイン後、インストーラをダウンロードして、各種OfficeソフトをインストールすればOK。(インストール可能なソフトはSolo/Businessで違いはない。)

Businessの場合の作業

  • 個人アカウントと連携不可なので、OneDrive内のファイルは、期限終了前に自分でOneDrive For Businessに移行しないといけない。(移行ツールなどは存在しない)
  • (僕のケースのように)永続ライセンスのOfficeソフト等を使っている場合、(必要ないのに)変にアンインストールなどをしないほうが良いと思う。(やっぱり再インストール...と思った場合、ライセンス認証がクッソ面倒。なぜか「台数が上限に」とか「電話での認証不可」とか言われたりするし)

単に値段だけで決めるのは早計ですが、やっぱり使用可能台数の差は大きいです。よく考えて、自分に合ったライセンスを選びましょう。

でも使用可能台数が多いのは、やはり便利です。これからSurfaceなど、別PCを買う機会も増えるでしょうし

【Xamarin】ペアリング済のBluetoothデバイスの情報を取得する。

やりたいこと

  • (今も愛用中)のガラケー購入時にBluetoothスピーカーをもらったが、リモコンがなく、音量調整が面倒。

  • iTunesは音量調整出来るが、微妙な音量調節が難しい。(特に手が大きいと)

  • Androidでできるアプリを持ってない。→ならXamarinで作ってみようか。※出来るとは言ってない

ハマった?事

  • 最初は、下記の「Bluetooth LE Plugin for Xamarin」を使おうとしたが、「ペアリング済の機器情報」を取得する方法が分からなかった。(あったならすいません...)

  • Bluetoothの「UUID」は「機器ごと」に個別ではなく「サービス/プロファイル」ごとに個別だった。(同じ「スピーカー」ならUUIDは同じだが、それを知らずにハマった…いい勉強になりました。)

参考にした情報

ソース&結果

※すいません。時間の関係で、ソース&結果のみ表示します。
(詳細は後日追記します。)

■SearchDevicePage.xaml(VolChanger.View)

<?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="VolChanger.View.SearchDevicePage">
    <ContentPage.Content>
        <StackLayout>
            <ActivityIndicator x:Name="AILoading" IsVisible="{Binding IsSearching}" IsRunning="{Binding IsSearching}" />
            <Button x:Name="ButtonSearch" Text="Bluetoothデバイスを検出する" Command="{Binding GetDevicesCommand}" />
            <ListView x:Name="ListViewDevices" ItemsSource="{Binding DeviceList}">

                <!--ここに ItemTemplate を追加-->
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <TextCell Text="{Binding Name}" Detail="{Binding Detail}"/>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

■SearchDevicePage.xaml.cs(VolChanger.View)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using VolChanger.View;
using VolChanger.ViewModel;

using Plugin.BLE.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using Plugin.BLE.Abstractions.Exceptions;

// using Reactive.Bindings;
// using Reactive.Bindings.Extensions;

namespace VolChanger.View
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class SearchDevicePage : ContentPage
    {
        private readonly SearchDeviceViewModel vm;

        public SearchDevicePage ()
        {
            InitializeComponent ();
            this.vm = new SearchDeviceViewModel();
            BindingContext = vm;

            // ButtonSearch.Clicked += ButtonSearch_Clicked;
        }
    }
}

■SearchDeviceViewModel.cs(VolChanger.ViewModel)

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

using VolChanger.View;
using VolChanger.ViewModel;
using VolChanger.Model;

using Plugin.BLE.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using Plugin.BLE.Abstractions.Exceptions;
using Xamarin.Forms;
using Plugin.BLE.Abstractions.EventArgs;

namespace VolChanger.ViewModel
{
    public class SearchDeviceViewModel : INotifyPropertyChanged
    {
        public ObservableCollection<BTDevice> DeviceList { get; set; } 
        public Command GetDevicesCommand { get; }
        public event PropertyChangedEventHandler PropertyChanged;
        private static PropertyChangedEventArgs SearchPropertyChangedEventArgs = new PropertyChangedEventArgs(nameof(IsSearching));

        bool isSearching = false;
        public bool IsSearching
        {
            get { return isSearching; }
            set
            {
                if(isSearching == value) { return; }

                isSearching = value;
                OnPropertyChanged(nameof(IsSearching));

                // CanExecute (このコマンドが実行可能かどうか)を更新
                GetDevicesCommand.ChangeCanExecute();
            }
        }

        public SearchDeviceViewModel()
        {
            DeviceList = new ObservableCollection<BTDevice>();

            GetDevicesCommand = new Command(
                () => GetDevices(), // 実行する処理
                () => !IsSearching); // このコマンドが実行可能の時にtrueを返す
        }

        private void GetDevices()
        {
            if (IsSearching)
                return;
            
            try
            {
                IsSearching = true;

                DeviceList.Clear();
                
                IBTManager manager = DependencyService.Get<IBTManager>();
                var list = manager.GetBondedDevices();

                // await Adapter.StartScanningForDevicesAsync();
                if(list != null && list.Count > 0)
                {
                    foreach(var dev in list)
                    {
                        DeviceList.Add(dev);
                    }
                }
                else
                { 
                    Application.Current.MainPage.DisplayAlert("VolChanger", "ペアリング済デバイスが見つかりませんでした。", "OK");
                }
            }
            catch (Exception e)
            {
                Application.Current.MainPage.DisplayAlert("Error!", e.Message, "OK");
            }
            finally
            {
                IsSearching = false;
            }
        }

        void OnPropertyChanged([CallerMemberName] string name = null) =>
            PropertyChanged?.Invoke(this, SearchPropertyChangedEventArgs);
    }
}

■BTDevice.cs(VolChanger.Model)

using System;
using System.Collections.Generic;
using System.Text;

namespace VolChanger.Model
{
    public class BTDevice
    {
        public string Name { get; set; }
        public string Uuid { get; set; }
        public string Adress { get; set; }
        public string Detail { get; set; }
    }
}

■IBTManager.cs(VolChanger.Model)

using System;
using System.Collections.Generic;
using System.Text;

using VolChanger.Model;

namespace VolChanger.Model
{
    public interface IBTManager
    {
        List<BTDevice> GetBondedDevices();
    }
}

■BTManager.cs(VolChanger.Android)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;

using VolChanger.Model;
using Android.Bluetooth;

using Xamarin.Forms;

[assembly: Dependency(typeof(VolChanger.Droid.BTManager))]

namespace VolChanger.Droid
{
    public class BTManager: IBTManager
    {
        
        public List<BTDevice> GetBondedDevices()
        {
            // アダプター作成
            BluetoothAdapter adapter = BluetoothAdapter.DefaultAdapter; 

             // ペアリング済みデバイスの取得
            var listBondedDevices = adapter.BondedDevices;              
            var result = new List<BTDevice>();

            if (listBondedDevices != null && listBondedDevices.Count > 0)
            {
                foreach (var device in listBondedDevices)
                {
                    var btDevice = new BTDevice();
                    btDevice.Uuid = device.GetUuids().FirstOrDefault().ToString();

                    // Bluetoothスピーカーのみ取得する。
                    if (btDevice.Uuid.Substring(4, 4).ToUpper() == "110A" ||
                        btDevice.Uuid.Substring(4, 4).ToUpper() == "110B")
                    {
                        btDevice.Name = device.Name;
                        btDevice.Adress = device.Address;
                        btDevice.Detail = string.Format("MACアドレス:{0}", btDevice.Adress);
                        result.Add(btDevice);
                    }
                }
            }
            return result;
        }
    }
}

f:id:Makky12:20181201191328j:plain
結果(MACアドレスは一部編集で消してます)

あとは「音量の調整」なんですが、ここからが本当に難しいところでしょうね。

...まあ、それは旅行から帰ってきてからの続き、ということで。