Yaml を利用して C# でヒアドキュメントを考える

XAML + XDocument + XamlReader.Load を使うと、ヒアドキュメントっぽくコードに XAML を書くことができます。

[WinRT] XElement を使い XAML を構築して動的に XamlReader.Load で読み込む技 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/6556

な感じで、XElement を使って構築してもよいし、文字列で XAML を書いてもよい(実際 Storyboard のクローンを作るときは、そうしています)。VB であれば XML 構文を使えばよいのです。
が、ヒアドキュメントで書くにせよ、外部ファイルの読み込み(あるいはリソースからの読み込み)であっても、XML を手書きするのがちょっと面倒で、特に「閉じタグ」の問題が関わってきてしまいます。Visual Studio 付属の XML エディタを使って作ればよい(単純に拡張子を .xml にするだけ)なものの、できれば「閉じタグ」なしで書いていきたい。

XElement の羅列のほうも、閉じタグではないですが、閉じ括弧「)」が頻発します。適切なインデントを付ければ間違いは少ないのですが、そもそもが適切なインデントが付いているはずなので、閉じ括弧はいらないはずです。

Yaml には閉じタグがない

YAMLAin’t Markup Language (YAML) Version 1.2
http://www.yaml.org/spec/1.2/spec.html
C#でYAMLを使えるか試してみた | OPC Diary
http://opcdiary.net/?p=25878
NuGet Gallery | FsYaml 1.1.3
https://www.nuget.org/packages/FsYaml/
閉じタグを超えた先に僕が見た景色とは
http://www.slideshare.net/muyuu/ss-40425076

最後のものは Yaml ではありませんが、Jade という Yaml と似た構文で HTML タグを生成するフレームワークです。

Jade について。
https://gist.github.com/japboy/5402844

を見ると Haml という軽量 HTML 構文から影響を受けているそうなので、Yaml とは潮流が違うみたいですが、

  • インデントを利用して記述する
  • 閉じタグ/括弧が必要ない

ってところで同じでしょう。Yaml 自体は、SPEC で v1.0 以前から見知っていたのですが、Ruby の設定以外にあまり見たことが無くて、それならば XML でも十分ではないか?と思っていたのですよ。ですが、ExDoc を使って検索をしてみると、C# でヒアドキュメントの可能性が出てくるので、そうなると XML 自体の冗長性がちょっと問題になってくるという話です。

Yaml 自体は、YamlDotNet を使うと手軽に読み込めます。取り出しが KeyValue になっているので、今一つ取り出しが面倒ですが、「C#でYAMLを~」の記事のように既存のクラスにマッピングしてから使うのが定番でしょう。ファイル読み込みになっていますが、StringReader を使えば文字列として読み込めるので、コード内のヒアドキュメントとして利用できます。

以下は思考実験です。

Yaml を XML にマッピングする

改めて、Yaml の仕様を見直していたのですが、Yaml にはハッシュとリストしかありません。そういう意味は、XML に属性と値しかないのと似ていて非常にシンプルです。まあ、実際は SPEC を見るとえらい複雑なんですが、まだ v1.0 以前のころはもっとシンプルだったような気が。

とはいえ、適当に XML にマッピングしてみます。XML にマッピングするのは ExDoc で使いやすいのと、他のデータのコンバートしやすいからですね。おそらく JSON からのマッピングも同じになると思います。

Root:
 - person:
   name: masuda
   Age: 46

 

<root>
  <person name='Masuda' age='46' />
</root>

Yaml にはルートは必要ありませんが、仮にルートを決めます。複数並べることがある person タグの場合はリストに、重複しない場合はハッシュで書く、というルールを決めておきます。
上の Yaml から期待されるのは、こんな風に person に属性値で設定された XML ですよね、おそらく。ただし、データとしては属性値であろうと要素の値であろうとどちらでもよいので、

<root>
  <person>
    <name>masuda</name>
    <age>46</age>
  </person>
</root>

こんな感じですべてを要素にするのも可能ですよね。ExDoc の場合は、/ や * 演算子を使うか、% 演算子を使うかという違いが出てきてしまうので、出力される XML は区別されてしまいますが。

先の場合は、person 要素自体が値を持たないので、属性値で要素の値でも似た感じになるのですが、person 要素に値を持たせるようにすると、こんな感じになります。

Root:
 - person: masuda tomoaki
   ID: 100
   Age: 46

person に続くハッシュ値は属性になるというルールを作って、person の属性に設定します。

<root>
  <person ID='100' age='46'>
    Masuda tomoaki
  </person>
</root>

実は、要素だけを使って下記のように作れるのですがあまりお勧めできません。というのも、person の要素を .Value で取れるようにしているのは XElement や ExElement のシンタックスシュガーなところがあって、本来は Text 要素として取ることになります。そうすると、ID タグの後ろとか、age タグの後ろにテキストが入っている場合も探索の対象になって、あまいな部分が多くなります。これは HTML 記法では普通に出てくるものなのですが、ヒアドキュメント的に使いたい場合はこの曖昧さは避けておきたいかなと。

<root>
  <person>
    masuda tomoaki
    <ID>100</ID>
    <age>46</age>
  </person>
</root>

上記の場合、person がリストになっていますが、あえてハッシュの場合も考えてみます。

Root:
  person: masuda tomoaki
    ID: 100
    Age: 46

Root のハッシュとして person を付けます。更に person のハッシュとして ID と Age がある構造ですね。
Yaml ではハッシュの値がハッシュ、ということができるのですが、XML の場合は属性の中に要素を入れることができません。なので、Root のハッシュがさらに子要素を持つような場合には、属性から要素に昇格するという手順が必要そうです。

<root>
  <person ID='100' age='46'>
    Masuda tomoaki
  </person>
</root>

この手順がイリーガルなので Yaml をヒアドキュメントするときに躓きそうな気もするのですが。まあ、子要素は一般的にリスト「-」を使う、というローカルルールにしておけば問題ないかと思います。もともと、ここでの XML の変換自体がローカルルールですから。

Yaml の文章構造をマッピングする

実は Haml という HTML 軽量構文があるので、あまり詳しく作っても仕方がないのですが、ちょっとした RTF っぽい記述をコード内に書き下すことを想定してみます。

HTML:
 Head:
  Title: yaml page
 Body:
  H1: yaml to XML を作る
  P:
    ここに文章を書き下す。

こんな風に Yaml で書いておいてコンバートすると、XHTML(のようなもの)を吐き出します。

<html>
 <head>
  <title>yaml page</title>
 </head>
 <body>
  <h1>yaml to XML を作る</h1>
  <p>ここに文章を書き下す。</p>
 </body>
</html>

XMLの書式に則るので、p タグや br タグの扱いが普段の HTML とは異なりますが、ある程度はこれでできるはずです。このあたりは、XAML の RichTextBlock に渡す糖化構文も合わせて考えていきたいところですね。

カテゴリー: 開発, C#, EXDoc | Yaml を利用して C# でヒアドキュメントを考える はコメントを受け付けていません

[WinRT] XElement を使い XAML を構築して動的に XamlReader.Load で読み込む技

Xamarin.Forms には無い XamlReader なんですが、動的に XAML をロードすることができるので結構便利です、って話を少し(まあ、XAML のパーサーは作ったので、ロードしてやればいいだけなんですけど)。

XamlReader.Load メソッドを使うと XAML 文字列を動的に読み込むことができます。XML を作るときは実は Visual Basic が便利なんですけど、今回は C# で XElement を使って構築します。

XElement で XAML を作成する

こんな風に、チェックボックス(CheckBox)を縦に並べた画面を作ります。これはアンケートツクレールから自動生成するために動的に作っています。

