Quantcast
Channel: SourceChord
Viewing all 153 articles
Browse latest View live

そのReactほんとに必要ですか?~もうすぐElectronで使えるようになるWeb Componentsの世界~

$
0
0

この記事はelectronアドベントカレンダー 2016 21日目の記事です。
遅くなってしまい申し訳ありません。。。

アドベントカレンダーのリンクが間違っていたので修正しました・・・汗

前置き

↓去年はこんな記事を書いていました。

このCSS Grid Layout Module Level1ですが、少しずつ仕様の策定が進み、とうとう勧告候補の段階まできました。
CSS Grid Layout Module Level 1CSS Grid Layout Module Level 1 (日本語訳)

CanIUseを見ると、もうすぐFirefoxChromeでの対応が行われるようです。
http://caniuse.com/#search=grid
https://developer.mozilla.org/ja/Firefox/Releases/52
f:id:minami_SC:20161225123610p:plain

未来は意外と早く来るもんですね。

本題

前置きが長くなりましたが、ここからが本題です。

近年のフロントエンド界隈では、ReactやらAngularやらのコンポーネント指向なライブラリ/フレームワークがもてはやされていると思います。

ですが、「ちょっとUIを部品化してみたい」という程度のケースでは、Reactなどのライブラリは少々OverKillな代物では、と感じます。
この手のライブラリを導入すると、なんだかんだで大量のツールチェインが必要になって大変ですよね。

一方で、UIをコンポーネント単位で部品化するための方法としてWebComponentsという仕様の策定が進められています。
主要ブラウザ全てで対応されるのはまだまだ先の話になりますが、このWebComponentsの機能はChrome54以降ではすでに実装されています。

Electron環境に限定すれば、わざわざReactやAngularを使わずとも、Web標準な方法でコンポーネント指向開発ができる未来が、もうすぐそこに来ています。

この記事では、そんなWebComponentsについてご紹介したいと思います。

補足

ReactにはUIのコンポーネント化だけでなく、ステートレスなコンポーネントとか一方向のデータフローなど、単純にUIを部品化する以上の目的があると思います。
なので、Web Componentsが使えるようになれば、ReactやAngularなどが不要になるということはないと思います。
開発対象の規模や用途に応じてケースバイケースで技術選定する上での、もう一つの選択肢ができるというイメージでしょうか?

ElectronのChrome54対応

現時点(2016/12/25現在)でElectronの内部で使用されているChromiumは53.0.2785.143となっています。

そのため、今のElectron最新版(v1.4系)では、WebComponentsの機能はまだ一部しか使えません。
ということで、この記事のサンプルコードは現在のElectronでは使用できません。
(Electronアドベントカレンダーの記事なのに・・・)

次のElectronメジャーバージョンアップでは、Chromium54以降のバージョンのものになると思われます。
以下のプルリクで対応が進められているようなので、動向が気になる人はこれをウォッチしているとよいかと。
Chrome 54 update by groundwater · Pull Request #7909 · electron/electron · GitHub

ElectronとChromiumの関係について

余談ですが、Electronは通常であればChromiumのバージョンアップに1~2週間遅れくらいで追従していました。
http://electron.atom.io/docs/faq/#when-will-electron-upgrade-to-latest-chrome

しかし、Chrome54から、Chromeのビルドツールがgypからgnというツールに変わりました。
これに伴い、Electron内部で使用するChromiumのバージョンアップに、いつもより多くの時間がかかっているようです。
Chrome54が9月にリリースされたので、12月にはElectronでもこの機能が使えるようになる、、、と踏んでこんな記事を用意してましたが、完全に誤算でした。。。

Web Componentsとは

WebComponents自体の詳細な解説は、すでにWeb上に優良な記事が多数あるので、ここでは詳細な説明は割愛し概要だけ取り上げることとします。

一言で説明すると↓こんな風に、独自タグを定義して別途作りこんだUI部品を組み込んでいけるような仕組みです。

<body><sample-element></sample-element></body>

使ってみる

Web Componentsを構成する要素

Web Componentsは、以下の4つの技術を組み合わせて実現されています。

  • Custom Elements v1
  • HTML templates
  • HTML Imports
  • Shadow DOM v1

これら4つの要素を順番に使い、Web Componentsの基本的な動作を見ていきます。

サンプルコード

現時点のElectronでは、まだWebComponentsの機能は未実装な部分があるので、今回のサンプルコード類は動かせません。
サンプルコードは以下の場所に上げていますが、これは普通に静的なWebサーバーを立ち上げて、ブラウザで動作確認をするものです。

Chrome54以降のブラウザであれば、このサンプルコードの動作確認ができるかと思います。
一応、Electronの次バージョンがリリースされたら、Electron用のサンプルコードを作って追記しようと思います。

Custom Elements v1

まずは、Custom Elementsから。
Custom Elementsでは、独自タグを定義してhtmlやJavaScriptから利用できるようにすることができます。
Web Componentsの肝になる部分です。

HTMLElementなどのベースとなる型を継承して、独自タグ用のクラスを作ります。
そして、customElements.define()という関数に、タグ名と先ほど作成したクラスを指定して実行します。

index.js

class SampleElement extends HTMLElement {
    constructor() {super();
    }
    connectedCallback() {this.innerHTML = `
<div>
    <h1>Sample Component</h1>
    <p>
        CustomElements v1のサンプル
    </p>
    <button>sample</button>
</div>`;
    }}

customElements.define('sample-element', SampleElement);

これでsample-elementというタグが定義されて、html上から使用できるようになります。
以下のように書いてページを表示してみると、先ほどinnerHTMLに文字列で渡したDOM要素が描画されることが確認できます。

index.html

<!DOCTYPE html><html><head><metacharset="UTF-8"><title>Document</title><scriptsrc="index.js"></script></head><body><sample-element></sample-element></body></html>

f:id:minami_SC:20161225123759p:plain

HTML templatesと組み合わせる

先ほどの例では、コンポーネントの中身となるDOM要素を、JavaScript中に文字列として定義していました。

文字列として書いてしまっているので、エディタでのシンタックスハイライトも効かないですし、コード短縮化など、htmlに関わる各種ツール類との連携もできません。

これはイケてませんね。

ということで、templateタグを使い、DOM要素の定義をhtmlファイル側に移動します。

index.js

class SampleElement extends HTMLElement {
    constructor() {super();
    }
    connectedCallback() {const template = document.querySelector('#sample-element-template');
        const instance = template.content.cloneNode(true);
        this.appendChild(instance);
    }}

customElements.define('sample-element', SampleElement);

index.html

<!DOCTYPE html><html><head><metacharset="UTF-8"><title>Document</title><scriptsrc="index.js"></script><templateid="sample-element-template"><div><h1>Sample Component</h1><p>
                CustomElements v1のサンプル
            </p><button>sample</button></div></template></head><body><sample-element></sample-element></body></html>

f:id:minami_SC:20161225123807p:plain
実行結果は特に変わりません。

HTML Imports

html側にコンポーネントのDOM要素の定義を持ってくることができました。 しかし、このままではコンポーネント利用者側のhtmlに、templateタグによるコンポーネント定義が残ってしまいます。

これらを、HTML Importsの機能を使って外部にもっていきましょう。

ついでにフォルダ構成なども変更し、以下のような構成にしてみます。
f:id:minami_SC:20161225123820p:plain

sample-elementというフォルダ内に、コンポーネント定義をすべて閉じ込めることができました。

sample-element/sample-element.js

class SampleElement extends HTMLElement {
    constructor() {super();
    }
    connectedCallback() {const ownerDocument = document.currentScript.ownerDocument;
        const template = ownerDocument.querySelector('#sample-element-template');
        const instance = template.content.cloneNode(true);
        this.appendChild(instance);
    }}

customElements.define('sample-element', SampleElement);

sample-element/sample-element.html

<templateid="sample-element-template"><div><h1>Sample Component</h1><p>
            CustomElements v1のサンプル
        </p><button>sample</button></div></template><scriptsrc="sample-element.js"></script>

index.html
コンポーネント利用者側は、HTML Importsを使って以下のようにコンポーネント定義ファイルを読み込むだけで使えるようになります。

<linkrel="import"href="sample-element/sample-element.html">
<!DOCTYPE html><html><head><metacharset="UTF-8"><title>Document</title><linkrel="import"href="sample-element/sample-element.html"></head><body><sample-element></sample-element></body></html>

実行結果は変わらないので省略します。

Shadow DOM

CSSには名前空間のようなスコープはありません。
意図しない場所への影響を与えないように、クラス名の命名規則で頑張ったり、セレクタの書き方を工夫して衝突回避していました。

ですが、Shadow DOMを使えば、Shadow Rootという他のDOM要素からの影響を受けない領域を作ることができます。

Shadow Rootの内部で書いたスタイルなどは、コンポーネントの外部には影響を与えないので、クラスの命名規則でムリヤリ頑張ったりしなくても、スタイルの衝突を防ぐことができます。

sample-element/sample-element.js

class SampleElement extends HTMLElement {
    constructor() {super();
    }
    connectedCallback() {const ownerDocument = document.currentScript.ownerDocument;
        const template = ownerDocument.querySelector('#sample-element-template');
        const instance = template.content.cloneNode(true);

        // ShadowDOMの構築let shadowRoot = this.attachShadow({mode: 'open'});
        shadowRoot.appendChild(instance);
    }}

customElements.define('sample-element', SampleElement);

sample-element/sample-element.html

<templateid="sample-element-template"><style>.title{color: red;
        }button{background: lightblue;
        }</style><div><h1class="title">Sample Component</h1><p>
            CustomElements v1のサンプル
        </p><button>sample</button></div></template><scriptsrc="sample-element.js"></script>

index.html

<!DOCTYPE html><html><head><metacharset="UTF-8"><title>Document</title><style>button{font-size: 20px;
        }</style><linkrel="import"href="sample-element/sample-element.html"></head><body><sample-element></sample-element><hr /><h2class="title">Web Componentsの呼び出し元</h2><button>ほげほげ</button></body></html>

f:id:minami_SC:20161225123856p:plain
コンポーネントの内部/外部で、スタイルが互いに干渉していないことが確認できます。

具体的なサンプル

以上で、WebComponentsを構成する一通りの要素の使い方を見てきました。

具体的なコンポーネントの例として、シンプルなストップウォッチのコンポーネントを作ってみました。

stopwatch-element.html

<templateid="stopwatch-element-template"><div><spanid="content"></span><buttonid="btnStartStop">start</button><buttonid="btnReset">reset</button></div></template><scriptsrc="stopwatch-element.js"></script>

stopwatch-element.js

