やる気駆動型エンジニアの備忘録

WPF(XAML+C#)の話題を中心に.NET/Android/CI やたまに趣味に関するブログです

ErgoDox EZのキースイッチとキーキャップを変えてみた

趣味の話です!

私は会社用と自宅用にErgoDox EZを使っているのですが、自宅用のは今年に入ってから購入しました。 ErgoDox EZは、現在販売されているものはキースイッチが差し替えられるようになっているため、最初購入したキースイッチが気に入らない場合は変更することが可能です。

ちなみに私は会社用に購入したものは2年以上前のもののためキースイッチは差し替えることができません。

いろいろなキースイッチを試してみたいな~、という欲求には勝てず、ついついキースイッチとキーキャップを新しく購入してしまいました。

購入元はTALPさんです。何回かお世話になってますが、良いところですよ。

talpkeyboard.stores.jp

さて、購入したものを組み込むビフォー・アフターはこちら。

  • Before

f:id:iyemon018:20190907143938j:plain

  • After

f:id:iyemon018:20190907144001j:plain

プロファイルはDCSからDSAに変更しました。また、妻もPCを使用するので印字タイプのキーキャップにしています。 写真じゃわかりにくいですが、水色のキーが白のErgoDox EZと大変マッチしております。

また、キースイッチも少量購入しました。(右側の写真は取り忘れてますが)よく使い、かつ小指や薬指といった力の入りづらい位置に赤軸と青軸のキーを割り当てています。

ErgoDox EZは、キースイッチが変えられるものの、差し替えの際には結構注意が必要です。 キーボードの性質上のせいなのでしょうか、思ったよりもガッチリとキーが固定されており、結構力を入れないとキーが取り外せません。 また、力を入れすぎるとこんな事になりかねません…

取り扱いの際には最新のご注意を!

さて、これで私のErgoDox EZは茶・赤・青の3軸の組み合わせとなりました。 使った感じとしては思った通り、小指・薬指の操作は多少楽になりました。やっぱり青軸のクリック感はキー入力の醍醐味ですね。

予想していなかったのは、赤・青に比べて茶軸の入力が若干違和感があることでしょうか。 これは恐らく私が会社用に使用しているErgoDox EXのキーが赤軸だからでしょう。 1つのキーボードに複数のキースイッチが混ざっていると余計にその差異がわかります。

いっそのこと全部青軸に変えようかな?

(WPF)App Centerでクラッシュ レポートを送る

前回、App Center + WPF を使ってユーザー利用状況を収集する機能を試してみました。 今回はクラッシュ レポートで遊んでみます。

参考資料

docs.microsoft.com

今回はこの資料をベースに進めていきます。

意図的にクラッシュさせる

App Center の API をテスト的に使用したい場合は、以下のコードを呼び出してクラッシュさせてね、とのこと。

Microsoft.AppCenter.Crashes.Crashes.GenerateTestCrash();

多分、私がやっているような検証のためのコードだと思うんですが、あってるのかな?

前回クラッシュした情報を取得する

前回アプリがクラッシュしたかどうか、クラッシュした時の情報を取得する方法です。 用途としてはユーザーにより詳細な操作内容を入力してもらうためのクラッシュ レポートを送る、などでしょうか。

前回クラッシュしたかどうかを取得するには、以下のようなコードで実現できます。

bool didAppCrash = await Microsoft.AppCenter.Crashes.Crashes.HasCrashedInLastSessionAsync();

クラッシュ時の情報を取得する方法はこう。

Microsoft.AppCenter.Crashes.ErrorReport crashReport = await Microsoft.AppCenter.Crashes.Crashes.GetLastSessionCrashReportAsync();

Microsoft.AppCenter.Crashes.ErrorReport型では以下のような情報が取得できます。 この辺の情報は見つけられなかったので、クラス定義を翻訳しただけです。間違ってたらごめんなさい。

  • Id : クラッシュ レポートの UUID。クラッシュ情報を一位に識別するためのもの。
  • AppStartTime : 前回クラッシュしたときのアプリが起動した日時。
  • AppErrorTime : 前回クラッシュしたときの日時。
  • Device : クラッシュしたデバイスの情報。Microsoft.AppCenter.Device型。OS の情報とか取れるっぽい。
  • Exception : 前回クラッシュした際にスローされた例外インスタンス。App Center に通知される内容と同じもの。
  • AndroidDetails : Android 特有のエラー情報。多分、Javathrowableとの互換性のためのもの。
  • iOSDetails : iOS 特有のエラー情報。こちらも iOS との五感のためのものっぽい。

さて、試しに前回クラッシュした情報をエラーメッセージで表示するようなサンプルを作りました。 こんな感じです。

namespace AppCenter.Wpf
{
    using System;
    using System.Collections.Generic;
    using System.Windows;
    using Microsoft.AppCenter.Analytics;
    using Microsoft.AppCenter.Crashes;

    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
        {
            bool didAppCrash = await Crashes.HasCrashedInLastSessionAsync();
            if (didAppCrash)
            {
                ErrorReport crashReport = await Crashes.GetLastSessionCrashReportAsync();

                MessageBox.Show($"前回セッションでクラッシュしました。{Environment.NewLine}"
                                + $"- 発生日時 : {crashReport.AppErrorTime}{Environment.NewLine}"
                                + $"- エラー内容 : {crashReport.Exception.Message}"
                              , "Report"
                              , MessageBoxButton.OK
                              , MessageBoxImage.Information);
            }
        }
    }
}