XElement CheckboxToXaml(ExElement el)
{
    string qtext = ((ExElement)(el * "td" % "class" == "qtext")).Value;
    string qbody = ((ExElement)(el * "td" % "class" == "qbody")).Value;
    var lst = el * "input" % "type" == "checkbox";

    var panel = new XElement("StackPanel", new XAttribute("Grid.Row", "1"));
    var hub = new XElement("HubSection",
        new XAttribute("Width", "400"),
        new XAttribute("Header", qtext),
            new XElement("DataTemplate",
                new XElement("Grid",
                    new XElement("Grid.RowDefinitions",
                    new XElement("RowDefinition", new XAttribute("Height", "50")),
                    new XElement("RowDefinition", new XAttribute("Height", "*"))),
                    new XElement("TextBlock",
                        new XAttribute("Grid.Row", "0"),
                        new XAttribute("FontSize", "16"),
                        new XAttribute("TextWrapping", "NoWrap"),
                        new XAttribute("Text", qbody)),
                panel)));
    foreach (var it in lst)
    {
        var node = it.Parent.XElement.NextNode as XText;
        if (node != null)
        {
            string name = it.Parent % "name";
            string value = it.Parent % "value";
            string xname = name.Replace("[]","") + "-" + value;
            _names[xname] = null;

            var ee = new XElement("CheckBox",
                new XAttribute("Content", node.Value),
                new XAttribute("Name", xname));
            panel.Add(ee);
        }
    }
    return hub;
}

まあ、後から気づいたのですが、こんな風にちまちまと XAML を書くぐらいだったら適当なユーザーコントロールにしてしまったほうがいいんですよね。ListView を使って CheckBox を並べるようなユーザコントロールを作って、データバインドを使えば良いわけです。

XElement と XAttribute を使って書き下しているのですが、それぞれのコントロールを使っても同じことができます。実は、このコードは、いちど XAML で書いてから XElement に直しています。このあたり、Visual Basic の場合はヒアドキュメント風に XML を書くことができるのでメリットは大きいのですが…C# だといまいちですね。

XmlReader で文字列の XAML をロードさせる。

あらかじめデザイン上の HubSection のテンプレートを作っておいて、最初と最後のみ残して、その他はすり替えます。XamlReader.Load に渡す XML の xmlns はスキーマを設定しないといけないのですが、そうもそのままではうまくいかないので Replace で逃れています。これは、ルートの XDocument を作るときに指定すればよいでしょう。

// 最初と最後だけ残す
var sec0 = this.hub.Sections[0];
var sece = this.hub.Sections[this.hub.Sections.Count - 1];
this.hub.Sections.Clear();
this.hub.Sections.Add(sec0);