class StopwatchElement extends HTMLElement {
    constructor() {super();
        this.baseTime = null;
        this.offset = null;
        this.timerId = null;
    }/** DOMに要素が追加された際に発生するイベント */
    connectedCallback() {const ownerDocument = document.currentScript.ownerDocument;
        const template = ownerDocument.querySelector('#stopwatch-element-template');
        const instance = template.content.cloneNode(true);

        let shadowRoot = this.attachShadow({mode: 'open'});
        shadowRoot.appendChild(instance);

        this.content = shadowRoot.querySelector("#content");
        this.showTime(0);
        // 各種イベントハンドラの設定this.btnStartStop = shadowRoot.querySelector("#btnStartStop");
        this.btnStartStop.addEventListener('click', this.onStartStop.bind(this));
        let btnReset = shadowRoot.querySelector("#btnReset");
        btnReset.addEventListener('click', this.onReset.bind(this));
    }/** start/stopボタン押下時のイベント */
    onStartStop() {if (!this.timerId) {this.btnStartStop.textContent = "stop";
            this.baseTime = Date.now(); 
            this.timerId = setInterval(() => {let ellapse = this.offset + Date.now() - this.baseTime;
                this.showTime(ellapse);
            }, 10);
        }else{
            clearInterval(this.timerId);
            let ellapse = Date.now() - this.baseTime;
            this.offset += ellapse;
            this.timerId = null;
            this.btnStartStop.textContent = "start";
        }}/** resetボタン押下時のイベント */
    onReset() {
        clearInterval(this.timerId);
        this.showTime(0);
        this.timerId = null;
        this.baseTime = null;
        this.offset = null;
    }/** 経過時間を表示するための関数 */
    showTime(time) {let pad = (num, digit) => ('000' + Math.floor(num)).slice(-digit);
        let h = pad(time / (60*60*1000), 2);
        time = time % (60*60*1000);
        let m = pad(time / (60*1000), 2);
        time = time % (60*1000);
        let s = pad(time / 1000, 2);
        time = time % 1000
        let ms = pad(time, 3);
        this.content.textContent = `${h}:${m}:${s}.${ms}`;
    }}

customElements.define('stopwatch-element', StopwatchElement);

index.html

<!DOCTYPE html><html><head><metacharset="UTF-8"><title>Document</title><linkrel="import"href="stopwatch-element/stopwatch-element.html"></head><body><stopwatch-element></stopwatch-element><hr /><stopwatch-element></stopwatch-element><hr /></body></html>

f:id:minami_SC:20161225124247p:plain

ただテキストで経過時間が表示されるだけのものですが、コンポーネントに関わるUI定義やロジックを、再利用しやすいように独立して記述できるのがわかると思います。

まとめ

以上、駆け足でElectronでのWeb Componentsの使い方を見てきました。

この記事のサンプルでは、フロント側では特にライブラリを使用していません。
(ブラウザでの動作確認のために、テスト用Webサーバーとしてnode-staticを使っているだけ。)

ReactもAngularも使ってないですが、ちゃんとコンポーネント指向な開発ができる、という雰囲気はつかめていただけたのではないでしょうか?
未来感ハンパねぇですね!!

Electronの次のメジャーバージョンアップを楽しみに待ちましょう♪


WPF用にNotifyIconクラスをラップしてみた

$
0
0

C#で常駐アプリなどを作り、タスクトレイにアイコンを出す場合には、Win Formsで用意されているNotifyIconというクラスを使います。

以前、このクラスを使って常駐アプリを作る方法を↓に書きました。

この方法だと、使うたびにWinFormsのデザイナを利用して設定したりする必要があり面倒です。
ということで、NotifyIconに関する処理を1ファイルにまとめ、簡単に再利用できるようにしてみました。

今回は、以前のサンプルと比べ、以下のような点を修正してます。

  • Formsのコンポーネントクラスではなく、NotifyIconExという普通のクラスとして実装
    • Win Formsのデザイナをわざわざ使いたくなかったので、すべてC#のコードで書くように修正しました。
    • タスクトレイ関係の処理はすべてこのクラスの中に閉じ込めました。
  • メニューを、WPFのContextMenuクラスで作れるように修正
  • バルーン表示に関わる各種プロパティ/メソッドを利用できるようにラップ
  • 各種イベント類もラップ

コード一式は以下のリポジトリに上げています。
WPFSamples/NotifyIconSample at master · sourcechord/WPFSamples · GitHub

使い方

準備

  • プロジェクトにアセンブリを追加
  • 以下のNotifyIconExクラスをプロジェクトに加える
NotifyIconExクラス
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NotifyIconSample
{
    publicenum ToolTipIconEx
    {
        None = 0,
        Info = 1,
        Warning = 2,
        Error = 3
    }

    publicclass NotifyIconEx : IDisposable
    {
        private System.Windows.Forms.NotifyIcon _notify;

        publicstring Text
        {
            get { returnthis._notify.Text; }
            set { this._notify.Text = value; }
        }

        publicbool Visible
        {
            get { returnthis._notify.Visible; }
            set { this._notify.Visible = value; }
        }

        public Uri IconPath
        {
            set
            {
                if (value == null) { return; }
                var iconStream = System.Windows.Application.GetResourceStream(value).Stream;
                this._notify.Icon = new System.Drawing.Icon(iconStream);
            }
        }

        public System.Windows.Controls.ContextMenu ContextMenu { get; set; }

        public ToolTipIconEx BalloonTipIcon
        {
            get { return (ToolTipIconEx)this._notify.BalloonTipIcon; }
            set { this._notify.BalloonTipIcon = (System.Windows.Forms.ToolTipIcon)value; }
        }

        publicstring BalloonTipTitle
        {
            get { returnthis._notify.BalloonTipTitle; }
            set { this._notify.BalloonTipTitle = value; }
        }

        publicstring BalloonTipText
        {
            get { returnthis._notify.BalloonTipText; }
            set { this._notify.BalloonTipText = value; }
        }

        publicvoid ShowBalloonTip(int timeout)
        {
            this._notify.ShowBalloonTip(timeout);
        }

        publicvoid ShowBalloonTip(int timeout, string tipTitle, string tipText, ToolTipIconEx tipIcon)
        {
            var icon = (System.Windows.Forms.ToolTipIcon)tipIcon;
            this._notify.ShowBalloonTip(timeout, tipTitle, tipText, icon);
        }

        public NotifyIconEx()
            : this(null) { }

        public NotifyIconEx(Uri iconPath)
            : this(iconPath, null) { }

        public NotifyIconEx(Uri iconPath, string text)
            : this(iconPath, text, null) {}

        public NotifyIconEx(Uri iconPath, string text, System.Windows.Controls.ContextMenu menu)
        {
            // 各種プロパティを初期化this._notify = new System.Windows.Forms.NotifyIcon();
            this.IconPath = iconPath;
            this.Text = text;
            this.ContextMenu = menu;

            // マウス右ボタンUpのタイミングで、ContextMenuの表示を行う// ダミーの透明ウィンドウを表示し、このウィンドウのアクティブ状態を用いてContextMenuの表示/非表示を切り替えるthis._notify.MouseUp += (s, e) =>
            {
                if (e.Button != System.Windows.Forms.MouseButtons.Right) { return; }

                var win = new System.Windows.Window()
                {
                    WindowStyle = System.Windows.WindowStyle.None,
                    ShowInTaskbar = false,
                    AllowsTransparency = true,
                    Background = System.Windows.Media.Brushes.Transparent,
                    Content = new System.Windows.Controls.Grid(),
                    ContextMenu = this.ContextMenu
                };

                var isClosed = false;
                win.Activated += (_, __) =>
                {
                    if (win.ContextMenu != null)
                    {
                        win.ContextMenu.IsOpen = true;
                    }
                };
                win.Closing += (_, __) =>
                {
                    isClosed = true;
                };

                win.Deactivated += (_, __) =>
                {
                    if (win.ContextMenu != null)
                    {
                        win.ContextMenu.IsOpen = false;
                    }
                    if (!isClosed)
                    {
                        win.Close();
                    }
                };
                
                // ダミーウィンドウ表示&アクティブ化をする。// ⇒これがActivatedイベントで、ContextMenuが表示される
                win.Show();
                win.Activate();
            };

            this._notify.Visible = true;
        }

        #region NotifyIconクラスの各種イベントをラップするpublicevent EventHandler BalloonTipClicked
        {
            add { this._notify.BalloonTipClicked += value; }
            remove { this._notify.BalloonTipClicked -= value; }
        }

        publicevent EventHandler BalloonTipClosed
        {
            add { this._notify.BalloonTipClosed += value; }
            remove { this._notify.BalloonTipClosed -= value; }
        }

        publicevent EventHandler BalloonTipShown
        {
            add { this._notify.BalloonTipShown += value; }
            remove { this._notify.BalloonTipShown -= value; }
        }

        publicevent EventHandler Click
        {
            add { this._notify.Click += value; }
            remove { this._notify.Click -= value; }
        }

        publicevent EventHandler Disposed
        {
            add { this._notify.Disposed += value; }
            remove { this._notify.Disposed -= value; }
        }

        publicevent EventHandler DoubleClick
        {
            add { this._notify.DoubleClick += value; }
            remove { this._notify.DoubleClick -= value; }
        }
        #endregion        #region IDisposable Supportprivatebool disposedValue = false;

        protectedvirtualvoid Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    this._notify.Dispose();
                }

                disposedValue = true;
            }
        }

        publicvoid Dispose()
        {
            Dispose(true);
        }
        #endregion

    }
}

タスクトレイにアイコン表示

private NotifyIconEx _notify;

        public MainWindow()
        {
            InitializeComponent();

            var iconPath = new Uri("pack://application:,,,/NotifyIconSample;component/Icon1.ico", UriKind.Absolute);
            this._notify = new NotifyIconEx(iconPath, "Notify Title");
        }

タスクトレイ登録時には、アイコンが必要になります。
プロジェクトに、「ビルドアクション:Resource」に設定したicoファイルを追加しておき、Uri指定することでアイコンを設定できます。
f:id:minami_SC:20170211125013p:plain

お片付け

Disposeメソッドを呼び出すと、タスクトレイアイコンを破棄します。

protectedoverridevoid OnClosed(EventArgs e)
        {
            base.OnClosed(e);

            // ウィンドウを閉じる際に、タスクトレイのアイコンを削除する。this._notify.Dispose();
        }

Disposeをせずにアプリを終了すると、タスクトレイにアイコンが残るので注意してください。
(タスクトレイのアイコン上にマウスカーソルを乗せると消えますが・・・)

タスクトレイのアイコンからContextMenu表示

以下のようにすると、XAML上で定義したContextMenuを表示できます。

MainWindow.xaml
<Window.Resources><ContextMenu x:Key="sampleWinMenu"><MenuItem Header="ShowBalloon"Click="MenuItem_Show_Balloon_Click" /><Separator /><MenuItem Header="Show"Click="MenuItem_Show_Click" /><MenuItem Header="Exit"Click="MenuItem_Exit_Click" /></ContextMenu></Window.Resources>
MainWindow.xaml.cs
public MainWindow()
        {
            InitializeComponent();

            var iconPath = new Uri("pack://application:,,,/NotifyIconSample;component/Icon1.ico", UriKind.Absolute);
            var menu = (ContextMenu)this.FindResource("sampleWinMenu");
            this._notify = new NotifyIconEx(iconPath, "Notify Title", menu);
        }

ContextMenuは、タスクトレイのアイコン右クリックで表示されます。
f:id:minami_SC:20170211125036p:plain

バルーン表示

以下のメソッドでバルーン表示できます。

this._notify.ShowBalloonTip(1000, "tipTitle", "tipText", ToolTipIconEx.Info);

ちなみに、Windows10ではバルーンはこんな風に表示されます。
f:id:minami_SC:20170211125027p:plain

以前のものより、だいぶカッコよくなってますね!!

タスクトレイのメニューについて