例外を適当にスローすると App Center にクラッシュレポートが通知されます。初回、レポートが通知されるまで2,3分かかったのでちょっと待ったほうがいいかもしれません。 レポートされると以下のような結果が表示されます。

f:id:iyemon018:20190817190138p:plain

f:id:iyemon018:20190817190211p:plain

レポートを送信するかどうか判断する

何らかの条件によってクラッシュ レポートを送信したくない場合は次のようにコールバックを呼び出します。 例えば、アプリ固有の例外がスローされた場合、あるレベル以下の例外がスローされても通知しない、といった使い方でしょうか。 このコールバックはAppCenter.Start()よりも前に呼び出す必要があります。

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    // アプリ固有の CustomException がスローされた場合、ShouldReportProcessed = true (= 処理済み) であればレポートを出力しない。
    // ShouldReportProcessed = false (= 未処理) もしくは CustomException 以外の例外はレポートに通知する。
    Crashes.ShouldProcessErrorReport = (ErrorReport report)
                                            => !(report.Exception is CustomException customException)
                                                || !customException.ShouldReportProcessed;
    AppCenter.Start("xxxxxxxxxxxxxxxxxxxxxxxx", typeof(Analytics), typeof(Crashes));
}

クラッシュ レポートを送信するかどうかユーザーに確認する

クラッシュ レポートに個人情報などが含まれる場合、予めユーザーに確認しておくことでトラブルを防ぐことができます。 あるいは、プロダクトのポリシーによっては個人情報の収集はユーザーの意思がなくてはならない場合もあるでしょう。 Microsoft でもユーザーへの確認を推奨しています。

App Center でもこの仕組みを使うことができます。 Microsoft.AppCenter.Crashes.Crashes.ShouldAwaitUserConfirmationコールバックを呼び出すことで、レポート送信するかどうかを決定することができます。 このコールバックを使うことで、ユーザーが操作するまでクラッシュ レポートが送られなくなります。 ただし、このコールバックは UI を提供するものではないため、ダイアログは自前で呼び出す必要があります。

また、アプリとしてクラッシュ レポートを送信するかどうかは以下の API によって決定します。

Microsoft.AppCenter.Crashes.Crashes.NotifyUserConfirmation(Microsoft.AppCenter.Crashes.UserConfirmation.Send);

引数の Microsoft.AppCenter.Crashes.UserConfirmationによって、"送信する", "送信しない", "常に送信する" から選択することができます。