foreach (var it in hubs)
{
    it.SetAttributeValue("ns", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
    var xml = it.ToString(SaveOptions.None);
    xml = xml.Replace("ns="http", "xmlns="http");
    var sec = XamlReader.Load(xml) as HubSection;
    this.hub.Sections.Add(sec);
}
this.hub.Sections.Add(sece);

コントロールの名前を検索する

実は XAML 文字列で渡すときには問題があって、内部のコントロールに触れないことです。それぞれのコントロールをクラスで組み合わせたときは適当なオブジェクトとして保存しておけばよいのですが、XAML 文字列の場合は、XamlReader.Load の後にオブジェクトができるので、Load した後にオブジェクトを見つける必要があります。
これをどうするのか?ってのが謎なのですが、実は Name プロパティが使えます。XAML で指定するときは x:Name でオブジェクトと結び付けをするのですが、実は FramworkElement に Name プロパティがあるのです。ここに名前を付けておいて、Load 後に検索をします。

// 画面を更新して _names を作る
await Task.Delay(1);

var names = new Dictionary<string, object>();
foreach (var key in _names.Keys)
{
    var obj = FindByName(key, this);
    names[key] = obj;
}
_names = names;

いちど画面に描画してあげてから、Name を検索します。Storyboard のように描画自体がないものはどうなるのかわかりません。アニメーションを使うときには必須なので、これも調べておかないといけませんね。

FindByName メソッドは自作したものです。x:Name を検索するときの FindName メソッドと同じですね。プレロードする XAML の場合は、構築しながら x:Name に対応するオブジェクトを保存していくので高速なのですが、こっちのはコントロールのツリーを探索していくのでちょっと遅いです。とはいえ、画面に表示されるコントロール程度ならば問題ないでしょう。

object FindByName(string name, DependencyObject el , int indent = 0)
{
    string sp = "";
    for ( int i=0; i<indent; i++ ) sp += " ";
    // Debug.WriteLine(sp + el.GetType().Name);
    var fw = el as FrameworkElement;
    if (fw != null)
    {
        if (fw.Name == name)
        {
            return fw;
        }
    }
    int cnt = VisualTreeHelper.GetChildrenCount( el );
    for (int i = 0; i < cnt; i++)
    {
        var child = VisualTreeHelper.GetChild(el, i);
        var obj = FindByName(name, child, indent + 1);
        if (obj != null)
        {
            return obj;
        }
    }
    return null;
}

そんな訳で、XamlReader.Load を使って動的に XAML をロードすることができます。このぐらいならばユーザコントロールを作ったほうがデザイン的にもよいのですが、実は RichTextBlock を作るときに有効に働きます。その話はまた後日。現在制作中の「ふぁぼ付箋」アプリで使っているテクニックです。

カテゴリー: C#, WinRT | [WinRT] XElement を使い XAML を構築して動的に XamlReader.Load で読み込む技 はコメントを受け付けていません

[WinRT] HttpClient+HtmlAgilityPack+XDocument+ExDoc で HTML データから抽出する話

Web API 華やかな昨今ですが、HTML形式からデータ抽出しないといけないパターンがあります。と言いますか、ちょっと前までは Excel VBA とか DOM を使って抽出したものですが、Wordpress のバックグランドの MySQL から直接取り出したり、そもそも Web API が用意されていたりして、それほど機会は多くないと思うのですが。まあ、ブラウザから手軽に拾えるのは良いかと。

WPF だったりすると、DOM を使って検索する方が早いのですが、ストアアプリだと DOM が取れません。わざわざストアアプリにする理由もないのですが、ダウンロードが手軽なのと、タブレットで使いやすいので敢えてストアアプリを使います。

アンケートツクレール http://enq-maker.com/ 自体が Web API を提供しているのかどうか?は別として、ブラウザから直接抽出を試みます。実は、HTML 形式から抽出する場合も、取り出しやすい HTML 形式と取り出しにくい HTML 形式があります。取り出しやすいのは、name が id が付いていたり、CSS を使ってスタイルが付いている場合ですね。id や name を頼りに検索すれば一発で目的のデータが取り出せます。
取り出しにくいのは、それらの名前がついてないパターンで、前後の文字列を見たり、場合によっては行数をみたりして検索をします。行数の決め打ちはあまり良くなくてデザインが変わってしまうと行数が変わるので非常に変更に弱い作りになってしまいます。大抵の場合は、独自のスタイルが付いてることが多いので、それを目的にして、次のタグとか親タグ、子タグを調べていけばできあがります。

データ自体が JSON や XML で取れるとそのままクラス化して抽出がしやすいのですが、HTML 形式のままだとそうはいきません。試行錯誤が必要になり、また試行錯誤の作業量に見合うデータが取れるかどうかの肝になります。ブラウザの手打ち部分を、そのまま自動化するところが目的ですね。また、見た目のブラウザしか用意されていないパターンでも、プロキシ的にライブラリを噛ませれば Web API にすることもできる、というパターンになります。

データ変換を組み合わせる

HTML 形式の適当なパーサーを書こうかなと思ったところ、HtmlAgilityPack があるのを思い出しました。半年前ぐらいに知って試してみようと思ってそのままだったのですが、先のアンケートアプリで使っています。

  1. HttpCilent クラスを利用して HTML 形式で取得
  2. HtmlAgilityPack ライブラリを利用して DOM 化
  3. XDocument を使って XML 形式に変換
  4. ExDoc を使ってワンライナーなクエリでデータ取得

という流れです。HtmlAgilityPack で XPath を指定してもよいのですが、自前の ExDoc を使いたいので XML 形式(XHTMLもどき)に変換します。

HttpClient でデータ取得

HttpClient オブジェクトを作って、GetStringAsync で全行持って来れば ok です。

var cl = new HttpClient();
var text = await cl.GetStringAsync(url);

この方式の場合は、javascript でデータ加工していた場合は取れないので、その場合は別途 WebView を使います。大抵の場合、jQuery が動いているので、下記のように body 配下を取ってきます。

string[] para = { "$('body').html();" };
string html = await web.InvokeScriptAsync("eval", para);
html = "<html><body>" + html + "</body></html>";
textHtml.Text = html;

どちらの場合も HTML 形式は文字列で取得することになるので、これを DOM にパースする必要があります。

HtmlAgilityPack で DOM を作る

HtmlAgilityPack にはいくつものパラメータがあるのですが、デフォルトの状態で大丈夫です。

var hdoc = new HtmlAgilityPack.HtmlDocument();
hdoc.LoadHtml(text);

HTMLの場合、閉じタグを必要としないタグ(brとかhrとか)があるので、これを適当に補完して貰います。はじめは正確に DOM の形式にならないと、と思っていたのですが、ある一定のアルゴリズムで同じ結果になれば良いわけで、このあたりは大ざっぱでも構いません。見た目が問題なのではなく、データとしての一貫性があればよいのです。

HTMLのDOMをXDocumentに直す

適当に作った拡張クラスで、HtmlAgilityPack の HtmlDocument を XDocument に変換します。コメントなどが「#comment」のようになっているので、これを「_comment」に直しています。後で利用しやすいようにテキスト(#text)は、最初の項目だけ要素の値にしておきます。

public static class HtmlDocumentExtenstions
{
    public static XDocument ToXDocument(this HtmlAgilityPack.HtmlDocument doc)
    {
        try
        {
            var xdoc = new XDocument();
            if (doc.DocumentNode.ChildNodes.Count == 1)
            {
                xdoc.Add(doc.DocumentNode.FirstChild.ToXElement());
            } else
            {
                foreach ( var it in doc.DocumentNode.ChildNodes) {
                    if (it.ChildNodes.Count > 0)
                    {
                        xdoc.Document.Add(it.ToXElement());
                    }
                }
            }
            return xdoc;
        }
        catch (Exception ex)
        {
            var msg = ex.Message;
            return null;
        }
    }
    public static XElement ToXElement(this HtmlAgilityPack.HtmlNode node)
    {
        if (node.Name.StartsWith("#"))
            return new XElement(node.Name.Replace("#", "_"));

        var el = new XElement(node.Name);
        foreach (var it in node.Attributes)
        {
            el.SetAttributeValue(
                it.Name, it.Value);
        }
        foreach (var it in node.ChildNodes)
        {
            if (it.Name == "#text")
            {
                el.Add(new XText(it.InnerText));
            }
            else
            {
                el.Add(it.ToXElement());
            }
        }
        return el;
    }
}

利用するときは、こんな感じで一発で変換します。

var xdoc = hdoc.ToXDocument();

XDocument を ExDocument に直す

ExDocument の Load メソッドを使うだけです。

var doc = ExDocument.Load(xdoc);

moonmile/ExDoc
https://github.com/moonmile/ExDoc

後で NuGet にもしておきます。

ExDoc で検索する

こんな風に、<td class=”qtext” … > 要素を取り出します。

var lst = doc * "td" % "class" == "qtext";

結果は List になっていて、マッチした要素をすべて取るのですが、ひとつだけと決まっているのであれば、

ExElement lst = doc * "td" % "class" == "qtext";

こんな風に、ExElement 型で受けることができます。List<ExElement> と ExElement の両方を受けることができるように暗黙のキャストを使ったトリックです。なので、型指定をするとその型に変換されるようになっていて、var で推論させるとコレクションが取れるのです。
実は、このトリックは F# と相性が悪くていちいち op_implict でキャストしないといけないのですが。まあ、C# オンリーの技ということで良しとしましょう。

もうちょっと複雑な例としては、

ExElement lpage = (doc * "div" % "id" == "left") * "img" % "class" == "image";

<div id=”left”> の中にある <img class=”image”> を取ってきます。括弧が必要なのがいまひとつなのですが、演算子 * と % を使って次々とつなげていけます。* 演算子は子孫要素を検索して、/ 演算子は子要素のみを検索対象にします(ちなみに / 演算子のほうはバグが入っているらしくうまくいかないこともあるので、* 演算子を使ったほうが無難です)。

親要素は Parent プロパティで取得ができるので、el.Parent.Parent のように親の親を示すこともできます。目的の要素を見つけたら、その親にさかのぼって周辺のデータを取得する、という方法にも使えます。
要は、かっちりした形式ではなくて、なんらかの試行錯誤が必要なときに使えるライブラリです。遅延処理にはなっていないので、いちいち List を作ってしまうのですが、HTML からパースする分にはこれで十分かなと。イミディエイトウィンドウで、ちまちま検索するのにも利用できます、ってことで。

サンプル

アンケートツクレールで作成したアンケートをストアアプリで投稿するツール
https://github.com/moonmile/NetLabEnquete

 

カテゴリー: C#, EXDoc, WinRT | [WinRT] HttpClient+HtmlAgilityPack+XDocument+ExDoc で HTML データから抽出する話 はコメントを受け付けていません

[Xamarin] Visual Studio から Mac に iOS SDK Synchronization できない/iOS シミュレータに接続できないときの解決方法

たぶん、自分の環境前提かもしれませんが、一応メモ的に。

何かのはずみで「iOS SDK Synchronization」の Check Now がクリックできなくなって、Mac の MonoTouch との同期ができなくなることがあります。何故、こんなことになるのか分からないのですが、過去に2度ほど経験して、今回が3回目。

image

このチェックができないと、Visual Studio から Mac の iOS シミュレータに接続できなくて困った状態になるんですよね。暫くして、Xamarin がバージョンアップされると直ったりするのですが、今回は直近で仕事で使うためにいくつか調べました。

公式な回答としては、下記のように手動でシンクロする方法が示されていますが、うまくいきませんでした。回答自体が以前のバージョンなので(確か、この時にも Visual Studio からアクセスができなくておかしなことになった気が)、うまくいかないのかもしれません。

Downgrade or ignore iOS SDK sync error “The SDK synchronization returned with error Unknown” – Xamarin Forums
http://forums.xamarin.com/discussion/19540/unable-to-connect-to-server-and-the-sdk-synchronization-returned-with-error-unknown

先の Check Now が押せる状態になると、タスクバーに MonoTouch を同期させてください、というメッセージがでます。これをクリックして同期さえるわけですが、なんらかのタイミングでこのメッセージ自体がでなくなってしまいます。

image

たぶん、↑のが出していると思うんですよね。

でもって、サービス自体は↓ではないかと。

image

タスクマネージャを開くと「Bonjour Service」というものがあります。おそらく、これがシンクロを管理しているものです。このサービスを再起動させることによって、うまく同期が始まります。何かのフラグかなんかが引っ掛かっているような気がするんですよね。ちなみに、PC を再起動しても Xamarin を再インストールしても、この内部的なフラグはクリアされなくて、残っているようです。曖昧ですが、そんな感じで「再起動」するとうまくいきます。

ちなみに、Xamarin.iOS Build Host Diagnosis で疎通チェックをすると、Connection to Mac server で失敗した状態になっています…が、これで iOS シミュレーターに接続できる状態です。以前は、Failed のままだとシミュレータに接続できなかったので、どこかの時点で内部的に変わったのかもしれません。

image

ちなみに、iOS シミュレータに接続できない状態の時は、最後の Comparing のところが Failed になっていて、上の項目は Passed になっていました。何か変な状態になっているようですが、ひとまずこれで Visual Studio から iOS シミュレータに接続ができます。

カテゴリー: Xamarin | [Xamarin] Visual Studio から Mac に iOS SDK Synchronization できない/iOS シミュレータに接続できないときの解決方法 はコメントを受け付けていません

Xamarin Android Player と Hyper-V を共存させてエミュレータ環境を改善する

Xamarin Android Player | Xamarin
http://developer.xamarin.com/guides/android/getting_started/installation/android-player/
Xamarin Android Player のインストール方法 ( Google Apps & Google Play Services 含む ) – Yuta Watanabe’s Blog
http://yutawatanabe.hatenablog.com/entry/xamarin-android-player-preview-install
Xamarin Android Player 小ネタ – Xamarin 日本語情報
http://ytabuchi.hatenablog.com/entry/2014/10/09/164202

インストール等は、上記の情報を参照して頂くとして、自前の環境で Hyper-V と Xamarin Android Player を同時に動かしています。Xamarin Android Player 自体は、VirtualBox 上で動いているものを OpenGL で描画部分だけ取り出している(っぽい)ので、結論的には Hyper-V と VirtualBox を共存させればよい、ということみたいですね。

ちなみに、Xamarin Andorid Player 自体 ver.0.0.24 というアルファ版っぽいバージョンなので、このあたりは今後改善されるでしょう。OpenGL 経由で表示するので、Hyper-V の仮想環境内やリモートデスクトップでは動きません。OpenGL 対応のリモートツールがあれば動くのかも。

若干パフォーマンスが落ちるらしい

Hyper-V を有効にした状態だと、下のような警告がでます。最初、なかなか先に進まないのでエラーなのかと思ったのですが「警告」です。

image_thumb[1]

Client Hyper-V is enabled on this system; this can result in decreased performance.  For maximum performance, please disable Hyper-V and reboot.

パフォーマンスは落ちるけど、動くよ、ってことなので、そのまま進めます。Windows Phone 8.1 を開発していなかったり、Windows 10 Technical Perview などを試さない場合は、Hyper-V を切っておくとよいでしょう。コントロールパネルで「Windows の機能」で検索して、Hyper-V のチェックを外しておけば ok です。

image_thumb[2]

私の環境では、Windows TP と Windows Phone エミュレータと同時に動いています。

VirtualBox のネットワークを確認する

私が嵌ったのは、ネットワークの問題でした。最初にインストールした状態では、下のようなダイアログがでて起動しなかったんですよね。

image

なにやら、OpenGL のサーバーに繋げているときに、ネットワークが通らない…って話です。VirtualBox を導入したのは初めてなもので、Hyper-V と Xamarin Android Player は共存できないのか?とあきらめました。あれこれ弄ると、下のようなメッセージにもなります。

image_thumb[4]

どうやら、VisualBox 上で動く OpenGL サーバーと Player が通信していることが分かったので、VirtualBox のネットワークを確認してみます。

image_thumb[5]

一発でうまくいく場合は、VirutalBox Host-Only Ethernet Adapter しかないと思います。私の場合は、このホスティングのインストールが失敗したらしく、#3 のようにいくつかのアダプターがつくられています。コントロールパネルで「ネットワーク接続」を開いて IP を調べてみると、こんな風に違いがあります。左がつながらない元のネットワークで、右が「#3」のつながっているほうです。

image_thumb[8] image_thumb[9]

私の場合、以前 VMWare を入れていたので、ローカルネットワーク構築時の IP がややこしくなっているのかなと想像しています。うまく OpenGL サーバーにつながらないときは、この部分を見直してみてください。(10.* なのに、サブネットマスクが 255.255.255.0 ってのもアレですが)。

外部接続は アダプター2 で接続している

Player に OpenGL をホストしている方は「アダプター1」を使っていて、外部ネットワークへ接続する方(Google Play などで必要)は「アダプター2」を使っているようです。私の場合、初期値である割り当てが「NAT」ではうまくつながらなかったので、ブリッジアダプターを使って接続しました。

image_thumb[10]

一度接続した後は、NAT に戻しても接続できるようになっているので、VirtualBox のネットワークの問題ですよね、おそらく。うまく繋がらないときは、このあたりを変更してみましょう。私の環境が QEMU とかを含めてややこしいネットワーク環境になっているため、ってのもありますが。

最初は Xamarin Studio で起動させる

Player への接続ですが、最初は Visual Studio から起動できませんでした。選択肢に Player の KitKat が現れません。これも環境によるのかもしれませんが、

  1. Xamarin Android Player を起動させる。
  2. Xamarin Studio で認識させる。
  3. Visual Studio で Target Android Device に現れる。

という流れで確実に表示できます。

スピード的には HAXM で Android 仮想デバイスを高速化 と同じぐらいのスピードで動いています。Hyper-V を切って、VirtualBox Only で動かせば、結構なスピードがかもしれません。AEDSearch の Azure サービス部分が妙に重たくて Mac のほうを使っていたりしたのですが、これで開発のスピードアップができそう。かなり早く動いてます(なんか、地図のところがエミュレーターでうまく動かないんですよね…)。

カテゴリー: Xamarin | Xamarin Android Player と Hyper-V を共存させてエミュレータ環境を改善する はコメントを受け付けていません

[WinRT] Youtube のサムネールを GridView に表示させる

VS魂100連発アプリでは Youtube のリストを拾ってきて表示させています。Youtube の API を使うためには、Google アカウントが必要で、それぞれの APP KEY が必要になってきます。
データアクセスは Google.Apis.YouTube.v3 で、起動時の URL を取得するところは MyToolkit.Extended を使っています。MyToolkit を使わなくても URL のパラメータでできるハズなんですが、なんとなく。

Google.Apis.YouTube.v3 の APP KEY を取得する

Google Developer Console で「YouTube Data API v3」を ON にします。

「APIs & auth」→「Credentials」で、Public API access キーを作成します。検索するだけなので、ここで作られる API KEY を使います。もし、この API KEY が漏れて悪用されているようであれば、もう一度作成してアプリに埋め込めば ok です。
まあ、ストアアプリの場合、実行ファイルを覗けてしまうので API KEY を暗号化とかしない限りばれてしまうわけですが。そのあたりは適当に。

NuGetを利用する

Youtube へのアクセスは、Google.Apis.YouTube.v3 で検索します。

MyToolkit も Nuget にあります。

なんか、いっぱい DLL をインストールされますが、気にしないことにします。まあ、適当なツールアプリなので。

動画リストを取得する

100連発のリストは、動画リストから取ってきているのですが、これたちょうど VisualStudioJapan のチャンネルID にあたります。このチャンネル ID をどうやって拾うのか?は疑問なのですが、あれこれ実験しているうちに取れたものです。動画を検索したときに、どうやら一致しているっぽい値を入れました。URL には出てこないので、別途検索キーにするといいでしょう。この場合は「VisualStudioJapan」の検索にマッチさせたものを取ってくると良いでしょう。

async Task<VideoList> getYoutube()
{
    // タイトル一覧を取得する
    var youtube = new YouTubeService(new Google.Apis.Services.BaseClientService.Initializer()
    {
        ApiKey = "", // Google API key
        ApplicationName = "VS100Watcher"
    });

    var videos = new VideoList();

    var req = youtube.Search.List("snippet");
    req.ChannelId = "UCy2j4l0auN8DJaQ6itwVWFA"; // "VisualStudioJapan" のチャンネルID
    var res = new Google.Apis.YouTube.v3.Data.SearchListResponse(); 
    do
    {
        req.PageToken = res.NextPageToken;
        res = await req.ExecuteAsync();
        // Youtube のサムネールをリスト化する
        foreach (var it in res.Items)
        {
            var vi = new Video() { Result = it };
            videos.Add(vi);
        }
    } while (res.NextPageToken != null && res.NextPageToken != "");

    // 番号でソートする
    var lst = new VideoList();
    foreach ( var it in videos.OrderBy(x => {
        if ( x.Title.StartsWith("VS100") ) {
            return x.Title.Replace( " ", "" ).Replace("-", "");
        } else {
            return x.Title;
        }}))
    {
        lst.Add(it);
        Debug.WriteLine(it.Title);
    }
    return lst;
}

Youtube クラスの使い方に若干癖がある(ビデオや他のデータと混在しているため)のですが、それはインテリセンス等で確認してください。
Java のコードサンプルを参考にするとよいでしょう。.NET のもあるのですが、数が少なくなってます。

YouTube Data API: Java コード サンプル – YouTube ? Google Developers
https://developers.google.com/youtube/v3/code_samples/java?hl=ja

リファレンスがあるのですが、個人用のアプリであればそこまで使わないような気がします。

検索結果は複数のページに分かれています。応答データの NextPageToken プロパティを見て次のページを取得していきます。最後の番号でソートしているのは、タイトルフォーマットがいくつか違っているので揃えるためです。

動画のURLを取得する

GridView のリストをタップしたときには、高画質動画を表示しています。このためだけに MyToolkit を使っているという贅沢な仕様ですが、まあ手早く作れるのでよいでしょう。以前作ったときのコードをそのまま使っています。

private async void itemGridView_Tapped(object sender, TappedRoutedEventArgs e)
{
    var item = (sender as GridView).SelectedItem ;
    if (item == null ) return;

    vm.Current = item as Video;
    MainPage.SelectItemIndex = (sender as GridView).SelectedIndex;

    var uri = new Uri( vm.Current.Url );
    if (vm.GoIE == true)
    {
        await Launcher.LaunchUriAsync(uri);
    }
    else
    {
        try
        {
            var url = await YouTube.GetVideoUriAsync(vm.Current.VidoId, YouTubeQuality.Quality1080P);
            this.media.AutoPlay = true;
            this.media.Source = url.Uri;
            this.media.Play();
        }
        catch { }
    }
}

今回は、常時接続を前提としているので動画ファイルは保存していませんが、ストリームを使ってローカルファイルに保存もできるでしょう。よく見る画像は、ビデオフォルダに保存しておくと、手軽に見られると思います。また、検索用のキーワードを付ければ汎用的な Youtube の閲覧ツールにもなるでしょう。

サンプル

moonmile/WatchVs100
https://github.com/moonmile/WatchVs100

カテゴリー: C#, WinRT | [WinRT] Youtube のサムネールを GridView に表示させる はコメントを受け付けていません

[WinRT] MediaElement の操作メニューは AreTransportControlsEnabled で出す

既に出オチ…というか、これだけなのですが、Windows 8.1 から(というか、.NET Framework 4.5.1 からは)MediaElement に AreTransportControlsEnabled プロパティというのがあって、簡単に操作ボタンを出せます。あの「Play」とか「Stop」とかのボタンを自作しないで済むんですね。インジケータも出るので、AreTransportControlsEnabled=”True” と書いておけば一発です。

いつも忘れるのですが、Windows 8 のほうには無くて、

MediaElement プロパティ (System.Windows.Controls)
http://msdn.microsoft.com/ja-jp/library/system.windows.controls.mediaelement_properties(v=vs.110).aspx

Windows 8.1 のほうにはあるのです。

MediaElement.AreTransportControlsEnabled property – Windows app development
http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.controls.mediaelement.aretransportcontrolsenabled

なので、普段 Developer Network で検索してもなかなか出てこなくて、うまくやると Dev Center のほうで検索されるという。いや、Dev Center のほうを使えばいいのですが、英語が主なので、何となく日本語版の Developer Network が google で検索されるというオチです。

普段は小さ目に貼り付けておいて、フル画面表示にすることもできます。

20141009_05

VS魂100連発ではスタート画面のピン留めから起動されたときには、自動的にフル画面になるようにしてあって MainPage クラスにこんな仕込みをしてあります。

public async void LoadState( string videoid )
{
    if (videoid != "")
    {
        this.media.IsFullWindow = true;
        var url = await YouTube.GetVideoUriAsync(videoid, YouTubeQuality.Quality1080P);
        this.media.AutoPlay = true;
        this.media.Source = url.Uri;
        this.media.Play();
    }
}

そして、app.xaml.cs の OnLaunched メソッドのときにページの状態をみて LoadState メソッドを呼び出します。100連発のアプリは一枚しかないので、Page を見る必要はないのですが念のため。

	if (rootFrame.Content == null)
	{
	    // ナビゲーションの履歴スタックが復元されていない場合、最初のページに移動します。
	    // このとき、必要な情報をナビゲーション パラメーターとして渡して、新しいページを
	    // 作成します
	    if (!rootFrame.Navigate(typeof(MainPage), e.Arguments))
	    {
	        throw new Exception("Failed to create initial page");
	    }
	}
	else
	{
	    // 既存のページにパラメータを渡す。
	    var page = rootFrame.Content as MainPage;
	    if ( page != null ) {
	        page.LoadState(e.Arguments);
	    }
	}
	// 現在のウィンドウがアクティブであることを確認します
	Window.Current.Activate();
}