FormsのNotifyIconクラスでメニューを出す場合、FormsのContextMenuStripクラスを使ってメニューを構築します。

ですが、WPFでの開発に慣れてくると、Formsのデザイナで作るこのContextMenuStripクラスは使いにくく感じます。
ContextMenuもXAML上で定義したいですよね。。。

ということで、NotifyIconのMouseUpイベントなどを使い、WPFのContextMenuを表示するようにしました。

しかし、ここで問題が、、、

タスクトレイ上から、WPFのContextMenuを表示すると、メニューが非アクティブ状態になっても消えずに残り続けてしまいます。

原因や対処方法などは、以下のリンクを参考にやってみました。
http://blogs.wankuma.com/youryella/archive/2009/11/01/182630.aspx
http://copycodetheory.blogspot.jp/2012/07/notify-icon-in-wpf-applications.html

ContextMenuはアプリケーションの持つウィンドウのアクティブ状態などを監視して、自動で閉じる処理を行っているようです。
なので、アクティブなウィンドウがない状態では、ContextMenuが非アクティブになっても閉じることができないようです。

ということで、上記リンクなどを参考に以下のような処理を行っています。

  • ContextMenu表示時に透明なダミーウィンドウを作りアクティブ化
  • 上記ウィンドウが非アクティブになったらContextMenuを閉じる(& ダミーのウィンドウも閉じる)

だいぶトリッキーですね・・・orz

その他

ここで作ったサンプルコードでは、タスクトレイのコンテキストメニューを自動で閉じるためにトリッキーな処理をしています。

手軽にタスクトレイやトレイのメニューを使うにはいいかもしれませんが、
この手のタスクトレイ関係の機能をガッツリと作りこみたい場合には、↓のライブラリのようなものを別途使用したほうがよいかもしれません。
http://www.hardcodet.net/wpf-notifyicon
https://www.nuget.org/packages/Hardcodet.NotifyIcon.Wpf/

WPFでホットキーの登録

$
0
0

WPFでホットキーの登録を行うサンプルを書いてみました。

Win32のRegisterHotKey/UnregisterHotKeyというAPIを呼び出すことで、ホットキーの登録/登録解除ができます。
このAPIでホットキーを登録しておくと、アプリがアクティブでないときでも有効なグローバルなホットキーとなります。

サンプル

HotKeyHelperというクラスを作り、以下のように登録/登録解除できるようにしました。

MainWindow.xaml.cs
/// <summary>/// MainWindow.xaml の相互作用ロジック/// </summary>publicpartialclass MainWindow : Window
    {
        private HotKeyHelper _hotkey;
        public MainWindow()
        {
            InitializeComponent();
            // HotKeyの登録this._hotkey = new HotKeyHelper(this);
            this._hotkey.Register(ModifierKeys.Control | ModifierKeys.Shift,
                                  Key.X,
                                  (_, __) => { MessageBox.Show("HotKey"); });
        }

        protectedoverridevoid OnClosed(EventArgs e)
        {
            base.OnClosed(e);

            // HotKeyの登録解除this._hotkey.Dispose();
        }
    }
HotKeyHeloper.cs

続いてHotKeyHelperの中身です。

このクラスで、複数のホットキー登録を管理します。
また、IDisposableを実装しておき、Disposeのタイミングですべてのホットキー登録を解除するようにしています。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;

namespace HotKeySample
{
    publicclass HotKeyItem
    {
        public ModifierKeys ModifierKeys { get; private set; }
        public Key Key { get; private set; }
        public EventHandler Handler { get; private set; }

        public HotKeyItem(ModifierKeys modKey, Key key, EventHandler handler)
        {
            this.ModifierKeys = modKey;
            this.Key = key;
            this.Handler = handler;
        }
    }

    /// <summary>/// HotKey登録を管理するヘルパークラス/// </summary>publicclass HotKeyHelper : IDisposable
    {
        private IntPtr _windowHandle;
        private Dictionary<int, HotKeyItem> _hotkeyList = new Dictionary<int, HotKeyItem>();

        privateconstint WM_HOTKEY = 0x0312;

        [DllImport("user32.dll")]
        privatestaticexternint RegisterHotKey(IntPtr hWnd, int id, int modKey, int vKey);

        [DllImport("user32.dll")]
        privatestaticexternint UnregisterHotKey(IntPtr hWnd, int id);

        public HotKeyHelper(Window window)
        {
            var host = new WindowInteropHelper(window);
            this._windowHandle = host.Handle;

            ComponentDispatcher.ThreadPreprocessMessage += ComponentDispatcher_ThreadPreprocessMessage;
        }

        privatevoid ComponentDispatcher_ThreadPreprocessMessage(ref MSG msg, refbool handled)
        {
            if (msg.message != WM_HOTKEY) { return; }

            var id = msg.wParam.ToInt32();
            var hotkey = this._hotkeyList[id];

            hotkey?.Handler
                  ?.Invoke(this, EventArgs.Empty);
        }

        privateint _hotkeyID = 0x0000;

        privateconstint MAX_HOTKEY_ID = 0xC000;

        /// <summary>/// 引数で指定された内容で、HotKeyを登録します。/// </summary>/// <paramname="modKey"></param>/// <paramname="key"></param>/// <paramname="handler"></param>/// <returns></returns>publicbool Register(ModifierKeys modKey, Key key, EventHandler handler)
        {
            var modKeyNum = (int)modKey;
            var vKey = KeyInterop.VirtualKeyFromKey(key);

            // HotKey登録while(this._hotkeyID < MAX_HOTKEY_ID)
            {
                var ret = RegisterHotKey(this._windowHandle, this._hotkeyID, modKeyNum, vKey);

                if (ret != 0)
                {
                    break;
                }

                this._hotkeyID++;
            }

            if (this._hotkeyID < MAX_HOTKEY_ID)
            {
                // HotKeyのリストに追加
                var hotkey = new HotKeyItem(modKey, key, handler);
                this._hotkeyList.Add(this._hotkeyID, hotkey);
                returntrue;
            }
            else
            {
                returnfalse;
            }
        }

        /// <summary>/// 引数で指定されたidのHotKeyを登録解除します。/// </summary>/// <paramname="id"></param>/// <returns></returns>publicbool Unregister(int id)
        {
            var ret = UnregisterHotKey(this._windowHandle, id);
            return ret == 0;
        }

        /// <summary>/// 引数で指定されたmodKeyとkeyの組み合わせからなるHotKeyを登録解除します。/// </summary>/// <paramname="modKey"></param>/// <paramname="key"></param>/// <returns></returns>publicbool Unregister(ModifierKeys modKey, Key key)
        {
            var item = this._hotkeyList
                           .FirstOrDefault(o => o.Value.ModifierKeys == modKey && o.Value.Key == key);
            var isFound = !item.Equals(default(KeyValuePair<int, HotKeyItem>));

            if (isFound)
            {
                var ret = Unregister(item.Key);
                if (ret)
                {
                    this._hotkeyList.Remove(item.Key);
                }
                return ret;
            }
            else
            {
                returnfalse;
            }
        }

        /// <summary>/// 登録済みのすべてのHotKeyを解除します。/// </summary>/// <returns></returns>publicbool UnregisterAll()
        {
            var result = true;
            foreach(var item inthis._hotkeyList)
            {
                result &= this.Unregister(item.Key);
            }

            return result;
        }

        #region IDisposable Supportprivatebool disposedValue = false;

        protectedvirtualvoid Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // マネージリソースの破棄
                }

                // アンマネージリソースの破棄this.UnregisterAll();

                disposedValue = true;
            }
        }

        ~HotKeyHelper()
        {
            Dispose(false);
        }

        publicvoid Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion

    }
}

http://sourcechord.hatenablog.com/entry/2017/02/11/125649
↑に書いたタスクトレイ常駐の方法と組み合わせると、
常駐アプリ作ったりするときに役に立つかな、、、と思います。

OpenCvSharpで透視投影の補正

$
0
0

OpenCvSharpを使って、透視変換を行ってみました。

staticvoid Main(string[] args)
        {
            // 入力画像中の四角形の頂点座標
            var srcPoints = new Point2f[] {
                new Point2f(69, 110),
                new Point2f(81, 857),
                new Point2f(1042, 786),
                new Point2f(1038, 147),
            };

            // srcで指定した4点が、出力画像中で位置する座標
            var dstPoints = new Point2f[] {
                new Point2f(0, 0),
                new Point2f(0, 480),
                new Point2f(640, 480),
                new Point2f(640, 0),
            };

            using (var matrix = Cv2.GetPerspectiveTransform(srcPoints, dstPoints))
            using (var src = new Mat("Images/1.jpg"))
            using (var dst = new Mat(new Size(640, 480), MatType.CV_8UC3))
            {
                // 透視変換
                Cv2.WarpPerspective(src, dst, matrix, dst.Size());
                using (new Window("result", dst))
                {
                    Cv2.WaitKey();
                }
            }
        }

こんな感じのコードで、この画像が
f:id:minami_SC:20170220232931j:plain

こうなる!!
f:id:minami_SC:20170220232940j:plain

これはなかなか便利そうですね。
カメラ画像を利用する際など、いろんな場面で活用できそうです。

OpenCvSharpで顔検出

$
0
0

今度は顔検出をやってみました。

OpenCvSharpのサンプルコードをベースにちょっと書き換えただけですが、意外とシンプルなコードで書けるもんですね。

今回は程よいサンプル画像がないので、コードだけφ(..)メモメモ

staticvoid Main(string[] args)
        {
            // カスケード分類器の準備
            var haarCascade = new CascadeClassifier("Data/haarcascade_frontalface_default.xml");

            using (var src = new Mat("Images/2.jpg"))
            using (var gray = new Mat())
            {
                var result = src.Clone();
                Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);

                // 顔検出
                Rect[] faces = haarCascade.DetectMultiScale(
                    gray, 1.08, 2, HaarDetectionType.FindBiggestObject, new Size(50, 50));

                // 検出した顔の位置に円を描画foreach (Rect face in faces)
                {
                    var center = new Point
                    {
                        X = (int)(face.X + face.Width * 0.5),
                        Y = (int)(face.Y + face.Height * 0.5)
                    };
                    var axes = new Size
                    {
                        Width = (int)(face.Width * 0.5),
                        Height = (int)(face.Height * 0.5)
                    };
                    Cv2.Ellipse(result, center, axes, 0, 0, 360, new Scalar(255, 0, 255), 4);
                }

                using (new Window("result", result))
                {
                    Cv2.WaitKey();
                }
            }
        }

UWP Community Toolkit 1.3がリリースされてました

$
0
0

UWP Community Toolkit 1.3がリリースされてました。 V1.2/V1.3で色々と便利なコントロールが追加されているので、 ざっとめぼしい部分を見繕って、使い方をまとめておこうと思います。

MasterDetailView

MasterDetailsView - UWPCommunityToolkit

これは、V1.2で追加されていたコントロールです。

よくある、Master-DetailパターンなUIを簡単に実現するためのコントロールです。
f:id:minami_SC:20170228235405p:plain

ItemTemplateで、Master側のリスト表示のテンプレートを設定し、
DetailsTemplateで、Detail側の各項目を詳細表示する部分のテンプレートを設定します。

MainPage.xaml