試しに書いてみたサンプルはこちら。

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    Crashes.ShouldAwaitUserConfirmation = () =>
                                            {
                                                MessageBoxResult result = MessageBox.Show("クラッシュレポートを送信します。よろしいでしょうか?"
                                                                                        , "Question"
                                                                                        , MessageBoxButton.YesNo
                                                                                        , MessageBoxImage.Question);

                                                if (result == MessageBoxResult.Yes)
                                                {
                                                    Crashes.NotifyUserConfirmation(UserConfirmation.Send);
                                                    return true;
                                                }
                                                else
                                                {
                                                    Crashes.NotifyUserConfirmation(UserConfirmation.DontSend);
                                                    return false;
                                                }
                                            };
    AppCenter.Start("xxxxxxxxxxxxxxxxxxxxxxxx", typeof(Analytics), typeof(Crashes));
}

クラシュ レポートのログ送信状態を取得する

Microsoft.AppCenter.Crashes.Crashes.ShouldAwaitUserConfirmationコールバックでクラッシュ レポートを送信する前と送信成功・失敗それぞれの場合ごとにコールバックが用意されています。 用途としてはレポートの送信ダイアログを表示したりとかでしょうか。 API は次のとおりです。

  • クラッシュ レポートを送信する前に呼ばれるコールバック
Microsoft.AppCenter.Crashes.Crashes.SendingErrorReport
  • クラッシュ レポートの送信成功後に呼ばれるコールバック
Microsoft.AppCenter.Crashes.Crashes.SentErrorReport
  • クラッシュ レポートの送信失敗後に呼ばれるコールバック
Microsoft.AppCenter.Crashes.Crashes.FailedToSendErrorReport

クラッシュ レポートに添付ファイルを追加する

レポートに画像ファイルやテキストファイル(添付ファイル)を追加する仕組みがあります。 例えばクラッシュした時点のキャプチャなどを送ったりする場合に使用するのでしょうか。 添付ファイルを送信するコールバックは次のとおりです。

Microsoft.AppCenter.Crashes.Crashes.GetErrorAttachments

注意点として、送信可能な添付ファイルのサイズは 7MB となっています。 この容量を超える場合、送信エラーとなります。 検証してませんが、多分Crashes.FailedToSendErrorReportが呼ばれるんじゃないでしょうか。

なお、呼びだされるコールバックの順序は次のようになります。

  1. Crashes.ShouldAwaitUserConfirmation
  2. Crashes.GetErrorAttachments
  3. Crashes.SendingErrorReport
  4. Crashes.SentErrorReport / Crashes.FailedToSendErrorReport

これらのコールバックを実行するサンプルを書いてみました。

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    Crashes.SendingErrorReport += (object sender, SendingErrorReportEventArgs sere) =>
                                    {
                                        // Your code, e.g. to present a custom UI.
                                        MessageBox.Show("クラッシュ レポートを送っています。"
                                                    , "Sending Crash Report"
                                                    , MessageBoxButton.OK
                                                    , MessageBoxImage.Information);
                                    };

    Crashes.SentErrorReport += (object sender, SentErrorReportEventArgs sere) =>
                                {
                                    // Your code, e.g. to hide a custom UI.
                                    MessageBox.Show("クラッシュ レポートの送信を完了しました。"
                                                    , "Send Crash Report Completed"
                                                    , MessageBoxButton.OK
                                                    , MessageBoxImage.Information);
                                };

    Crashes.FailedToSendErrorReport += (object sender, FailedToSendErrorReportEventArgs ftsere) =>
                                        {
                                            // Your code goes here.
                                            MessageBox.Show("クラッシュ レポートの送信に失敗しました。"
                                                            , "Send Crash Report Failed"
                                                            , MessageBoxButton.OK
                                                            , MessageBoxImage.Information);
                                        };

    Crashes.GetErrorAttachments = (ErrorReport report) =>
                                    {
                                        ErrorAttachmentLog textLog =
                                            ErrorAttachmentLog.AttachmentWithText("This is a text attachment.", "text.txt");

                                        byte[] imageBuffer;
                                        using (var bitmap = new Bitmap("test-image.png"))
                                            using (var ms = new MemoryStream())
                                        {
                                            bitmap.Save(ms, ImageFormat.Png);
                                            imageBuffer = ms.GetBuffer();
                                        }

                                        ErrorAttachmentLog binaryLog =
                                            ErrorAttachmentLog.AttachmentWithBinary(imageBuffer, "test-image.png", "image/jpeg");

                                        MessageBox.Show("Get Error Attachments");

                                        return new List<ErrorAttachmentLog> {textLog, binaryLog};
                                    };

    AppCenter.Start("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", typeof(Analytics), typeof(Crashes));
}