実は、プロトコルからアクセスしたときのために OnActivated イベントがあるのですが、これは Launch の時(スタート画面などから起動されたとき)は呼び出されないため、OnLaunched の中に書きます。それ以外のときは、args.Kind で呼び出された状態を識別して分岐させます。

ActivationKind enumeration – Windows app development
http://msdn.microsoft.com/en-us/library/windows/apps/windows.applicationmodel.activation.activationkind(v=win.10).aspx

FileOpenPicker や ContactPicker を作ると、独自のピッカーが作れるという訳です…まだ作ったことはありませんが。

カテゴリー: C#, WinRT | [WinRT] MediaElement の操作メニューは AreTransportControlsEnabled で出す はコメントを受け付けていません

[WinRT] ストアアプリで動画のサムネールを取得する

怒涛の GridView シリーズの続きです。Kindle Launcher ネタはひと区切りついたので MP4 Launcher で使っているテクニックの紹介です。とある理由で、MP4 ファイルがたくさんあると仮定して、Surface RT やら PC でそれを観賞しようとすると、真っ先に動くのはストアアプリの「ビデオ」アプリなんですが、このアプリ、いちいちフォルダを開いて動画ファイルを探しに行かないといけないし、そもそも前の状態を覚えておいてくれないので、シリーズものを連続で観賞するには非常に不便です。いきおい、従来の Windows Media Player を動かすという手もあるのですが、Surface のようなタブレットの場合、音量の調節とか「標準ビデオ」のほうがやりやすいんですよね。