<Page.Resources><DataTemplate x:Key="ListTemplate"x:DataType="local:MenuItem"><StackPanel Margin="0,8"><TextBlock Style="{ThemeResource SubtitleTextBlockStyle}"Text="{x:Bind Title}" /><TextBlock Text="{x:Bind ImagePath}" /></StackPanel></DataTemplate><DataTemplate x:Key="DetailsTemplate"x:DataType="local:MenuItem"><Grid><Grid.RowDefinitions><RowDefinition Height="Auto" /><RowDefinition /></Grid.RowDefinitions><RelativePanel Margin="5"><Ellipse x:Name="FromEllipse"Width="50"Height="50"Fill="Gray" /><TextBlock Margin="12,-6,0,0"RelativePanel.RightOf="FromEllipse"Style="{ThemeResource SubtitleTextBlockStyle}"Text="{x:Bind Title}" /><TextBlock RelativePanel.Below="FromEllipse"Text="{x:Bind ImagePath}" /></RelativePanel><Image Grid.Row="1"Margin="5"Source="{x:Bind ImagePath}"Stretch="Uniform" /></Grid></DataTemplate></Page.Resources><Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><controls:MasterDetailsView ItemsSource="{x:Bind List}"ItemTemplate="{StaticResource ListTemplate}"DetailsTemplate="{StaticResource DetailsTemplate}"MasterHeader="ヘッダー"MasterPaneWidth="200"NoSelectionContent="要素が選択されていません"/></Grid>

MainPage.xaml.cs

publicsealedpartialclass MainPage : Page
    {
        public ObservableCollection<MenuItem> List { get; set; }

        public MainPage()
        {
            this.InitializeComponent();

            this.List = new ObservableCollection<MenuItem>()
            {
                new MenuItem("Sample1", "Images/1.JPG"),
                new MenuItem("Sample2", "Images/2.JPG"),
                new MenuItem("Sample3", "Images/3.JPG"),
                new MenuItem("Sample4", "Images/4.JPG"),
                new MenuItem("Sample5", "Images/5.JPG"),
                new MenuItem("Sample6", "Images/6.JPG"),
            };
        }
    }

    publicclass MenuItem
    {
        publicstring Title { get; set; }
        publicstring ImagePath { get; set; }

        public MenuItem(string title, string path)
        {
            this.Title = title;
            this.ImagePath = path;
        }
    }

これは便利!!

しかも、画面幅が小さくなった場合には、こんな風にMaster/Detailのそれぞれを同時に表示するのをやめて、リストの各要素を選択したらDetailにページ遷移するような動作に切り替わります。

リスト表示詳細表示
f:id:minami_SC:20170228235601p:plain:w250f:id:minami_SC:20170228235609p:plain:w250

各種プロパティなど

MasterDetailViewの各種プロパティはこんな感じ

プロパティ名内容
ItemsSourceobjectListBoxなどと同じように、表示する要素のリストをここで設定します
SelectedItemobject選択された要素を取得または設定します
ItemTemplateDataTemplateMaster側の領域のリスト表示用のテンプレートを設定します
DetailsTemplateDataTemplate選択された要素を表示する領域のテンプレートを設定します
NoSelectionContentobject選択された要素が何もないときの表示内容を設定します
NoSelectionContentTemplateDataTemplate↑の表示時のテンプレートを設定します
MasterPaneWidthdoubleMaster側の領域の幅を設定
MasterPaneBackgroundBrushMaster側の領域の背景色を設定
MasterHeaderobjectMaster側領域のヘッダーを設定
MasterHeaderTemplateDataTemplate↑のテンプレートを設定

WPFでもこんなの用意しておくと、色々と便利に使えそうですよね。 こんどWPF用にこのコントロールを再現してみようかな。。

Loading

これも結構使いそう。

http://docs.uwpcommunitytoolkit.com/en/master/controls/Loading/

こんな風に、ローディング時のオーバーレイ表示をするコントロールです。
f:id:minami_SC:20170228235753p:plain

IsLoadingプロパティで、ローディング表示のオン/オフを切り替えます。

以下のサンプルでは、ボタンを押したらイベントハンドラ内でLoadingコントロールのIsLoadingプロパティを変更して3秒間だけローディング表示をおこなっています。 MainPage.xaml

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><Button Margin="10"HorizontalAlignment="Left"VerticalAlignment="Top"Click="Button_Click"Content="Button" /><controls:Loading x:Name="LoadingControl"HorizontalContentAlignment="Center"VerticalContentAlignment="Center"d:IsHidden="True"Background="CadetBlue"Opacity="0.75"><TextBlock Text="Loading..." /></controls:Loading></Grid>

MainPage.xaml.cs

private async void Button_Click(object sender, RoutedEventArgs e)
        {
            this.LoadingControl.IsLoading = true;
            await Task.Delay(3000);
            this.LoadingControl.IsLoading = false;
        }

MarkdownTextBlock

http://docs.uwpcommunitytoolkit.com/en/master/controls/MarkdownTextBlock/

マークダウン形式のテキストデータを、整形して表示してくれるコントロールです。
これを使えば、簡単にMarkdown用のエディタを作ったりできそう。

Textプロパティに、Markdown形式の文字列を渡すだけで使えるので、↓みたいにTextBoxのプロパティとバインドするだけで、Markdownのプレビュー表示ができます。これは楽しい!!
f:id:minami_SC:20170228235630p:plain

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><Grid.ColumnDefinitions><ColumnDefinition /><ColumnDefinition /></Grid.ColumnDefinitions><TextBox x:Name="txtMarkdownSource"Margin="5"AcceptsReturn="True" /><ScrollViewer Grid.Column="1"Margin="5"BorderBrush="{ThemeResource AppBarBorderThemeBrush}"BorderThickness="2"HorizontalScrollBarVisibility="Disabled"VerticalScrollBarVisibility="Auto"><controls:MarkdownTextBlock Margin="6"Foreground="Black"Text="{Binding Text, ElementName=txtMarkdownSource}" /></ScrollViewer></Grid>

ちなみにV1.3だと、テーブル記法がうまく表示されなくなっているようです。
ですが、以下のようなプルリクが出ていたので、そのうち修正されるかな、、と思います。
https://github.com/Microsoft/UWPCommunityToolkit/pull/989

Expander

http://docs.uwpcommunitytoolkit.com/en/master/controls/Expander/WPFのExpanderと同じようなコントロールです。

IsExpandedプロパティで、子要素を表示したり折りたたんだりできます。

<controls:Expander Margin="5"HorizontalContentAlignment="Stretch"Header="ヘッダー"IsExpanded="True"><Grid Height="250"Background="LightCyan"><TextBlock HorizontalAlignment="Center"VerticalAlignment="Center"Style="{StaticResource HeaderTextBlockStyle}"Text="TextBlock"TextWrapping="Wrap" /></Grid></controls:Expander>

f:id:minami_SC:20170228235810p:plain

プロパティ名内容
IsExpandedbool子要素を表示するか、折りたたむかを設定
Headerstringヘッダーの文字列を設定
HeaderTemplateDataTemplateHeader要素のテンプレートを設定するためのプロパティ

WrapPanel

これも、WPFの同名コントロールを再現したもの。

というか、ほぼまんまの雰囲気なので説明は省略します。

SurfaceDialTextboxHelper

http://docs.uwpcommunitytoolkit.com/en/master/controls/SurfaceDialTextboxHelper/

TextBoxをSurface Dial対応にするためのヘルパーも用意されています。

実物持ってなくて試せないので、これも省略。

Surface Dial欲しいなぁ・・・

Firefox52リリース!! CSS Grid Layoutへの対応キター!!

$
0
0

以前、↓の記事で書いたCSS Grid Layout Module Level1にFirefoxが対応しました!!

ベンダープレフィックスも、試験的な機能のフラグも特に必要なく、デフォルトの状態で使えるようになっています。

デフォルトでこの機能が有効になったのは、主要ブラウザの中では、Firefoxが一番乗りですかね。
この機能の実装はChromeが一番進んでると思ってたので、Chromeが真っ先に対応すると思ってました。。。

Chromeの方も次のリリースのChrome57で対応予定ですので、近いうちに使えるようになります。

サンプル

とりあえず、よくあるHoryGrail的なレイアウトをFirefoxで試してみました。
f:id:minami_SC:20170308082117p:plain

<!DOCTYPE html><html><head><metacharset="UTF-8"><title>grid-template-areas sample</title><styletype="text/css">*{box-sizing: border-box;
    }html,body{width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }body{font-family: 'Lucida Grande','Hiragino Kaku Gothic ProN', Meiryo,sans-serif;
    }.wrapper{display: grid;
      width: 100%;
      height: 100%;
      grid-template-columns: 150px3fr 1fr;
      grid-template-rows: auto1fr 1fr auto;
      grid-template-areas: 
          "header header header""left  content right""left  content right""footer footer footer";
      padding: 10px;
    }.box{background-color: #444;
      color: #fff;
      border-radius: 5px;
      border: 2pxsolidblack;
      padding: 20px;
    }.header{grid-area: header;
    }.left{grid-area: left;
    }.content{overflow: auto;
      grid-area: content;
    }.right{grid-area: right;
    }.footer{grid-area: footer;
    }</style></head><body><divclass="wrapper"><divclass="box header">header</div><divclass="box left">left</div><divclass="box content">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Obcaecati debitis nemo excepturi voluptatibus magnam fugit officia, ut, dicta, animi inventore quidem aliquam adipisci vitae perspiciatis id autem maiores asperiores dolores!</div><divclass="box right">right</div><divclass="box footer">footer</div></div></body></html>

EdgeやSafariはまだ未対応だったり、IEはそもそも対応しない、、てことを考えると今すぐ使えるものではないかもしれません。。。

ですが、複雑なレイアウトを作るうえでは、flexboxとかよりも断然便利なので、 はやくこれが一般的に使えるようになるといいなぁ、、、、と思います。

Visual Studio 2017をインストールしてみました


Visual Studio 2017のXAMLエディタ

$
0
0

VS2017をインストールしたので、ざっと色々な機能を試してみました。

IDEの起動も速くなってるし、便利な機能も色々と追加されてて、なかなかイイ感じです。

XAMLエディタの新機能は以下のページにまとめられています。
Visual Studio 2017 リリース ノート

XAMLのエディット・コンティニュー

XAMLでもエディット・コンティニューに対応しました。

F5キーでデバッグ実行中にXAMLコードを編集すると、 その変更内容が即座に実行中のアプリにに適用されます。

XAMLを編集するたびに実行しなおす手間が省けるので、かなり便利。
そこそこ規模の大きいアプリだと、画面遷移などで目的ページ開くまでの手順を省略できるので、特に便利かも。

XAMLエディタのインテリセンス

項目のフィルタリング

XAMLのコード補間で、一致する項目だけがフィルタリング表示されるようになりました。
以前のと比べてみると、フィルタリングされることで、目的の項目を探しやすくなってるのがわかるかと思います。

VS2015の表示VS2017の表示
f:id:minami_SC:20170312133521p:plain:w250f:id:minami_SC:20170312133532p:plain:w250

x:Bindでのインテリセンス

x:Bindでバインディング先のパスを書いてるときも、インテリセンスでちゃんと候補が表示されるようになりました。
x:Bind便利なんだけど、コード補間が効かないのが地味に辛かったですが、だいぶ改善されました。