なお、送信された添付ファイルは Dignostics から該当のレポートを選択して ATTACHEMENTS タブから参照することができます。

f:id:iyemon018:20190818180121p:plain

クラッシュ レポートの送信有無を設定する

総合テストや受け入れテストなど、後工程のテストでクラッシュ レポートを送信したくないようなケースで以下の API を使用することで、レポートを送信するかどうかを決定することができます。

Microsoft.AppCenter.Crashes.Crashes.SetEnabledAsync

引数に true を設定することでレポートを送信し、false を設定することでレポート送信を無効化します。 この設定はデバイスのストレージ上(どこかは不明)に保存されるため、一度呼び出すと永続的に適応されるようです。

また、この送信有無を設定したかどうかは以下の API で判定することができます。

bool isEnabled = await Crashes.IsEnabledAsync();

エラー レポートを送信する

App Center ではクラッシュ レポートの他に、何らかの例外がスローされた場合にその内容を App Center に送信する機能があります。 そして、エラー レポートにはキーと値を持つプロパティを登録することができます。 エラー レポートはクラッシュ レポートと同じく Dignostics から参照することができます。

private void ExceptionButton_OnClick(object sender, RoutedEventArgs e)
{
    try
    {
        throw new ApplicationException($"これは手動で発生さました。- Exception ({DateTime.Now:yyyy-MM-dd HH:mm:ss})");
    }
    catch (ApplicationException exception)
    {
        var properties = new Dictionary<string, string>
                            {
                                { "Category", "Test" },
                            };
        Microsoft.AppCenter.Crashes.Crashes.TrackError(exception, properties);
    }
}

最後に

クラッシュ レポートに関してもいろいろな API が用意されていて検証した感じだとプレビュー版でも特に問題なく使えています。 機能的にも十分なのであとはどう使うかに重点をおいてプロダクトを開発していけば良さげです。

(WPF)App CenterにWPFが対応したので使ってみた

devblogs.microsoft.com

上記ページでApp Centerに WPF/WinForms が対応したとのことなので早速使ってみました。 私自身これまで App Center を使ったことがなかったため、このエントリでは導入から Analytics までをチュートリアル形式で記載しています。

開発環境

開発環境はこんな感じ。

なお、2019年8月8日時点で App Center の WPF/WinForms はプレビュー版のみの提供となっています。 そのため、.NET Coreには対応していません。 .NET Framework 4.5以上が対象となっています。 今後は.NET Core 3.0にも対応予定とのことなので期待したいところです。App Center のロードマップについては以下を参照してください。

github.com

また、今回私は以下のページを参考にしています。

docs.microsoft.com

App Centerの設定

App Center を開き、[Add new] - [Add new app] を選択して以下の様に OS は Windows, Platform は WPF を選択します。あとは[Add new app] を選択します。

f:id:iyemon018:20190808221428p:plain

[Settings] を開いて右上のメニューから [Copy app secret] を選択します。 ここでコピーした Secret は後ほど使うのでエディタにでもコピペしておいてください。

f:id:iyemon018:20190808221948p:plain

さて、これで App Center の準備は整いました。

アプリに組み込む

次はアプリです。 適当に.NET Framework 4.5以上の WPF アプリを作成してください。

まずは、NuGet から App Center のパッケージを取得します。

Install-Package Microsoft.AppCenter.Analytics -Version 2.2.1-preview
Install-Package Microsoft.AppCenter.Crashes -Version 2.2.1-preview

これを実行すると大量のアセンブリが追加されるのでしばらく待ちます。

準備が整ったらApp.xaml.csを以下のようにします。 まずは、アプリ セッションを開始して App Center と接続できることを確認します。 using は忘れずに。