以前、シリーズ毎のストアアプリを作ってみたものの、シリーズ毎に自作しなくてはいけなくて途中で面倒になってしまいました。で、Kindle Launcher と同じようにスタート画面にピン留めすれば良いのでは?と思って勢いで作ったのが MP4 Launcher です。
Kindle Launcher の場合は、漫画の表紙をタイルに表示しています。表紙そのものが png あるいは jpeg でダウンロードできるので比較的簡単です。ビデオファイルの場合はどうするのか、と悩むところなのですが、実は WinRT には動画ファイルのサムネールを取得できる機能があります。

ファイルピッカーを使うと、mp4 のような動画ファイルはサムネールが表示されます。動画ファイルだけでなく
ピクチャファイルなどのサムネールも取れたりするのですが、ここでは動画を対象にします。この機能をそのまま、自作アプリに持ってこれるといいですよね。

自作アプリ内の GridView に表示させるとこんな感じになります。それぞれのセルにサムネールを表示させています。サムネール自体は、自動で作成されるらしく動画の位置を指定することはできません(と思います)。なので、時には同じ画像ばっかりが並ぶことがあるのですが、大抵の場合は違う画像が並びます。

フォルダを開くピッカーを使う

まずは、動画の入っているフォルダを指定します。ファイル指定でもいいのですが、大抵はシリーズものがひとつのフォルダにまとめてある(と思われる)ので、ユーザーにフォルダを指定して貰います。