XAMLリファクタリング

名前空間の補間やリファクタリングができるようになりました。
C#コードと似た感じの使い勝手です。

名前空間の補間

C#でのusing補間と同じような感じです。
Ctrl+.名前空間プレフィックス定義を自動で追加してくれます。
f:id:minami_SC:20170312133602p:plain

名前空間プレフィックスの名前変更

Ctrl+R, Ctrl+Rで名前変更できます。
f:id:minami_SC:20170312133613p:plain

名前空間の削除と並べ替え

Ctrl+R, Ctrl+Gで使っていない名前空間プレフィックスを削除して、残りの項目をソートできます。

あと、↓のように使っていない項目は、エディタ上で薄く色分けして表示してくれるのも地味にうれしい改善点ですね。
f:id:minami_SC:20170312133623p:plain

Chrome57リリース!! ChromeでもCSS Grid Layoutがサポートされました。

$
0
0

先日のFirefox52に続き、ChromeでもCSS Grid Layoutへの対応が行われました!!

sourcechord.hatenablog.com

Chromeでも↓こんな風に拡張フラグなどの設定なしで利用できるようになっています。
f:id:minami_SC:20170315003245p:plain:w350

<!DOCTYPE html><html><head><metacharset="UTF-8"><title>Holy Grail Layout</title><styletype="text/css">*{box-sizing: border-box;
    }.wrapper{display: grid;
      width: 100%;
      height: 100%;
      grid-template-columns: 150px3fr 1fr;
      grid-template-rows: auto1fr auto;
      padding: 10px;
    }.box{background-color: #444;
      color: #fff;
      border-radius: 5px;
      border: 2pxsolidblack;
      padding: 20px;
    }.header{/*↓grid-areaで書く場合はこんな感じ*//*grid-area: 1/1/2/4;*/grid-row: 1;
      grid-column: 1 / span 3;
    }.left{/*grid-area: 2/1/3/2;*/grid-row: 2;
      grid-column: 1;
    }.content{overflow: auto;
      /*grid-area: 2/2/3/3;*/grid-row: 2;
      grid-column: 2;
    }.right{/*grid-area: 2/3/3/4;*/grid-row: 2;
      grid-column: 3;
    }.footer{/*grid-area: 3/1/4/4;*/grid-row: 3;
      grid-column: 1 / span 3;
    }</style></head><body><divclass="wrapper"><divclass="box header">header</div><divclass="box left">left</div><divclass="box content">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Obcaecati debitis nemo excepturi voluptatibus magnam fugit officia, ut, dicta, animi inventore quidem aliquam adipisci vitae perspiciatis id autem maiores asperiores dolores!</div><divclass="box right">right</div><divclass="box footer">footer</div></div></body></html>

Chromeでの対応が行われたので、Electronも近いうちのアップデートで、このCSS Grid Layoutがデフォルトで使えるようになると思います。
Electronの方のアップデートも今から楽しみですね!!

Windows10 Creators Updateに更新しました

$
0
0

先日から、更新アシスタント経由でアップデートできるようになってたので、さっそく更新してみました。

設定⇒「更新とセキュリティ」を開き、Windows Updateのタブを見るとこんな項目が出ています。
f:id:minami_SC:20170411001838p:plain

リンクをクリックしてみると、以下のページが開きます。
ここから更新アシスタントをダウンロードして実行すればOK!!
https://www.microsoft.com/ja-jp/software-download/windows10

あとはツール画面の指示に従うだけです。
f:id:minami_SC:20170411001848p:plain

自分の環境では、だいたい1時間弱で更新が完了しました。

Edgeでタブのサムネイル表示ができるようになったのが結構イイ感じです。

ただ、自分の環境ではなぜか、アップデートしたらフォトアプリだけが英語表示になってしまいました。。。。
f:id:minami_SC:20170411001856p:plain

他のアプリは大丈夫なのに、、、なぜだ・・・orz

そのうちのアップデートで直ることを祈って気長に待とうと思います。

MADOSMA Q601買いました!!

$
0
0

ここのところ、Windows 10 Mobile界隈、かなり雲行きが怪しいですね。。。
そんなこともあってか、MADOSMA Q601がだいぶ値下がりしてたので買ってみました。

f:id:minami_SC:20170418221009j:plain:w350

ただ、最近忙しくて、あまり使い込めずにいたのですが、
さっきamazon見てみたら、さらに値下がりしてました。。。。なんとまぁ。。。

自分が買った時は25000円くらいだったけど、19800円まで値下がりしてます。
Amazon CAPTCHA

このスペックの端末としては、めっちゃコスパ良いのでは、と思います。

感想

わかっちゃいたけど、だいぶでかいです。。 iPhone7Plusくらいの大きさです。ジーパンのポッケとかに入れると、ちょっと違和感を感じるサイズ。
まぁ、6インチのファブレットって言われるようなラインの製品ですからね。

自分はいまだにガラケーメイン使いなので、コイツはサブ機として普段はカバンに入れて持ち歩いてます。

あと、Q501は液晶フィルム貼付済みの状態で届いたのですが、Q601はフィルムが入ってて、自分で貼るようになってました。
フィルム貼るような神経使う作業は苦手なんで、、、出荷時にフィルム貼ってあるQ501の配慮はすごくありがたかったな、、と思います。

Continuum

Q601は無線接続のContinuumに対応してるんで試してみました。
Win10 Anniverssary UpdateからはWindowsPC自体をMiracastデバイスとしてcontinuumの表示先デバイスにして使えます。

あちこちで言われてますが、そこにPCがあるのにわざわざContinuum接続してモバイル環境のアプリ使わなくても、、って気はします。。

何に使うのか、、と言われると微妙なのですが、やってみると結構楽しい。
f:id:minami_SC:20170418221021p:plain:w350

PCのキーボードやマウスを使って、Continuumで動作させているモバイル側のアプリを操作できます。
この辺は、もうちょいアプリが増えてくれば化けるのかなぁ。。。

しばらくは日本での新機種発売が無さそうな空気が漂ってるので、当面の間はコイツで遊ぼうかと思います。

UWP Community Toolkit 1.4.1を使ってみた

$
0
0

UWP Community ToolkitのV1.4.1がリリースされてました。
https://github.com/Microsoft/UWPCommunityToolkit/releases/tag/1.4.0
https://github.com/Microsoft/UWPCommunityToolkit/releases/tag/1.4.1

ということで今回も、個人的にいいなと思った点をまとめておきます。

新規コントロール

Carousel

一昔前によく見かけた感じの、カルーセル表示をするコントロールが追加されてました。

ItemsControl派生のコントロールとなっているので、ItemsSourceプロパティに表示したい要素を設定すれば、
こんな風に表示することができます。

f:id:minami_SC:20170421234658p:plain

MainPage.xaml

<controls:Carousel ItemDepth="200"ItemsSource="{x:Bind ImageList}"ItemRotationY="45"ItemMargin="-30"><controls:Carousel.ItemTemplate><DataTemplate><Image Width="200"Height="200"Source="{Binding}"Stretch="Uniform" /></DataTemplate></controls:Carousel.ItemTemplate></controls:Carousel>

MainPage.xaml.cs

publicsealedpartialclass MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            this.ImageList = new List<string>()
            {
                "Images/1.JPG",
                "Images/2.JPG",
                "Images/3.JPG",
                "Images/4.JPG",
                "Images/5.JPG",
                "Images/6.JPG",
            };
        }

        public IList<string> ImageList { get; set; }
    }
プロパティなど
プロパティ名内容
ItemsSourceobjectルーセルに表示する要素を設定
ItemTemplateDataTemplate各要素を表示するためのテンプレートを設定
OrientationOrientation要素が並ぶ向きを設定
ItemDepthint選択状態の要素と、それ以外の要素の距離を設定。これを大きな値にすると、非選択状態の要素がより奥に引っ込む感じになります。
ItemMarginint各要素同士の感覚を設定します
ItemRotationX
ItemRotationY
ItemRotationZ
int非選択状態の要素を、それぞれの軸方向にどれだけ傾けるかを設定します。
InvertPositiveboolこのプロパティをfalseにすると、非選択状態の要素がすべて同じ方向に傾くようになります。デフォルトのtrueの状態だと、左右で反転して傾くようになります。
EasingFunctionEasingFunctionBase選択項目が切り替わる際のアニメーションのイージングを設定
注意事項

↓を見たところ、ItemDepthプロパティを明示的にセットしておかないと、例外を吐いてアプリが落ちてしまいます。
https://github.com/Microsoft/UWPCommunityToolkit/issues/1090

そのうち修正されるとは思いますが、しばらくの間はこのプロパティには何らかの値を必ずセットして使うようにしましょう。

Developer Tools

デバッグ用途で役に立ちそうなコントロールが2つ追加されています。

  • FocusTracker
  • AlignmentGrid

これらのコントロールを使う場合は、NugetからMicrosoft.Toolkit.Uwp.DeveloperToolsをインストールしておきましょう。

FocusTracker

こちらは、現在フォーカスを持っている要素の情報を表示してくれるコントロールです。

f:id:minami_SC:20170421234718p:plain

こんな風に、フォーカスが当たっているコントロールの名前などの情報が表示されます。
使いどころがあまりわからないですが・・・

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><Grid.ColumnDefinitions><ColumnDefinition /><ColumnDefinition Width="300" /></Grid.ColumnDefinitions><developerTools:FocusTracker Grid.Column="1"IsActive="True" /><Button x:Name="button1"Margin="10,10,0,0"HorizontalAlignment="Left"VerticalAlignment="Top"Content="Button" /><TextBox Margin="10,47,0,0"HorizontalAlignment="Left"VerticalAlignment="Top"Text="TextBox"TextWrapping="Wrap" /><RadioButton Margin="10,78,0,0"HorizontalAlignment="Left"VerticalAlignment="Top"Content="RadioButton" /></Grid>
AlignmentGrid

こんな風に、一定間隔のグリッドが表示されます。
レイアウトが乱れていないかチェックするのに役立つかと思います。

f:id:minami_SC:20170421234726p:plain

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><developerTools:AlignmentGrid HorizontalStep="20"LineBrush="Black"Opacity="0.2"VerticalStep="20" /><Button Margin="20,20,0,0"HorizontalAlignment="Left"VerticalAlignment="Top"Content="Button" /><TextBlock Margin="40,80,0,0"HorizontalAlignment="Left"VerticalAlignment="Top"Text="TextBlock"TextWrapping="Wrap" /></Grid>
プロパティ名内容
Opacitydoubleコントロール全体の不透明度
LineBrushBrushグリッド線描画に利用するブラシを設定
HorizontalStepdouble水平方向のグリッド間隔
VerticalStepdouble垂直方向のグリッド間隔

既存コントロールの改善点

MarkdownTextBlock

マークダウンの各種構文への対応改善

V1.3では、テーブル構文が正しく表示されない、、、など色々とマークダウンの表示に問題がありましたが、色々と修正されています。