namespace AppCenter.Wpf
{
    using System.Windows;
    using Microsoft.AppCenter;
    using Microsoft.AppCenter.Analytics;
    using Microsoft.AppCenter.Crashes;

    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            AppCenter.Start("<ここに Secret を貼り付ける>", typeof(Analytics), typeof(Crashes));
        }
    }
}

以上! 簡単ですね。

このままアプリを起動すると App Center の[Analytics] - [Overview] は次のようになります。

f:id:iyemon018:20190808222745p:plain

まずはこれでアプリが App Center に接続できたことを確認できたので、もうちょっと触ってみます。

チュートリアルはいくつかあるのですが、わかりやすいのが Event だったのでこちらを実装します。 ちなみに以下のページを参考にしています。

docs.microsoft.com

Event には以下のようなコードを追加して予めどのようなイベントが有るのかを定義します。

// 第一引数は name, 第二引数は parameter
Analytics.TrackEvent("Video clicked", new Dictionary<string, string> {
    { "Category", "Music" },
    { "FileName", "favorite.avi"}
});

MS Docs ページとコードのコメントを読む限りだとname, parameterには以下のような制限があるようです。 元の文章が英語なので間違ってたらごめんなさい…

  • Event に定義できる数は最大で 200 個。これはnameに最大 200 種類のイベント名称を定義できるということ。
  • nameは最大 256 byte まで。
  • 1種類の Event につき、プロパティ パラメータは 5 つまで。これはparameterに 5 種類のプロパティ名を設定できるということ。
  • parameterのプロパティ名は最大 64 byte まで。

実質 Event 名称 : 200 x パラメータ 5 で 1000 種類のイベントを計測することができそうです。

これをApp.xaml.csに追加します。

namespace AppCenter.Wpf
{
    using System.Collections.Generic;
    using System.Windows;
    using Microsoft.AppCenter;
    using Microsoft.AppCenter.Analytics;
    using Microsoft.AppCenter.Crashes;

    public partial class App : Application
    {
        #region Methods

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            AppCenter.Start("<ここに Secret を貼り付ける>", typeof(Analytics), typeof(Crashes));

            Analytics.TrackEvent("Example", new Dictionary<string, string>
                                            {
                                                {"Category", "Music"}, {"FileName", "favorite.avi"}
                                            });
        }
    }
}

例えば上記のExampleイベントを App Center で取得するには以下のようにします。

Analytics.TrackEvent("Example");

あとはこれを画面からボタンクリックなどのイベントハンドラから呼び出してやればOKです。

namespace AppCenter.Wpf
{
    using System.Collections.Generic;
    using System.Windows;
    using Microsoft.AppCenter.Analytics;

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void TestButton_OnClick(object sender, RoutedEventArgs e)
        {
            Analytics.TrackEvent("Example");
        }

        private void MusicEventButton_OnClick(object sender, RoutedEventArgs e)
        {
            Analytics.TrackEvent("Example", new Dictionary<string, string> {{"Category", "Music"}});
        }
    }
}

Analytics.TrackEventメソッドの第二引数にプロパティ名を指定することでより詳細なイベントの振り分けができそうです。

さて、これでアプリを起動して何回かボタンを押すと、 App Center の[Analytics] - [Events] は以下のようになります。

f:id:iyemon018:20190808224726p:plain

Exampleというイベントが追加されました。 イベントをクリックするとさらに詳細な情報を取得できます。

f:id:iyemon018:20190808224917p:plain f:id:iyemon018:20190808224934p:plain

Event はうまく使えばユーザーの利用状況とかかなり具体的かつ正確に収集できそうです。

最後に

さて、他にもクラッシュログとかプッシュ通知、アプリの自動配布なんかもあるそうです。 クラッシュログは Android の Crashlytics みたいなイメージでしょうか? アプリの自動配布もこれまでは Click once しかなかったので、代替案になるかも検証していきたいと思っています。

ちなみに今回のサンプルは App Center 初利用の私でも 1時間程度で組み込むことができました。

まだまだ App Center は触り始めたばかりで勉強不足な部分が多々あるので、もう少し色々触ってみようと思います。