private async void OnButtonItemSearch(object sender, RoutedEventArgs e)
{
    var picker = new FolderPicker();
    picker.CommitButtonText = "フォルダを指定する";
    picker.ViewMode = PickerViewMode.Thumbnail;
    picker.SuggestedStartLocation = PickerLocationId.VideosLibrary;
    picker.FileTypeFilter.Add(".mp4");

    var folder = await picker.PickSingleFolderAsync();

PickerViewMode には、リスト形式(List)とサムネール形式(Thumbnail)があります。ここでは Thumbnail を指定しておきます。このあたりはファイルを指定するときと同じです。

フォルダ内の動画のサムネールを取得する

ピッカーで取れたフォルダを自前のリストに保存します。リストは ViewModel スタイルにして、GridView にバインドできるようにしておきます。

public class VideoList : ObservableCollection<Video> { }
public class Video : BindableBase
{
    /// <summary>
    /// タイトル
    /// </summary>
    private string _Title;
    public string Title
    {
        get { return _Title; }
        set { this.SetProperty(ref this._Title, value); }
    }
    /// <summary>
    /// ファイルパス
    /// </summary>
    private string _Path;
    public string Path
    {
        get { return _Path; }
        set { this.SetProperty(ref this._Path, value); }
    }
    /// <summary>
    /// サムネール自体
    /// </summary>
    private Windows.Storage.FileProperties.StorageItemThumbnail _Thum;
    public Windows.Storage.FileProperties.StorageItemThumbnail Thum
    {
        get { return _Thum; }
        set { this.SetProperty(ref this._Thum, value); }
    }
    /// <summary>
    /// サムネールのBitmap
    /// </summary>
    private ImageSource _IconImage;
    public ImageSource IconImage
    {
        get { return _IconImage; }
        set { this.SetProperty(ref this._IconImage, value); }
    }
    /// <summary>
    /// サムネールの保存パス名
    /// </summary>
    private string _IconUrl;
    public string IconUrl
    {
        get { return _IconUrl; }
        set { this.SetProperty(ref this._IconUrl, value); }
    }
}

ちょっと、ややこしいですが、サムネール自体(Thum)とサムネールのビットマップ(IconImage)を別に用意しておきます。ビットマップのほうは Image コントロールにバインドするものです。サムネール自体を保存しているのは、スタート画面にピン留めするためにアプリケーションデータにファイル保存をするためです。

XAML 自体は、こんな風になります。Image コントロールに Source=”{Binding IconImage}” でバインドですね。これと Video クラスの IconImage プロパティが結びつきます。

<GridView
    Grid.Column="1" Grid.Row="1"
    x:Name="itemGridView"
    TabIndex="1"
    Padding="10"
    SelectionMode="{Binding SelectMode}"
    ItemsSource="{Binding Items}"
    IsSwipeEnabled="false"
    Tapped="itemGridView_Tapped">
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid HorizontalAlignment="Left" Width="250" Height="250">
                <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
                    <Image Source="{Binding IconImage}" Stretch="UniformToFill" />
                </Border>
                <StackPanel VerticalAlignment="Bottom" Orientation="Vertical"
                    Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
                    <TextBlock Text="{Binding Title}"
                        Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}"
                        Height="30" Margin="3"
                        TextWrapping="NoWrap"/>
                </StackPanel>
            </Grid>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

フォルダのピッカーで拾えるのは、StorageFolder オブジェクトなので、この中のふぁいるからサムネールを取ってきます。

async void OpenFolder(StorageFolder folder)
{
    if (folder == null) return;
    vm.FolderPath = folder.Path;
    vm.FolderName = folder.Name;
    // 拡張子 *.mp4 のファイルを探す
    var lst = await folder.GetFilesAsync();
    var items = lst.Where(x =>
    {
        switch (System.IO.Path.GetExtension(x.Name))
        {
            case ".mp4":
                return true;
            default:
                return false;
        }
    })
        .Select(async x =>
        {
            var thum = await x.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode.VideosView);
            var bmp = new BitmapImage();
            bmp.SetSource(thum);
            return new Video()
            {
                Title = x.DisplayName,
                Path = x.Path,
                Thum = thum,
                IconUrl = x.Name + ".png",
                IconImage = bmp,
            };
        }
    );
    vm.Items.Clear();
    foreach (var it in items)
    {
        vm.Items.Add(await it);
    }
}
  1. GetFilesAsync クラスでフォルダ内のファイルを探索
  2. LINQ の Where メソッドで拡張子 .mp4 を拾い出す(switch になっているのは、.mpeg なども調べられるように修正した名残です)。
  3. Select メソッドで Video オブジェクトを作成する

サムネール自体は、GetThumbnailAsync メソッドで作成できます。これを Image コントロールに渡せる BitmapImage に直すためには、そのまま SetSource メソッドを呼び出せば ok です。BitmapImage オブジェクトは GC で不要になったら解放されます。サムネールを自前の GridView に表示している間、保持しておけばよいわけです。具体的には、ViewModel にあたる VideoList オブジェクトがキープされる間、保持されています。

これで先ほどの自前のサムネール表示ができます。ここではサムネール自体を加工せずに表示させていますが、ちょっと手間をかければ加工することも可能です。BitmapImage オブジェクトはそのままでは加工できないので、WritableBitmap に変換するか、DirectX を使います。C# から直接 DirectX を使うことはできないので Win2D(NuGet で取得できます)を使うとよいでしょう。Win2D での加工は今度やってみましょう。

サムネールをアプリケーションデータに保存する

Kindle Launcher のように、動画のサムネール画像を使ってスタート画面にピン留めできるようにしておきましょう。タイルに表示させる画像はリソースかアプリケーションデータ内と決まっているので、アプリデータにユニークな名前になるように保存します。動画ファイルのファイル名をそのまま使ってもよいのですが、セカンダリタイルの TileID として使いたいので、名前をユニークになるように変換します。TileID には「(」などの特殊な記号が使えないので、この方法を使っています。

private string toMD5(string s)
{
    //文字列をbyte型配列に変換する
    byte[] data = System.Text.Encoding.UTF8.GetBytes(s);

    //MD5CryptoServiceProviderオブジェクトを作成
    var md5 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Md5);
    BinaryStringEncoding encoding = BinaryStringEncoding.Utf8;
    var buff = CryptographicBuffer.ConvertStringToBinary(s, encoding);
    var hash = md5.CreateHash();
    hash.Append(buff);
    var dest = hash.GetValueAndReset();
    string signature = CryptographicBuffer.EncodeToHexString(dest);
    return signature;
}