テーブル表示だけでなく、GitHubのマークダウンのように```で括ってコードブロックを定義できるようになったりしてます。

ただし、言語を指定してのシンタックスハイライトなどは対応してないみたい。

f:id:minami_SC:20170421234734p:plain

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><Grid.ColumnDefinitions><ColumnDefinition /><ColumnDefinition /></Grid.ColumnDefinitions><TextBox x:Name="txtMarkdownSource"Margin="5"AcceptsReturn="True" /><ScrollViewer Grid.Column="1"Margin="5"BorderBrush="{ThemeResource AppBarBorderThemeBrush}"BorderThickness="2"HorizontalScrollBarVisibility="Disabled"VerticalScrollBarVisibility="Auto"><controls:MarkdownTextBlock Margin="6"Foreground="Black"Text="{Binding Text, ElementName=txtMarkdownSource}" /></ScrollViewer></Grid>

MasterDetailView

MasterとDetailそれぞれに別々のCommandBarを付けられるようになりました。

f:id:minami_SC:20170421234746p:plain

プロパティ名内容
MasterCommandBarCommandBarMaster側のCommandBarを設定
DetailsCommandBarCommandBarDetail側のCommandBarを設定

MainPage.xaml

<Page.Resources><DataTemplate x:Key="ListTemplate"x:DataType="local:MenuItem"><StackPanel Margin="0,8"><TextBlock Style="{ThemeResource SubtitleTextBlockStyle}"Text="{x:Bind Title}" /><TextBlock Text="{x:Bind ImagePath}" /></StackPanel></DataTemplate><DataTemplate x:Key="DetailsTemplate"x:DataType="local:MenuItem"><Grid><Grid.RowDefinitions><RowDefinition Height="Auto" /><RowDefinition /></Grid.RowDefinitions><RelativePanel Margin="5"><Ellipse x:Name="FromEllipse"Width="50"Height="50"Fill="Gray" /><TextBlock Margin="12,-6,0,0"RelativePanel.RightOf="FromEllipse"Style="{ThemeResource SubtitleTextBlockStyle}"Text="{x:Bind Title}" /><TextBlock RelativePanel.Below="FromEllipse"Text="{x:Bind ImagePath}" /></RelativePanel><Image Grid.Row="1"Margin="5"Source="{x:Bind ImagePath}"Stretch="Uniform" /></Grid></DataTemplate></Page.Resources><Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><controls:MasterDetailsView ItemsSource="{x:Bind List}"ItemTemplate="{StaticResource ListTemplate}"DetailsTemplate="{StaticResource DetailsTemplate}"MasterHeader="ヘッダー"MasterPaneWidth="200"NoSelectionContent="要素が選択されていません"><controls:MasterDetailsView.MasterCommandBar><CommandBar ClosedDisplayMode="Minimal"><CommandBar.SecondaryCommands><AppBarButton Label="Secondary1"Icon="Edit"/><AppBarButton Label="Secondary2"Icon="Edit"/><AppBarToggleButton Label="Secondary3"Icon="Edit"/></CommandBar.SecondaryCommands><!--ここに定義したものはPrimaryCommandsプロパティになる--><AppBarButton Label="Primary1"Icon="Font"/><AppBarToggleButton Label="Primary2"Icon="Edit"/></CommandBar></controls:MasterDetailsView.MasterCommandBar></controls:MasterDetailsView></Grid>

MainPage.xaml.cs

publicsealedpartialclass MainPage : Page
    {
        public ObservableCollection<MenuItem> List { get; set; }

        public MainPage()
        {
            this.InitializeComponent();

            this.List = new ObservableCollection<MenuItem>()
            {
                new MenuItem("Sample1", "Images/1.JPG"),
                new MenuItem("Sample2", "Images/2.JPG"),
                new MenuItem("Sample3", "Images/3.JPG"),
                new MenuItem("Sample4", "Images/4.JPG"),
                new MenuItem("Sample5", "Images/5.JPG"),
                new MenuItem("Sample6", "Images/6.JPG"),
            };
        }
    }

    publicclass MenuItem
    {
        publicstring Title { get; set; }
        publicstring ImagePath { get; set; }

        public MenuItem(string title, string path)
        {
            this.Title = title;
            this.ImagePath = path;
        }
    }

VS2017でOpenGLとGLUTを使う手順

$
0
0

OpenGLを使って、簡単にクロスプラットフォームなコードを書くための、GLUTというライブラリがあります。
ちょっと必要に迫られて、久しぶりにGLUTを使う機会があったので、VS2017でGLUTを使うまでの手順をざっとまとめておこうと思います。

GLUT使うのは、5~6年ぶりくらいかな。。。
単純なグラデーションを描いたりしただけですが、とても懐かしい気持ちになりましたw

freeglut

glutはもう随分と長いこと更新されなくなっているので、現在はfreeglutという互換ライブラリが使われることが多いようです。
知らんかった。。。

なので、ここではfreeglutを使ってサンプルコードを書いています。

Nugetパッケージ

このご時世に、必要なdll一式などをWebサイトから自分でダウンロードしてきて用意するのはちょっと面倒ですよね。
ライブラリはパッケージマネージャ経由で管理したいものです。

Nugetで利用できるパッケージがないか探してみると、以下のようなものがありました。 * NupenGL * freeglut

NupenGLがいい感じ!!
NupenGLは、freeglut/GLFW/GLEWの3つのライブラリをまとめてインストールしてくれます。

Nugetパッケージにfreeglutという項目もありますが、
こちらはVS2010/VS2012向けのビルドとなっていて、それ以降の環境ではlibファイルのリンクでエラーになってしまいます。

一応VS2012のコンパイラがあれば、プロジェクトのプラットフォーム・ツールセットをVS2012のものに変更することでVS2017からも利用できます。
ですが、わざわざそんなことするより、NupenGLの方を使った方が何かとトラブルが少ないかと思います。

準備

WindowsSDKの準備

OpenGLのヘッダーファイル類は、WindowsSDKに含まれています。

そのため、VS2017インストール時にWindowsSDKも一緒にインストールしておく必要があります。
(未インストールの場合は、VS2017のインストーラを起動し、以下の項目にチェックを入れて追加インストールしておきましょう)
f:id:minami_SC:20170423115452p:plain

プロジェクトの作成

新規プロジェクトでWin32コンソールアプリの空のプロジェクトを作成します。

f:id:minami_SC:20170423115502p:plain
f:id:minami_SC:20170423115507p:plain

Nugetでパッケージのインストール

Nugetパッケージの管理から、「nupengl.core」という項目をインストールします。

f:id:minami_SC:20170423115530p:plain
f:id:minami_SC:20170423115542p:plain

サンプル

以上で準備ができたので、サンプルコードを動かしてみます。

グラデーションのかかった四角形を書くだけの単純なコードです。

f:id:minami_SC:20170423115652p:plain

#include <windows.h>#include <GL/gl.h>#include <GL/glut.h>void disp(void) {
    glClear(GL_COLOR_BUFFER_BIT);
    glBegin(GL_POLYGON);
    glColor3d(1.0, 0.0, 0.0);
    glVertex2d(-0.9, -0.9);
    glColor3d(0.0, 1.0, 0.0);
    glVertex2d(0.9, -0.9);
    glColor3d(0.0, 0.0, 1.0);
    glVertex2d(0.9, 0.9);
    glColor3d(1.0, 1.0, 0.0);
    glVertex2d(-0.9, 0.9);
    glEnd();
    glFlush();
}

int main(int argc, char ** argv) {
    glutInit(&argc, argv);
    glutInitWindowPosition(100, 50);
    glutInitWindowSize(300, 300);
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

    glutCreateWindow("Hello GLUT!!");
    glutDisplayFunc(disp);
    glutMainLoop();
    return0;
}

MADOSMA Q501をinsider previewにしてCreators Updateにしてみました

$
0
0

先月のMSからのアナウンスで、残念ながら初代MADOSMA Q501はCreators Update対象外となってしまいました。

ですが、insider previewであれば、非サポートですが自己責任でCreators Updateに更新できるとのことでした。
てことで、insider previewをスローリングにして待ってましたが、いつまで待っても更新が来ない。。。

ちょっと調べてみると、どうやらQ501のファームバージョンが1.0.0.16などの古いファームだと、insider previewは降ってこないようです。
ということで、今まで面倒だと思ってやってなかったファームの手動更新をして、Creators Updateに更新してみました。

Q501をCreators Updateにする手順

ファームウェアの手動更新

↓のページでシリアルNo.の欄にIMEIを入力すると、ファームの更新方法などの案内があります。
https://www2.mouse-jp.co.jp/ssl/user_support2/sc_download.asp
この手順にある程度沿ってやってみました。

案内されたページでは、Windows 10 ADKというツールでのファーム更新方法が案内されています。

自分はとりあえず、以下のページから「WindowsADK for Windows 10 Version 1703」という最新バージョンのツールをインストールしてみました。
https://developer.microsoft.com/ja-jp/windows/hardware/windows-assessment-deployment-kit

しかし、このバージョンの「Windowsイメージングおよび構成デザイナー」では、 マウスのサポートページで案内されていた、「展開」メニューが表示されずファームの書き込みができません。。。

ということで、サポートページで案内されていた通りのWindows 10 ADK(1511)をインストールしてみたところ、ちゃんとファームの書き込みまで行えました。

手動更新は結構面倒ですね。。。

ファームウェアの手動更新完了後

手動でファーム書き換えてみたら、OSが10.0.10586.164になってました。

「システムの更新」からOSのアップデートをかけると、Anniversary Updateの状態まで更新できます。
そこまで更新してから、Windows Insider Programを有効にして「スロー」リングに設定すると、システムの更新でCreatorsUpdateを取得できます。

何度も更新かかるので、かなり時間がかかりますが、無事CreatorsUpdateまで更新できました。
これでQ501ももうしばらく活用できそうです。


UWPでのファイル・ストレージ操作

$
0
0

今まで、ちゃんとしたUWPアプリ作ってなかったので、ファイル操作などはほとんど扱ってませんでした。。。
ですが、本格的なアプリを作ろうと思うと、この手のファイル操作はが必要になる場面は多いですよね。

ということで、ファイル・ストレージ操作でよく使いそうなものをメモしておこうと思います。

UWPではセキュリティ確保などの観点から、ストレージなどへのアクセスは色々と制限されています。
そのため、UWPではユーザーのファイルにアクセスする場合は、以下のどちらかの手段を用いる必要があります。

  • 各種Pickerを通してユーザーが選択したファイル/フォルダにアクセスする
  • KnownFoldersとして定義されている場所へのアクセス(ピクチャライブラリなど)

また、通常のファイル/フォルダとは別に、アプリ専用のストレージなども用意されています。

  • ローカルのアプリ用ストレージ
  • ローミングのアプリ用ストレージ(Microsoftアカウントなどに紐づけて、複数端末でデータの同期ができるもの)

ファイル/フォルダ選択ダイアログ

ファイルやフォルダを選択するダイアログを開くには、FilePicker/FolderPickerなどといったピッカーというものを使用します。

ファイルを開くダイアログ

private async void Button_Click(object sender, RoutedEventArgs e)
        {
            var filePicker = new Windows.Storage.Pickers.FileOpenPicker();

            filePicker.FileTypeFilter.Add(".jpg");
            filePicker.FileTypeFilter.Add(".bmp");
            filePicker.FileTypeFilter.Add("*");

            // 単一ファイルの選択
            var file = await filePicker.PickSingleFileAsync();
            if (file != null)
            {
                var dlg = new MessageDialog(file.Name);
                await dlg.ShowAsync();
            }

            // 複数選択
            var files = await filePicker.PickMultipleFilesAsync();
            var result = string.Empty;
            foreach (var f in files)
            {
                result += f.Name + System.Environment.NewLine;
            }

            if (!string.IsNullOrEmpty(result))
            {
                var dlg = new MessageDialog(result);
                await dlg.ShowAsync();
            }
        }

f:id:minami_SC:20170506132910p:plain

ちなみに、Mobileの方で実行するとこんな感じ。
f:id:minami_SC:20170506133541p:plain
(エミュレータで実行したので、英語表示ですが・・・)

フォルダを開くダイアログ

ファイルと同じような感じです。
FolderPickerというクラスを使うと、フォルダを開くダイアログが表示できます。

private async void Button_Click(object sender, RoutedEventArgs e)
        {
            var folderPicker = new Windows.Storage.Pickers.FolderPicker();
            folderPicker.FileTypeFilter.Add("*");

            // フォルダの選択
            var folder = await folderPicker.PickSingleFolderAsync();
            if (folder != null)
            {
                var dlg = new MessageDialog(folder.Name);
                await dlg.ShowAsync();
            }
        }

f:id:minami_SC:20170506132925p:plain

ファイルの保存ダイアログ

private async void Button_Click(object sender, RoutedEventArgs e)
        {
            var filePicker = new Windows.Storage.Pickers.FileSavePicker();
            filePicker.FileTypeChoices.Add("テキストファイル", newstring[] { ".txt" });
            filePicker.SuggestedFileName = "新規テキスト";

            // 単一ファイルの選択
            var file = await filePicker.PickSaveFileAsync();
            if (file != null)
            {
                var dlg = new MessageDialog(file.Name);
                await dlg.ShowAsync();
            }
        }

f:id:minami_SC:20170506133008p:plain

ファイルの読み書き

今度は、実際にファイルの内容を読み込んだり、ファイルにデータを書き込んだりしてみます。

FilePickerなどを用いると、StorageFile形式のオブジェクトが取得できます。
ファイルの読み書きは、このStorageFileオブジェクトに対して行います。

ファイルへの書き込み
// 単一ファイルの選択
            var file = await filePicker.PickSaveFileAsync();
            if (file != null)
            {
                await Windows.Storage.FileIO.WriteTextAsync(file, "Hello World!!");
            }
ファイルからの読み込み
            var file = await filePicker.PickSingleFileAsync();
            if (file != null)
            {
                var text = await Windows.Storage.FileIO.ReadTextAsync(file);
                var dlg = new MessageDialog(text);
                await dlg.ShowAsync();
            }
ファイル書き込みの結果チェック

CachedFileManager.CompleteUpdatesAsyncメソッドで、ファイル操作結果の状態を確認できます。
このメソッドの戻り値をチェックすることで、ファイル書き込みが正常にできたかを確認できます。

            var file = await filePicker.PickSaveFileAsync();
            if (file != null)
            {
                await Windows.Storage.FileIO.WriteTextAsync(file, "Hello World!!");

                var result = await Windows.Storage.CachedFileManager.CompleteUpdatesAsync(file);
                if (result == Windows.Storage.Provider.FileUpdateStatus.Complete)
                {
                    var dlg = new MessageDialog("書き込み成功");
                    await dlg.ShowAsync();
                }
                else
                {
                    var dlg = new MessageDialog("書き込み失敗");
                    await dlg.ShowAsync();
                }
            }

ストレージ操作

UWPには、通常のファイル/フォルダとは別に、アプリ専用のデータ保存領域があります。

保存するデータの形式と保存場所が異なる、以下のようなストレージが用意されています。

  • 保存する形式
    • ○○Folder・・・・通常のフォルダのように、任意のファイルを保存できるストレージ
    • ○○Storage・・・Key/Valueペアの形式でデータを保存するストレージ
  • データの保存場所
    • Local〇〇・・・アプリをインストールしている端末上だけで利用できるストレージ
    • Roaming〇〇・・・ユーザーアカウントに紐づけられ、複数の端末で保存内容が同期されるストレージ

まとめると、こんな感じですね。
f:id:minami_SC:20170506133020p:plain

LocalFolder/RoamingFolder

LocalFolderと書いてる部分をRoamingFolderに変更すれば、ローミングを用いた保存内容の同期ができます。

書き込み
private async void Button_Click(object sender, RoutedEventArgs e)
        {
            // LocalFolderへの書き込み
            var storage = Windows.Storage.ApplicationData.Current.LocalFolder;
            var file = await storage.CreateFileAsync("sample.txt", Windows.Storage.CreationCollisionOption.ReplaceExisting);
            await Windows.Storage.FileIO.WriteTextAsync(file, "Hello World!!");
        }
読み込み
private async void Button_Click(object sender, RoutedEventArgs e)
        {
            // LocalFolderからの読み込みtry
            {
                var storage = Windows.Storage.ApplicationData.Current.LocalFolder;
                var file = await storage.GetFileAsync("sample.txt");
                var text = await Windows.Storage.FileIO.ReadTextAsync(file);

                var dlg = new MessageDialog(text);
                await dlg.ShowAsync();

            }
            catch (Exception)
            {
                // ファイルが存在しない場合の処理
            }
        }

LocalSettings/RoamingSettings

こちらも同様に、LocalSettingsの部分をRoamingSettingsに変えると、端末間での保存内容の同期ができます。

書き込み
privatevoid Button_Click(object sender, RoutedEventArgs e)
        {
            // LocalSettingsへの書き込み
            var container = Windows.Storage.ApplicationData.Current.LocalSettings;
            container.Values["Sample"] = "Hello World!!";
        }
読み込み
private async void Button_Click(object sender, RoutedEventArgs e)
        {
            // LocalSettingsからの読み込み
            var container = Windows.Storage.ApplicationData.Current.LocalSettings;
            var data = container.Values["Sample"];
            if (data != null)
            {
                var text = (string)data;
                var dlg = new MessageDialog(text);
                await dlg.ShowAsync();
            }
        }

その他

KnownFolderへのアクセス

ピクチャライブラリや、ドキュメントなどなど、

            var storage = Windows.Storage.KnownFolders.PicturesLibrary;
            var files = await storage.GetFilesAsync();
            var folderPicker = new Windows.Storage.Pickers.FolderPicker();

            // ピクチャライブラリの一番目のファイル要素を取得
            var firstItem = files.First();
            var dlg = new MessageDialog(firstItem.Name);
            await dlg.ShowAsync();

この例のPictureLibraryにアクセスするためには、プロジェクトのプロパティのパッケージマニフェスト画面を開き、以下のように「ピクチャライブラリ」の機能にチェックを付けておく必要があります。
f:id:minami_SC:20170506133036p:plain

MostRecentlyUsedList/FutureAccessList

FilePickerやFolderPickerなどを使って、ユーザにファイル/フォルダ選択を行わせれば、UWPアプリは選択された任意のファイル/フォルダにアクセスすることができます。

しかし、一度選択した項目でも、アプリ起動のたびにユーザーが選択しなおさなければならないのでは、あまりに不便になってしまいます。

そこで、UWPでは、一度アクセスしたフォルダを記憶しておき、後でアクセスする際に各種ピッカー操作を伴わなくても利用できるようにする仕組みが用意されています。

  • MostRecentlyUsedList
    • 最近使用したファイル一覧を格納するリスト
    • 最大25項目まで記録
    • 記録数の上限に達した後は、古いものから自動的に削除される
  • FutureAccessList
    • あとでアクセスする一覧
    • 最大1000項目を記録
    • 最大の記録数を超えないように、アプリ側で管理する必要がある

どちらもだいたい同じようなアクセス方法になるので、 とりあえず、このサンプルではMostResentlyUsedListを使ってみます。

MostRecentlyUsedListへの記録
            var filePicker = new Windows.Storage.Pickers.FileOpenPicker();
            filePicker.FileTypeFilter.Add(".txt");

            // 単一ファイルの選択
            var file = await filePicker.PickSingleFileAsync();
            if (file != null)
            {
                Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList.Add(file);
            }
MostRecentlyUsedListに記録された項目へのアクセス
            var entries = Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList.Entries;
            var firstItem = entries.First();
            var file = await Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList.GetFileAsync(firstItem.Token);

            var dlg = new MessageDialog(file.Name);
            await dlg.ShowAsync();

Credential Locker

今まで扱ってきたデータの保存方法は、内容を平文で保存するので、パスワードのような機密性の高い情報を保存する用途には使用できません。

PasswordVaultというクラスを使うと、パスワードのような平文で保存すべきでないような情報を、暗号化した状態で保管できるようになります。

パスワード情報の保存
// パスワード情報の保存
            var passwordVault = new Windows.Security.Credentials.PasswordVault();
            var credential = new Windows.Security.Credentials.PasswordCredential("MyApplicationName", "username", "ここにパスワード情報などを入れる");
            passwordVault.Add(credential);
パスワード情報の取得
// パスワード情報の取得
            var passwordVault = new Windows.Security.Credentials.PasswordVault();
            try
            {
                var credential = passwordVault.Retrieve("MyApplicationName", "username");
                var dlg = new MessageDialog(credential.Password);
                await dlg.ShowAsync();
            }
            catch (Exception)
            {
                // パスワード情報が取得できなかった場合
            }
保存済みのパスワード情報の削除
// 保存済みのパスワード情報の削除
            var passwordVault = new Windows.Security.Credentials.PasswordVault();
            var credential = passwordVault.Retrieve("MyApplicationName", "username");
            passwordVault.Remove(credential);

CretatorsUpdateから、UWPでもTypeConverter的なものが使えるようになりました

$
0
0

WPFとUWPでは、同じXAMLという仕組みを用いていますが、細かい部分を見ると「アレがない」「コレがない」といった、細かい違いがあります。

そんな違いの一つとして、「UWPにはTypeConverterがない」という違いがありました。

ですが、Windows 10 Creators Updateからは、UWPでも簡単にTypeConverter的なものを作れるようになりました。
(あくまでも「的な」ものなので、TypeConverterとはちょっと実装方法が異なります。)

コレ、結構便利な変更点だと思うんだけど、全然話題になってないのでまとめてみたいと思います。

サンプルコードは以下の場所に置いておきました。 github.com

TypeConvertertって?

まずはザックリとTypeConverterとは何か、という説明です。

例えば、以下のようなXAMLコードで、

<Button Margin="5,10,5,10"Content="Button" />

文字列で書かれた5,10,5,10という値がThickness型のMarginプロパティに設定できるのは、TypeConverterにより文字列⇒Thickness型への変換が行われているためです。

コントロールなどを作っていると、独自の型をプロパティに持つことがあると思います。
そんな時に、TypeConverterを作っておくと、独自型のプロパティも文字列で手短に定義できるようになるので、とても便利です。

WPFでのTypeConverter実装方法

WPFでは、以下のようにTypeConverter派生クラスを作成し、その派生クラスをTypeConverter属性として目的のクラスに付与しておくと、こうやってXAML上から文字列で独自型のプロパティを設定できるようになります。

f:id:minami_SC:20170510004528p:plain
(※Person型のPersonプロパティにXAMLから文字列で設定している例)

どうでもいいですけど、英語の名前でJohn Smithっていうと、日本語で名前のサンプルとしてよく出てくる山田太郎とか田中太郎って感じのイメージになるらしいですね。

    [TypeConverter(typeof(PersonTypeConverter))]
    publicclass Person
    {
        publicstring FirstName { get; set; }
        publicstring LastName { get; set; }
    }

    publicclass PersonTypeConverter : TypeConverter
    {
        publicoverridebool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return sourceType == typeof(string);
        }

        publicoverrideobject ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, objectvalue)
        {
            var text = (string)value;
            var list = text.Split(',')
                           .Select(o => o.Trim())
                           .ToList();


            if (list.Count() != 2)
            {
                thrownew ArgumentException($"'{value}' Invalid value. Person must contains 2 items.");
            }

            returnnew Person() { FirstName = list[0], LastName = list[1] };
        }
    }

UWPにはTypeConverterがない!!

このとても便利なTypeConverterという仕組みですが、UWPにはTypeConverterが用意されていません。

そのため、独自の型をプロパティに持つコントロールとかを作ると、入り組んだマークアップが必要になるケースがたびたびありました。

Windows10 Creators Updateでの対応

そんなUWPですが、Creators Updateからは、TypeConverterに似た感じで「XAML上に定義した文字列⇒独自型のオブジェクト」に変換する仕組みが提供されました。

TypeConverterクラスがUWPで使えるようになったわけではなく、別の手段が用意されています。

UWPでの対応方法

詳細は以下の記事に詳しくまとまっています。
http://timheuer.com/blog/archive/2017/02/15/implement-type-converter-uwp-winrt-windows-10-xaml.aspx

かいつまんで説明すると、以下のような感じです。

XAML上で文字列から変換できるようにしたいクラスに対して、以下の2点の実装を行います。

  • 文字列から対象の型へと変換するstatic関数を作成する
  • 対象のクラスに、上記static関数の関数名を引数にしてCreateFromString属性を付加する

これだけで、WPFでTypeConverterを使ってやってた事ができるようになります。

サンプル

ここから、実際にユーザーコントロールを作り、UWPのTypeConverter的機能を使ってXAMLから複雑なプロパティを設定してみたいと思います。

ここでは、FirstName/LastNameプロパティを持つPersonというクラスを作ります。
そして、このPerson型のプロパティを持ち、それを整形して画面上に表示するPersonViewerというコントロールを作ってみます。

こんな感じのユーザーコントロールを作ってみます。
f:id:minami_SC:20170510004556p:plain

PersonクラスとPersonViewerの作成

まずは、こんな感じのPersonクラスを作ります。

Person.cs

publicclass Person
    {
        publicstring FirstName { get; set; }
        publicstring LastName { get; set; }
    }

続いて、このPersonクラスを画面表示するためのPersonViewerというコントロールを作ります。
xamlとコードビハインドはそれぞれ以下のような感じです。

PersonViewer.xaml

<UserControlx:Class="TypeConverterUwp.PersonViewer"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="using:TypeConverterUwp"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"mc:Ignorable="d"d:DesignHeight="300"d:DesignWidth="400"><Grid x:Name="root"Margin="5"><Grid.ColumnDefinitions><ColumnDefinition Width="Auto" /><ColumnDefinition Width="Auto" /></Grid.ColumnDefinitions><Grid.RowDefinitions><RowDefinition Height="Auto" /><RowDefinition Height="Auto" /></Grid.RowDefinitions><TextBlock Margin="5"HorizontalAlignment="Left"Text="FirstName: " /><TextBlock Grid.Column="1"Margin="5"HorizontalAlignment="Left"Text="{Binding Person.FirstName}" /><TextBlock Grid.Row="1"Margin="5"HorizontalAlignment="Left"Text="LastName: " /><TextBlock Grid.Row="1"Grid.Column="1"Margin="5"HorizontalAlignment="Left"Text="{Binding Person.LastName}" /></Grid></UserControl>

PersonViewer.xaml.cs

publicsealedpartialclass PersonViewer : UserControl
    {
        public PersonViewer()
        {
            this.InitializeComponent();
            root.DataContext = this;
        }

        public Person Person
        {
            get { return (Person)GetValue(PersonProperty); }
            set { SetValue(PersonProperty, value); }
        }
        // Using a DependencyProperty as the backing store for Person.  This enables animation, styling, binding, etc...publicstaticreadonly DependencyProperty PersonProperty =
            DependencyProperty.Register("Person", typeof(Person), typeof(PersonViewer), new PropertyMetadata(null));
    }

MainPage.xaml

で、このコントロールを使う際には、以下のようにPersonプロパティを、ちょっと入り組んだXAMLで記述する必要があります。

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><local:PersonViewer><local:PersonViewer.Person><local:Person FirstName="John"LastName="Smith"/></local:PersonViewer.Person></local:PersonViewer></Grid>

ちょっと面倒ですね。。。

XAML上から文字列で定義できるように修正

Creators Updateから使えるようになった、CreateFromString属性を使った方法でPersonクラスを以下のように修正します。

Person.cs

    [Windows.Foundation.Metadata.CreateFromString(MethodName = "TypeConverterUwp.Person.ConvertToPerson")]
    publicclass Person
    {
        publicstring FirstName { get; set; }
        publicstring LastName { get; set; }

        publicstatic Person ConvertToPerson(stringvalue)
        {
            var text = (string)value;
            var list = text.Split(',')
                           .Select(o => o.Trim())
                           .ToList();

            if (list.Count() != 2)
            {
                thrownew ArgumentException($"'{value}' Invalid value. Person must contains 2 items.");
            }

            returnnew Person() { FirstName = list[0], LastName = list[1] };
        }
    }

これで、以下のようにXAML上から文字列で定義できるようになります。

f:id:minami_SC:20170510004644p:plain

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><local:PersonViewer Person="John, Smith" /></Grid>

対象プラットフォーム

このCreateFromStringという属性自体は、Aniverssary Update(14393)で追加された属性なので、ターゲットプラットフォームを14393にしていてもビルドできます。
(XAML側のコードを書かなければ・・・)

しかし、Aniverssary Update環境で、このTypeConverter的なマークアップをしたXAMLコードを読み込むと、実行時に例外が発生します。

CreateFromString属性でTypeConverter的な用途に使う場合には、プロジェクトのターゲットの最小バージョンを、以下のように「Windows 10 Creators Update(10.0; ビルド 15063)」として置いた方がよいかと思います。

f:id:minami_SC:20170510004632p:plain

GLFWを使ってみた

$
0
0

OpenGLでちょろっとウィンドウ表示をする時などには、GLUTをよく使っていたのですが、
近年(と言っても随分前から)ではGLFWというものもよく使われているようです。
http://www.glfw.org/documentation.html

先日↓に書いた、NupenGLというパッケージをインストールすると、このGLFWも利用できるようになるので、使ってみました。

とはいっても、自分は最近あまりC++を積極的に使ってないので、 HelloWorldレベルのコードをメモ書き程度で・・・

参考リンク

↓のあたりを参考にやってみます。
http://www.glfw.org/documentation.html
http://marina.sys.wakayama-u.ac.jp/~tokoi/?date=20120906

サンプル

公式ページのドキュメントなどを参考に、ウィンドウ表示と四角形の描画をしてみました。

f:id:minami_SC:20170513180554p:plain

#include <GLFW/glfw3.h>#pragma comment(lib, "opengl32.lib")void disp(void)
{
    glClear(GL_COLOR_BUFFER_BIT);
    glBegin(GL_POLYGON);
    glColor3d(1.0, 0.0, 0.0);
    glVertex2d(-0.9, -0.9);
    glColor3d(0.0, 1.0, 0.0);
    glVertex2d(0.9, -0.9);
    glColor3d(0.0, 0.0, 1.0);
    glVertex2d(0.9, 0.9);
    glColor3d(1.0, 1.0, 0.0);
    glVertex2d(-0.9, 0.9);
    glEnd();
    glFlush();
}

int main(void)
{
    GLFWwindow *window;

    // GLFWの初期化if (!glfwInit()) { return -1; }

    // GLFWのウィンドウ生成
    window = glfwCreateWindow(640, 480, "Hello GLFW!!", NULL, NULL);
    if (!window) {
        glfwTerminate();
        return -1;
    }

    glfwMakeContextCurrent(window);

    while (!glfwWindowShouldClose(window)) {
        // 描画処理を行う関数呼び出し
        disp();

        // バッファの入れ替え
        glfwSwapBuffers(window);

        // マウスやキーボードなどのイベント取得
        glfwPollEvents();
    }

    glfwTerminate();
    return0;
}

knockout.jsでバインディング・コンテキストの値をデバッガで確認する方法

$
0
0

近年のフロントエンド界隈では、Knockout.jsはあまり話題に上らなくなっちゃいましたね。。。
ですが、自分は以前Knockout.jsで書いてたプロジェクトを弄ったりと、今でも結構使ってます。

あと、なんだかんだ言って、双方向データバインディングだけあれば十分ってケースでは、 Knockout.jsのシンプルさは今でも魅力的ですしね。

で、Knockout.jsを使ってHTML上でバインディング定義をしていると、
要素のコンテキストがどんな値になっているか、、、を確認したくなる時がちょくちょくあります。

ちなみに、Chrome用では↓こんな便利な拡張機能があります。
http://qiita.com/sasaplus1/items/8c5a2216f3cb15b792c5
https://chrome.google.com/webstore/category/extensions?hl=ja

でも、拡張機能などを使わなくても、ブラウザの開発者ツールを活用してお手軽に確認することもできます。

手順

  1. 開発者ツールを開いて、DOM Explorer的なヤツから、目的のDOM要素を選択します。
  2. 続いて、コンソールを開き、以下のコマンドを実行します。
Ko.contextFor($0)

するとこんな風に、コンテキストに設定されているオブジェクトの値を確認できます。
f:id:minami_SC:20170516004442p:plain

ここでは、Edgeでやっていますが、IE11, Chrome, Firefoxなどのブラウザでも、だいたい同じような手順で確認できます。

Xamarin.FormsにWPF>K#対応するっぽい!!

$
0
0

ここのところ、先週のBuild2017の動画を色々見て回ってたのですが、
↓のXamarin.Formsの動画の34:00~あたり。
https://channel9.msdn.com/Events/Build/2017/B8099

今後Xamarin.Formsが、WPFとかGTK#上での実行に対応するようです。

ロードマップや公式ブログを見てみると、2017 3Qあたりでの対応になるみたいなんで、意外とすぐに出てきそうな感じです。
https://blog.xamarin.com/glimpse-future-xamarin-forms-3-0/
https://forums.xamarin.com/discussion/85747/xamarin-forms-feature-roadmap/p1

今までのXamarin.Formsはモバイル環境にフォーカスしてましたが、
この辺が対応されると、Xamarin.FormsでWin/Mac/Linuxのデスクトップアプリも作れるようになります。

なんか、世間の話題はもっぱらXAML Standardの方に持ってかれてましたが、 これってビッグニュースなんじゃないかな??

モバイルからデスクトップまですべてXamarin.Formsでできるなんて、メッチャ夢が広がりますね。

そろそろ、本腰入れてXamarin.Formsも使ってみようかな、と思いました。

Viewing all 153 articles
Browse latest View live