Surface Bookがモニターアーム+ノートPCスタンドで快適に使えるようになった

私は自宅の開発環境はSurface Bookを使っています。 SurfaceなのでもちろんSurface Dockも使っています。

自宅の環境で悩んだ末にモニターアーム+ノートPCスタンドの環境が自分にピッタリだったので紹介したいと思います。

これまでの問題点

私は開発するときはできるだけ作業スペースを確保したいのですが、ノートPCってキーボードが邪魔じゃないですか? Surface Bookのいいところは、キーボードとディスプレイが分離型であることとディスプレイを前後入れ替えても問題なく使用できるところにあると思っています。 なので、ディスプレイを通常とは逆向きに接続して作業スペースを確保するように使用していました。

f:id:iyemon018:20190714191928j:plain

ただ、これでもテーブルとの接地面積は結構広く、ちょくちょくSurface Bookの位置を調整することがありました。 これが面倒だったのでどうにかできないかなーといろいろ調べてみました。

モニターアーム+ノートPCスタンドを使ってみた

で、調べた結果、すでに使ってたモニターアームに取り付けられるノートPCスタンドを見つけました。

設置するとこんな感じになります。

f:id:iyemon018:20190714192001j:plain

ディスプレイを反転して使ってるのはモニタの高さと合わせるためです。 Surface Book以外のコンバーチブルでないノートPCだとそのまま乗せるしかないのですがこういうところが地味に便利ですね。

さて、モニターアームに取り付けるということはある程度の高さがります。 なので地震なんかで揺れたりすると落ちたりするのかと思いましたが、ガッチリ固定することができるため問題ありません。 手で位置を調整しても気にならないレベルです。

f:id:iyemon018:20190714192031j:plain

モニターアームに乗せることでテーブル上の作業面積が広がりました。

これで快適ですね!!

さいごに

これ、会社にも導入したいなーと思ってたんですが、会社のデスクはモニターアームが設置できない構造になっているため、あえなく断念しています。 利用しているテーブルの構造やノートPCによっては使用できないパターンはあるかもしれませんので、現在の環境で利用できるかどうかきちんと確認する必要があります。

特に、モニターアームはクランプ式とグロメット式があるため、どちらの設置方法で使うかによって利用不可能なシーンも有ることでしょう。 参考程度に私が使用しているモニターアームはこちらです。

みなさんも試行錯誤してベストな作業環境を作ってみてください。

Azure Pipelines で.NET Core 3.0 Preview をビルドする

前回.NET FrameworkWPF プロジェクトを.NET Core 3.0 Preview へ移行したのですが、そのときAzure Pipelines ではビルドできないと思っていました。

iyemon018.hatenablog.com

しかし、@kkamegawa さんからのアドバイスを頂き、自動ビルドができることがわかりました。

今回はその手順をまとめます。

実行手順

まずはgrobal.jsonファイルを作成します。 このファイルは実行環境に複数の.NET Core SDK が存在する場合にgrobal.jsonに定義したバージョンのSDKを使用する事ができます。

docs.microsoft.com

grobal.jsonを作るにはプロジェクト フォルダ上でコマンドプロンプトを起動して以下のコマンドを実行します。

dotnet new globaljson --sdk-version <.NET Core のバージョン>

例えば.NET Core Preview4 の場合は以下のようになります。

dotnet new globaljson --sdk-version 3.0.100-preview4-011223

次にAzure Pipelines に.NET Core SDK Installer タスクを追加します。 タスクの設定はこんな感じ。

f:id:iyemon018:20190422221050p:plain

YAML だとこんな感じ。

steps:
- task: DotNetCoreInstaller@0
  displayName: 'Use .NET Core runtime 3.0.0-preview4-27615-11'
  inputs:
    version: '3.0.100-preview4-011223'

この.NET Core のバージョンは以下のページを参照してください。 注意すべき点としてSDK とRuntime でバージョン番号の表記が若干異なります。 タスクのPackage to installの種別を確認しておきましょう。

github.com

なお、.NET Core 3.0 なのでVS 2019 にしないとビルドできませんのでご注意を。

あとはビルドをキューに登録して実行すればOKです。