private async System.Threading.Tasks.Task<bool> CreateSecondaryTileAsync(Video item)
{
    string tileId = "MP4-" + toMD5(item.Title);
    var tile = new Windows.UI.StartScreen.SecondaryTile()
    {
        TileId = tileId,
        DisplayName = item.Title,
        Arguments = toMD5(item.Title),
        RoamingEnabled = true,
    };
    tile.VisualElements.ForegroundText = Windows.UI.StartScreen.ForegroundText.Light;
    tile.VisualElements.ShowNameOnSquare150x150Logo = true;

    // アプリローカルに保存
    var stt = item.Thum.GetInputStreamAt(0);
    byte[] data = new byte[item.Thum.Size];
    var st = stt.AsStreamForRead();
    st.Read(data, 0, data.Length);
    var folder = Windows.Storage.ApplicationData.Current.LocalFolder;
    var path = toMD5(item.Title) + ".jpg";
    try
    {
        var file = await folder.CreateFileAsync(path);
        using (var sw = await file.OpenStreamForWriteAsync())
        {
            sw.Write(data, 0, data.Length);
            sw.Flush();
        }
    }
    catch
    {
        // 同名のファイルがある場合は、そのまま使う
    }
    // テキストファイルにmp4のパスを保存
    var path2 = toMD5(item.Title) + ".txt";
    try
    {
        var file = await folder.CreateFileAsync(path2);
        using (var sw = await file.OpenStreamForWriteAsync())
        {
            var data2 = System.Text.Encoding.UTF8.GetBytes(item.Path);
            sw.Write(data2, 0, data2.Length);
            sw.Flush();
        }
    }
    catch
    {
        // 同名のファイルがある場合は、そのまま使う

    }
    tile.VisualElements.Square150x150Logo = new Uri("ms-appdata:///local/" + path);
    tile.VisualElements.Square30x30Logo = new Uri("ms-appdata:///local/" + path);

    return await tile.RequestCreateForSelectionAsync(GetElementRect(this.btnMakeTile));
}

public static Rect GetElementRect(FrameworkElement element)
{
    GeneralTransform buttonTransform = element.TransformToVisual(null);
    Point point = buttonTransform.TransformPoint(new Point());
    return new Rect(point, new Size(element.ActualWidth, element.ActualHeight));
}

サムネールを取得したときの Thum プロパティの値を使います。StorageFile クラスの GetInputStreamAt メソッドを使って先頭位置からのストリームを取得します。byte[] を使っていますが、System.IO.MemoryStream を使ってもよいでしょう。このあたりは定番の処理になります。

たまたまサムネールの位置が同じ場合には同じ画像が並んでしまうのと、タイトルで表示するときに文字が読み辛くなるという難点はありますが、ひとまずスタート画面に画像付きのタイルができます。自前のリストにも画像付きのリストがでいるので、結構見栄えがよくなるのではないでしょうか。

サムネール保存時に遅延が発生する

ファイルピッカーもそうなのですが、サムネールを表示するときに遅延が発生します。await/async を使ってバインドを使っているせいなのですが、ちょっと面白い/困った現象が出ます。ViewModel でバインドをしているので、サムネールの画像が取得できたときに画面に表示されます。この操作自体がパラレルで行われるためか、次のようにたくさん動画ファイルのあるフォルダを指定したときに問題が出ます。

  1. 動画ファイルがたくさんあるフォルダを指定する。
  2. サムネールが順々に表示される。
  3. 表示の途中で、別のフォルダを選択する。
  4. 別のフォルダを指定したが、前のフォルダのサムネールがいくつか表示される。

4 のように、まだサムネールを取得しきっていないセルが GridView に表示されてしまいます。これはサムネールの遅延処理のための対策が、先にコードに不十分と思われるので、何か対処が必要ですよね。まあ、このあたりが ViewModel と遅延処理(あるいは重たい処理)の弊害ってところなんです。この話はまた別途。

 

カテゴリー: C#, WinRT | [WinRT] ストアアプリで動画のサムネールを取得する はコメントを受け付けていません

[WinRT] デスクトップアプリをスタート画面にタイルで表示する方法

デスクトップ アプリのスタート画面のタイルをカスタマイズする方法 (Windows ランタイム アプリ) (Windows)
http://msdn.microsoft.com/ja-jp/library/windows/apps/xaml/dn449733.aspx

基本は上記をよく読めばいいのですが、何故か最終的に 150x150Logo.png 等のタイルのファイルを何処に置くのか?が書いてないので、補足&メモ的に残しておきます(ステップ1から5までが対象になりますね)

*.visualelementsmanifest.xml ファイルを用意する

<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <VisualElements
        BackgroundColor="#FF0000"
        ShowNameOnSquare150x150Logo="on"
        ForegroundText="light">
</Application>

あるいは

<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <VisualElements
        BackgroundColor="#FF0000"
        ShowNameOnSquare150x150Logo="on"
        ForegroundText="light"
        Square150x150Logo="Assets150x150Logo.png"
        Square70x70Logo="Assets70x70Logo.png"/>
</Application>

という形で、ファイルを作成する。*.exe と同じ位置に用意するので、下の図のようにアプリケーション名 + visualelementsmanifest.xml で書いておきます。

image

Square150x150Logo と Square70x70Logo を指定しないときは、自動的にアプリのアイコンが使われます。背景色 BackgroundColor で揃えてもいいのですが、普通のストアアプリと同じように Logo のファイルも用意しておきます。
Assets フォルダを作っていますが、App.ico などと同じ場所に作ってもよいでしょう。その場合は、Assets150x150Logo.png を 150x150Logo.png のように書き換えます。

画像ファイルを「常にコピーする」に変更する。

image

ファイルのビルドアクションは「コンテンツ」、出力ディレクトリに「常にコピー」あるいは「新しい場合はコピーする」にします。ビルドしたときに、Assets フォルダに2つの画像ファイルが作られるようにしておけば ok です。

image

プログラムファイルにショートカットを作る

  1. Win+R キーを押して、shell:programs を起動する。
  2. *.exe のショートカットを作成する。

ショートカットは普通の *.lnk ファイルになります。

image

スタート画面のアプリメニューからピン留めする

スタートメニューのプログラム以下にショートカットを置くと、アプリ一覧にアイコンが出るのでスタート画面にピン留めします。

image

「Kindle 本棚」のアイコンがそれで、ストアアプリと同じように作れます。アイコン自体は、

Windows Store App logo Maker – 高橋 忍のブログ – Site Home – MSDN Blogs
http://blogs.msdn.com/b/shintak/archive/2013/01/01/10418997.aspx

を使うと便利です。必要なのは 150 と 70 のアイコンなので、それを Assets フォルダにコピーすれば ok です。

インストーラでスタートメニューに登録する

Visual Studioのセットアッププロジェクトが復活
http://www.infoq.com/jp/news/2014/05/vs2013_installer_project
Microsoft Visual Studio Installer Projects 拡張機能
http://visualstudiogallery.msdn.microsoft.com/9abe329c-9bba-44a1-be59-0fbf6151054d

標準の Visual Studio 2013 にはインストーラが付いていないのですが、Gallery からダウンロードできます。作り方は従来の Setup と同じなので、これを使ってスタートメニューに登録もできます。Kindle Launcher の購入一覧の取得ツールはこれを使っています。

image

アプリケーションフォルダに「コンテンツファイル」を追加すると Assets 以下のファイルもインストールの対象になります。

タイルを更新する裏ワザ

先の MSDN のヘルプにある、「ステップ 7: 重要!ショートカット ファイルを更新する」を利用します。裏ワザというか、リンクファイル(*.lnk)をスタート画面に再読み込みさせる仕組みです。画像ファイルや *.visualelementsmanifest.xml だけを更新してもスタート画面の画像は変化しません。説明にある通り、powsershell を使って (ls “$env:programdatamicrosoftwindowsstart menuprogramscontoso.lnk”).lastwritetime = get-date で *.lnk ファイルを更新します。いわゆる、touch コマンドと同じです。

なので、デスクトップアプリで定期的に画像ファイルを更新して、*.lnk を更新すればスタート画面のタイルをライブタイル化できるということです。定期的に *.visualelementsmanifest.xml の中身を書き換えるツールを作ればよいので、これはそれほど難しくないと思います。おそらくストアアプリの制限よりも早く回せるでしょう。まあ、デスクトップ側にスケジューラ用のアプリが自前で必要にはなりますが。

カテゴリー: WinRT | [WinRT] デスクトップアプリをスタート画面にタイルで表示する方法 はコメントを受け付けていません

Windows 10 Technical Preview を Hyper-V で試す

タブレットでWindows 10 TPに突撃してわかった3つの注意事項 – Surface 2 & Pro 3 非公式マニュアル + WP(使い方・活用法)
http://surface.viva-m-tablet.info/entry/2014/10/03/150520

等、タブレットPC 直接入れている方も多々居られるのですが、安全に(苦笑)Hyper-V に入れます。ひとまず、メモリ 8GB + HDD 128 GB を割り当てて Windows 10 TP を入れました。

英語版/中国語版/ポルトガル語版があるので「英語版」を選んでいます。インストール時に日本語キーボードがつながっていると、日本語IMEを自動で有効にしてくれるようです(ひょっとすると Hyper-V の機能かもしれません)。インストール自体は英語で行われますが、基本は Windows 8 のインストールと同じなので迷うことはないでしょう。

日本語キーボードに直す

日本語IMEは有効になるのですが、キーボードは英語キーボードのままです。英語Windows7で日本語キーボードを使う方法 | Moonmile Solutions Blog を参考にして、日本語キーボードに直しておくとよいでしょう。

スタート画面を有効にする

Windows 10 では「スタートメニューが復活します」と言われているのですが、そこそこ Windows 8 に慣れてしまうと「スタート画面」のほうが便利です。

image

Win キーを押しても、スタートメニューしか開かないので、タスクバーを右クリックして「properties」を開き「start menu」タブから「Use the Start..」のチェックを外します。この項目は Window 8 にはなく Windows 10 特有のものです。

image

一旦、サインアウトするとことで、スタート画面が有効になります。

image

日本版のストアアプリをインストールする

英語版のストアアプリはそのままインストールできるのですが、何故かに日本語版のストアアプリは「Get Widnows 8.1 to run this app.」になっていてインストールできません。

image

ですが、ストアのマイアカウントからマイアプリを選択するとインストールが可能です。なんででしょうね…不思議ですが、これでお試しができます。

image

ストアプリは2つのモードで動く

事前に噂があった通り Windowed モード(ウィンドウモード)とフルスクリーンで動きます。

image image

ウィンドウモードは、こんな風に大きさを自由に変えられます。この Frozen Free Fall というゲームはタブレットの横置きと縦置きに対応しているので、横幅を変えると画面が変わります。このあたりはアプリ/ゲームがどの程度対応しているかによると思います。

フルスクリーンにする場合は、システムメニューから「Full Screen」を選択します。まだ、ショートカットキーが付けられていませんが、何かつくでしょう。

image

フルスクリーンにすると、従来の Windows 8 と同じように全画面表示になります。何故か、タスクバーの表示とかチャームとかも表示できない不完全なフルスクリーンモードなのですが…これは開発途中だからでしょう。

image

普段は、いわゆる「最大化」モードでストアアプリが起動します。

マルチモニタでのストアアプリの動き

Hyper-V でマルチモニタを使うようにしてみました。

image

一見、ストアプリのウィンドウモードも自由に動いて結構いい感じのような感じがしますが、問題があります。

  1. ストアアプリをフルスクリーンで開いた状態にしておく。
  2. 別のモニタで、デスクトップのウィンドウあるいはストアプリのウィンドウを操作する。
  3. 1のストアプリがウィンドウモードになってしまう。

全画面のストアアプリがウィンドウモードに戻ってしまうのは難点ですね。これも開発途中だから、なのかもしれません。ちなみに、私の MP4 Launcher などは FilePicker を使ってフォルダを選択するのですが、これが別画面になって開いてしまいます。本来ならば同じ画面に開くものなのに(理由は分かりますが…ピッカーはコントラクトなので別アプリなんですよね)。そんな訳で、デスクトップに特化して、ストアアプリ(タブレットモード)がおざなりになってしまっています。内実は想像できるのですが(リリース自体が1年以上遅れてるのですから)、ちょっと不安です。

ちなみに IE はデスクトップモードしかなくストア版がありません。このあたりも相当変です。タブレットPC に Windows 10 TP を入れる勇者の方はご注意ください。

Task View が奇怪な動きをする

鳴り物入り?っぽいタスクビュー(マルチデスクトップ)ですが、これもかなり奇怪な動きをします。

タスクバーにある四角い箱をクリックすると、複数のデスクトップを開くことができるのですが…いま、どこのデスクトップにいるのか分かりません。さらに言うと、別のデスクトップで開いたウィンドウを別のデスクトップに動かす手段は…あるのかな?ちょっとわかりません。

image

追記:Thx です。

 

とのこと、ドラッグ等はいずれサードパーティ製で作られそうですね

デスクトップごとに背景を切り替えたいところなのですが、複数の Task View の全てが切り替わります。さらに言うと、デスクトップ毎にタスクバーのアイコンが切り替わりません。このあたりの動きは Ubuntu などの Linux 系のウィンドウシステムを見習ってほしいところです。

と言いますか、個人的にはこの Task View 機能はいりません。マルチモニタの場合には、作業用モニタと観賞用モニタを分けたり切り替えたりするので、作業領域を変えたりしないんですよね。おそらく、ノート PC のようなモニタが1枚しかない状態を想定しているのでしょうが、この手のマルチデスクトップはフリーウェアでいくつか出ているので OS の機能として必要なのかは疑問です。まあ、フリーウェアの機能を次バージョンで取り入れてしまうのが Microsoft の手法なので、それの一環なのかもしれませんが。

ちなみに Task View にストアプリを開いて切り替えようとすると、OS ごと固まります。Preview なのかもしれませんが、企業向けの評価版でもあるので、これによって「評価が下がる」ことも覚悟しないといけませんね。ちょっと、この状態では Windows 10 は人に勧められる状態ではありまえん。素直に来年まで待って、顧客バージョンを以って人に勧めるほうがよさそうです。

ストアアプリは ApplicationFrameWindow 内にある

まあ、そんな不具合込みでも色々弄りたいもので、ストアアプリがウィンドウ化したのですから、なんらかのウィンドウハンドルを持っているハズですよね。

20141003_03

Visual Studio を入れて Spy で見ると「ApplicationFrameWindow」というウィンドウの下につくられています。Windows 8.1 の時は、Windows.UI.Core.CoreWindow がトップウィンドウだったので、これをさらにくるむ形になります。おそらく、最大化/最小化/フルスクリーンは、このウィンドウに対して SendMessage を送ればよいのではないでしょうか。

低スペックPCではどうだろうか?

実はおもしろいことに、Windows 10 の要求するスペックは Windows 8 と同等です。Windows 8 の要求するスペックがそもそも低いので、結構古いノート PC でも動くということですね。Twitter を見ると、ハンドヘルドな Windows 7 マシンに Windows 10 TP を入れている方もいらっしゃいます。私の手元にあるのは、Windows CE なもんだからちょっと無理(苦笑)。古めのノート PC に Windows 10 TP を入れてみましょう。ただし、要求する CPU スペックは低いのですが、HDD のアクセスの推奨値は結構高いです。これに引っ張られて、起動はできるけど実用に耐えないという感じになるかもしれないので注意が必要です。うちの 32bit Windows 8.1 PC は Firefox を立ち上げていくつか動かすだけでメモリ不足になるので(Windows Media Center が悪さをしている可能性も高いのですが)、32bit だとちょっと難しいかもしれませんが。

カテゴリー: 開発 | Windows 10 Technical Preview を Hyper-V で試す はコメントを受け付けていません