Quantcast
Channel: かずきのBlog@hatena
Viewing all 1388 articles
Browse latest View live

ReactiveProperty v0.4.0-rc1をリリースしました

$
0
0

id:neueccさんから、nugetのownerに追加してもらったのでReactiveProperty-PCLとして公開していたパッケージをReactivePropertyのほうに載せました。それに伴い、ReactiveProperty-PCLは検索対象から消しました。

ソースコードの中身は、ReactiveProperty-PCLから変えてませんがnugetのパッケージの設定を変えたので色々ちゃんと参照設定に追加されたりするようになってます。

Windows 8.1だけBehavior SDK(XAML)を手動で追加しないといけないのはnugetが、現状対応してないからになります…

PCL化にあたってのメリット

PCL化したので、大部分をPortable Class Libraryで作成することができます。 ターゲットフレームワークの.NET 4.5, WP8, Windows store app(Windows 8.1)の中で、WP8だけDataAnnotationsが使えないので、その部分だけコードの共有が無理です。

CodePlexのサイトのサンプルソースの中もViewModel系のクラスはPCLで作成して、XAMLとコードビハインドだけ個々のプラットフォームで作成しています。

下位互換は捨てた・・・

PCL化にあたって下位互換は結構ばっさり切りました…。 PullRequestで来ていたPCL化のほうは、下位互換を、維持したままPCL化したり対象プラットフォームも過去のものを踏襲したりとかなり力が入っていたのですが、ソースコードが一本でもプラットフォームの数だけソースをリンクしたプロジェクトを維持していくことを考えると、もう何年も更新されてないものなので、ここらで大掃除といった感じでPCLに寄せれるところは寄せてしまおうというポリシーで今の状態になっています。

残タスク

リリースまでに残っているタスク。

  • UnitTestプロジェクトを修正
    • Molesベースで作られているのでFakesベースに作りなおす
  • Windows ストア アプリのサンプルの作成
  • PCLのターゲットフレームワークをひろげれないか確認(少なくとも.NET4から使いたい…)
  • CodePlexのページの内容の修正
    • 今回の修正内容を反映させた状態で書き直す。

結構重たい気がする。がxin9leさんもいるし大丈夫でしょう。


マルチカラム&縦スクロールのアプリを作ってみよう

$
0
0

初期のころのWindows ストアアプリでは御法度のように書いてあった横スクロールと縦スクロールによる横スクロールと縦スクロールが混在したアプリケーションですが、最近は、結構審査もゆる~くなったみたいなので、操作しにくくなかったらよさそうなのかなぁという雰囲気になってきてたりします。Facebookアプリとか、ストアアプリらしい???と疑問に思ったけれど、下のページで取り上げられたりしてるくらいだし。

ということで、縦スクロールと横スクロール混ぜるな危険なUIを作る方法を書いてみようと思います。ゴールのイメージとしてはマルチカラムのTwitterクライアントみたいになる感じです。

複数カラムを扱うようなデータ構造のクラスを定義する

まずは、画面に表示するためには裏でデータとっとかないといけないですね。ということで、クラスを定義します。ついでなんで、ReactivePropertyとMVVM LightのPCL版をNuGetからとってきて、それを使って作りました。最初にアプリのデータの最小単位。1つの項目を表すデータの入れ物です。これは、プロパティの中身が変わることを前提としてないので、単純なクラスにしました。

/// <summary>/// データの入れ物/// </summary>publicclass AppItem
{
    /// <summary>/// テキスト/// </summary>publicstring Text { get; set; }
}

コメント要らない感じがしますが、まあいいでしょう。

次は、カラムを表すクラスです。単純に列の名前と、列内で管理してるAppItemのコレクションを持っています。コレクションに追加するAppItemの条件を指定できるようにFunc<AppItem, bool>型のプロパティも追加してます。

/// <summary>/// 列を表すクラス。/// </summary>publicclass AppColumn : ObservableObject
{
    privatestring columnName;

    /// <summary>/// 列の名前/// </summary>publicstring ColumnName
    {
        get { returnthis.columnName; }
        set { this.Set(refthis.columnName, value); }
    }

    /// <summary>/// 列に入ってるデータ/// </summary>private ObservableCollection<AppItem> columnItems = new ObservableCollection<AppItem>();

    /// <summary>/// 列に入ってるデータの外部公開用プロパティ(読み取り専用)/// </summary>public ReadOnlyObservableCollection<AppItem> ColumnItems { get; private set; }

    /// <summary>/// この列で受け入れるAppItemの条件/// </summary>public Func<AppItem, bool> AcceptItemCondition { get; set; }

    public AppColumn()
    {
        this.ColumnItems = new ReadOnlyObservableCollection<AppItem>(this.columnItems);
    }

    /// <summary>/// AcceptItemConditionで指定した条件にマッチするAppItemの場合追加する。/// </summary>/// <paramname="item"></param>publicvoid AddItem(AppItem item)
    {
        if ((this.AcceptItemCondition ?? (_ => true))(item))
        {
            this.columnItems.Insert(0, item);
        }
    }

    /// <summary>/// 引数で渡されたAppItemのコレクションで列の中身をリセットする。/// </summary>/// <paramname="items"></param>publicvoid ResetItems(IEnumerable<AppItem> items)
    {
        this.columnItems.Clear();
        foreach (var item in items)
        {
            this.AddItem(item);
        }
    }
}

最後にModelのクラスで全体管理をする人を作ります。色々やってるけど、列の管理や、0.1秒に1つAppItemを適当につくってコレクションに突っ込んだり、突っ込まれたAppItemを管理してる列に通知してます。System.Threading.Timerを使ってバックグラウンドでAppItemをコレクションに追加してるのが怖いので、コレクションの変更通知を受けて行う処理は全て同一のSynchronizationContextを経由してやるようにしました。これで十分に安全なのかはちょっと自信ない。

/// アプリ全体を表すクラス/// </summary>publicclass MyAppContext
{
    /// <summary>/// アプリの唯一のインスタンス/// </summary>publicstaticreadonly MyAppContext Instance = new MyAppContext();

    /// <summary>/// コレクションを操作するときに使用するSynchronizationContext/// </summary>private SynchronizationContext context = new SynchronizationContext();

    /// <summary>/// AppItemを追加するタイマー/// </summary>private Timer timer;

    /// <summary>/// アプリで管理する全AppItemを保持するコレクション/// </summary>private ObservableCollection<AppItem> Items { get; set; }

    /// <summary>/// アプリのカラムのコレクション/// </summary>public ObservableCollection<AppColumn> Columns { get; private set; }

    public MyAppContext()
    {
        // コレクションの初期化this.Items = new ObservableCollection<AppItem>();
        this.Columns = new ObservableCollection<AppColumn>();

        // ItemsにAppItemが追加されたら、全カラムに通知する。this.Items.ObserveAddChanged()
            .ObserveOn(this.context)
            .Subscribe(this.NotifyAddAppItem);

        // Itemsに大きな変更があったら、全カラムもリセットするthis.Items.ObserveResetChanged()
            .ObserveOn(this.context)
            .Subscribe(_ => this.ResetColumnsItems());

        // カラムが追加されたら、追加されたカラムを初期化するthis.Columns.ObserveAddChanged()
            .ObserveOn(context)
            .Subscribe(c => c.ResetItems(this.Items));
    }

    /// <summary>/// アプリで管理してるカラムのデータを全てリセットする。/// </summary>privatevoid ResetColumnsItems()
    {
        foreach (var column inthis.Columns)
        {
            column.ResetItems(this.Items.AsEnumerable());
        }
    }

    /// <summary>/// AppItemが追加されたことを全カラムに通知する。/// </summary>/// <paramname="item"></param>privatevoid NotifyAddAppItem(AppItem item)
    {
        foreach (var column inthis.Columns)
        {
            column.AddItem(item);
        }
    }

    /// <summary>/// AppItemの追加処理の開始/// </summary>publicvoid Start()
    {
        // 二重呼び出しはとりあえずアプリ殺す感じでif (timer != null)
        {
            thrownew InvalidOperationException("既に開始しています");
        }

        // 0.1秒に1つのAppItemを追加する
        var r = new Random();
        this.timer = new Timer(_ => 
            {
                // 140文字くらいのテキストのAppItemを追加する
                var appItem = new AppItem
                {
                    Text = Enumerable
                        .Repeat("a", r.Next(140) + 1)
                        .Aggregate((x, y) => x + y)
                };
                this.Items.Insert(0, appItem);
            }, 
            null, 
            0, 
            100);
    }

    /// <summary>/// 列を追加する/// </summary>/// <paramname="name"></param>/// <paramname="predicate"></param>publicvoid AddColumn(string name, Func<AppItem, bool> predicate)
    {
        this.Columns.Add(new AppColumn 
        {
            ColumnName = name,
            AcceptItemCondition = predicate
        });
    }
}

ViewModelを定義

お次は、AppColumnをラップする感じのColumnViewModelクラスです。こいつは単純にAppColumnを薄くラップしつつ、コレクションの操作を全てUIスレッド上でやるように専念してるだけです。

/// <summary>/// AppColumnのViewModel/// </summary>publicclass ColumnViewModel
{
    /// <summary>/// 紐づくAppColumn/// </summary>public ReactiveProperty<AppColumn> Model { get; private set; }

    /// <summary>/// 画面に公開するAppItemのコレクション/// </summary>public ObservableCollection<AppItem> Items { get; private set; }

    public ColumnViewModel()
    {
        this.InitializeReactiveProperty();
    }

    privatevoid InitializeReactiveProperty()
    {
        // とりあえずプロパティの初期化this.Items = new ObservableCollection<AppItem>();
        this.Model = new ReactiveProperty<AppColumn>();

        // Modelに値が設定されたら、そのModelで管理してるAppItemをコピーする。this.Model
            .Where(c => c != null)
            .ObserveOnUIDispatcher()
            .Subscribe(c =>
            {
                this.Items.Clear();
                foreach (var i in c.ColumnItems)
                {
                    this.Items.Add(i);
                }
            });

        // Modelに値が追加されたら、Itemsプロパティにも要素を追加する。this.Model.Where(c => c != null)
            .Select(c => c.ColumnItems)
            .SelectMany(items => items.CollectionChangedAsObservable()
                .Where(e => e.Action == NotifyCollectionChangedAction.Add)
                .Select(e => new { Item = (AppItem)e.NewItems[0], Index = e.NewStartingIndex }))
            .ObserveOnUIDispatcher()
            .Subscribe(i => this.Items.Insert(i.Index, i.Item));
    }
}

最後に、MainPageに対応するViewModelを作ります。列の追加や、AppItem生成の開始に対応するコマンドを持つのと、列が追加されたときに対応するColumnViewModelを作ってます。

/// <summary>/// MyAppContextの薄いラッパ/// </summary>publicclass MainPageViewModel
{
    /// <summary>/// ラップするMyAppContext/// </summary>private MyAppContext context;

    /// <summary>/// 画面に公開する列のコレクション/// </summary>public ReactiveCollection<ColumnViewModel> Columns { get; private set; }

    /// <summary>/// AppItem生成処理開始コマンド/// </summary>public ReactiveCommand StartCommand { get; private set; }

    /// <summary>/// 列追加コマンド/// </summary>public ReactiveCommand AddColumnCommand { get; private set; }

    /// <summary>/// 追加する列名/// </summary>
    [Required]
    public ReactiveProperty<string> ColumnName { get; private set; }

    /// <summary>/// 追加する列が受け入れる条件(テキストの長さを10で割った余り)/// </summary>
    [Required]
    [Range(0, 9)]
    public ReactiveProperty<string> ColumnTextLength { get; private set; }

    public MainPageViewModel() : this(MyAppContext.Instance)
    {
    }

    public MainPageViewModel(MyAppContext context)
    {
        this.context = context;
        this.InitializeReactiveProperty();
    }

    privatevoid InitializeReactiveProperty()
    {
        // 入力用プロパティ。値の検証もするのでSetValidateAttributeをよんどくthis.ColumnName = new ReactiveProperty<string>()
            .SetValidateAttribute(() => ColumnName);
        this.ColumnTextLength = new ReactiveProperty<string>()
            .SetValidateAttribute(() => ColumnTextLength);

        // 列追加コマンドの初期化。// 入力項目全てのエラーが無くなったら実行出来るようにする。this.AddColumnCommand = Observable.CombineLatest(
            this.ColumnName.ObserveErrorChanged,
            this.ColumnTextLength.ObserveErrorChanged)
            .Select(l => l.All(o => o == null))
            .ToReactiveCommand(false);
        // 列を追加this.AddColumnCommand.Subscribe(_ =>
        {
            var value = int.Parse(this.ColumnTextLength.Value);
            this.context.AddColumn(
                this.ColumnName.Value,
                // テキストの長さを10で割った余りが入力値と等しかったらOK
                item => item.Text.Length % 10 == value);
        });

        // 列が追加されたら、対応するColumnViewModelを作るthis.Columns = this.context.Columns
            .ObserveAddChanged()
            .Select(c =>
            {
                var m = new ColumnViewModel();
                m.Model.Value = c;
                return m;
            })
            .ObserveOnUIDispatcher()
            .ToReactiveCollection();

        // AppItem生成処理開始コマンド作成this.StartCommand = new ReactiveCommand();
        this.StartCommand.Subscribe(_ => this.context.Start());
    }
}

画面作成

さて、やっと本題の画面ですが、こいつはGridViewの中にListViewを何個も横並びにするというカオスな感じで作ります。画面のDataContextには、先ほど定義したMainPageViewModelクラスを設定します。

<Page.DataContext><vm:MainPageViewModel /></Page.DataContext>

GridViewと関連するDataTempalteなどの定義は以下のとおり。ポイントは、ListViewのIsSwipeEnabledをFalseして、GridViewの横スクロール時にListViewを横にスワイプしてもイベントを横取りしないようにしている点くらいです。

... データテンプレートの定義 ...
<Page.Resources><!-- GridViewの中身は横並び --><ItemsPanelTemplate x:Key="ColumnsGridViewItemPanelTemplate"><VirtualizingStackPanel Orientation="Horizontal"/></ItemsPanelTemplate><!-- AppItemを表示するためのテンプレート --><DataTemplate x:Key="AppItemDataTemplate"><Grid d:DesignWidth="509.552"d:DesignHeight="97.612"><TextBlock TextWrapping="Wrap"Text="{Binding Text}"Style="{StaticResource BodyTextBlockStyle}"Margin="5"/></Grid></DataTemplate><!-- 列を表示するためのテンプレート --><DataTemplate x:Key="ColumnViewModelDataTemplate"><Grid d:DesignWidth="346.866"d:DesignHeight="343.881"><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="10" /><RowDefinition/></Grid.RowDefinitions><!-- ヘッダに列名を表示して、その下にListViewでAppItemを表示する。 --><TextBlock TextWrapping="Wrap"Text="{Binding Model.Value.ColumnName}"Style="{StaticResource TitleTextBlockStyle}"/><ListView ItemsSource="{Binding Items}"Grid.Row="2"ItemTemplate="{StaticResource AppItemDataTemplate}"IsSwipeEnabled="False"></ListView></Grid></DataTemplate></Page.Resources>

... GridViewの定義...
<GridView Grid.Row="1"ItemsSource="{Binding Columns}"Margin="0,20,0,0"Padding="120,0,0,0"ItemsPanel="{StaticResource ColumnsGridViewItemPanelTemplate}"ItemTemplate="{StaticResource ColumnViewModelDataTemplate}"SelectionMode="None"><GridView.ItemContainerStyle><Style TargetType="GridViewItem"><Setter Property="VerticalContentAlignment"Value="Top" /><Setter Property="Margin"Value="0,0,80,0" /><Setter Property="Width"Value="400" /></Style></GridView.ItemContainerStyle></GridView>

実行してみよう

さて、なんとなく出来た気がするので実行して動きをみてみます。実行直後は、何も出てません。

f:id:okazuki:20131223022450p:plain

テキストボックスにカラムの名前と、数字を入れるとカラムの追加ボタンが押せるようになるので、5個くらい追加してみました。既に、横スクロールが必要なくらいの幅になってますね。使い勝手を見るのには十分です。

f:id:okazuki:20131223022643p:plain

開始ボタンを押すと、どんどんデータが追加されていきます。データがある程度増えた状態でも、指で横スクロール、縦スクロール、アイテムの選択をやるのは慣れればなんとかなりそうでしたが…

f:id:okazuki:20131223023323p:plain

マウスで操作をしようとすると、ホイールスクロールで横スクロールしようとするとListViewにカーソルがさしかかった時点でListViewに横取りされて縦スクロールになっちゃう・・・。これはアカン。使いにくい。

結論

これは、ダメ絶対。

現実的なところとしては、横スクロールを無くして、1画面に収まりきる範囲内でいくつかカラム出す。ユーザーがWindowのサイズを変えて狭くしたら出すカラムの数を減らすという考えれば、当然な作りにするのがいいかな~と思いました。

一応ダウンロード

使いどころはないと思いますが、コードは以下からDL出来ます。使ってみて、使い勝手最悪なのを体験していただければ幸いです。こんなUIのアプリが出ないように・・・!!

マルチカラムのアプリをストアアプリで

$
0
0

先日の酷いアプリはあんまりだったので、じゃぁ横スクロールしないように縦スクロールをいくつも配置すればいいんじゃね?ってことで配置してみました。

今回は、そこまでする必要があったのかというのは謎ですが、カスタムコントロールに配置処理はカプセル化してます。というかカスタムコントロールを1つこさえてXAMLを差し替えるだけで見た目を変えれるってすごくいいですね。

びば見た目の分離。

f:id:okazuki:20131224235818p:plain

ソースコードはコードレシピに置いておきました。

キャメルケースやアッパーキャメルケースの文字列を_区切りの文字列にする

$
0
0

小ネタですが。

var name = "PropertyName";
var output = name.Zip((name + " ").Skip(1), Tuple.Create)
    .Aggregate("", (x, y) => char.IsLower(y.Item1) && char.IsUpper(y.Item2) ?
            x + char.ToLower(y.Item1) + "_" :
            x + char.ToLower(y.Item1));
Console.WriteLine(output); // property_name

正規表現でさくっと置換する方法もありますが、まぁなんとなくLINQで。定番の方法とかってどんなんなんだろうか。

.NETでSQLite使うときの接続情報

$
0
0

System.Data.SQLiteをNuGetで入れた後に以下のような構成を追加する。こうするとDbProviderFactories経由で諸々生成できてハッピー。

<?xml version="1.0" encoding="utf-8"?><configuration><connectionStrings><add name="sqlite"connectionString="Data Source=:memory:;"providerName="System.Data.SQLite"/></connectionStrings><system.data><DbProviderFactories><remove invariant="System.Data.SQLite"/><add name="SQLite Data Provider"invariant="System.Data.SQLite"description=".Net Framework Data Provider for SQLite"type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" /></DbProviderFactories></system.data></configuration>

connectionStringは、インメモリDBの時の例。ファイルを使う場合は、以下のサイトあたりを参考に。

SQLite connection strings - ConnectionStrings.com

MVAというサイトの紹介

$
0
0

Microsoft Virtual Acdemyっていうサイトを最近知りました。

Microsoft Virtual Academy – Free IT Training, Online Learning of Microsoft Technologies

どうも、無料でオンラインセミナーを好きな時に受けれるサイトみたいです。空き時間とかを使って気になるテクノロジの概要をつかむのにいいかも。個人的には、以下のコースがいいな~と思いました。

最新 .NET 技術によるアプリケーション開発入門 - Microsoft Virtual Academy

このコースのクライアントアプリケーション開発入門が個人的に興味をそそられました。 というかMSのエバンジェリストさんたちが使ってるスライドで見たことある絵がちらほら。Windows RuntimeのAPIで8.1で強化された点とかのスライドもあって気になった時に自分で見ようと思いメモメモ。

キャメルケースやアッパーキャメルケースの文字列を_区切りの文字列にする その2(追記)

$
0
0

LINQ使って無理やりやったり、別解がコメントについたりしましたが、とりあえ素直?に正規表現でやってみようと思いました。

キャメルケースやアッパーキャメルケースの文字列を_区切りの文字列にする - かずきのBlog@hatena

var name = "SamplePropertyName";
var result = Regex.Replace(
    name,
    "[a-z][A-Z]",
    m => m.Value.Insert(1, "_"))
    .ToLower();
Console.WriteLine(result); // sample_property_name

う~ん、これでいいのかな・・・?

追記

Facebookのコメントで後方参照を使った方法を教えてもらいました。これが一番よさそう。

var name = "SamplePropertyName";
var result = Regex.Replace(
    name,
    "([a-z])([A-Z])",
    "$1_$2")
    .ToLower();
Console.WriteLine(result); // sample_property_name

ReactiveProperty v0.4.0リリースしました

$
0
0

CodePlexにリリースのページ作りました。ついでにReactivePropertyのnugetパッケージを-Preが必要な状態から普通にインストールできる状態にしました。

前回のリリースからの違いは正直ありません。ReactivePropertyのプロジェクトとしては、無効化していた単体テストのプロジェクトを有効化して、テストが通るようにしたくらいです。因みにid:neueccさんがリリースしてたときと比べて以下の機能ダウン(!?)があります。

主な違い

  • Codeplex.Reactive.Asynchronous名前空間削除
    • WebClientやStreamをIObservableに変換するヘルパーメソッドが定義されていましたが、今の時代async awaitもあるし不要と判断して削除しました。必要な人は過去のコードから抜いてきてビルドするといいかなと思います。
  • ReactivePropertyクラスのSetValidateErrorメソッドとForceValidateメソッドとErrorプロパティの削除
    • IDataErrorInfoがPCLで使えなかったので、それに付随するメソッドを削除しました。これからはエラーの通知はINotifyErrorInfoいっぽんに統一されたということで、シンプルにね。
  • INotifyPropertyChangingのObservePropertyChangingメソッドの削除
    • INotifyPropertyChangingインターフェースがPCLで使えなかったのでこれも削除です。
  • UIDispatcherHelperの内部をDispatcherからSynchronizationContextに変更
    • DispatcherがPCLで(以下略

ASP.NET WebAPIでAPIを作ってJavaScriptから呼ぶまで

$
0
0

ということで、タイトル通りのことをしてみようと思います。空のASP.NETのプロジェクトからいろいろ足していく形でやろうと思います。

プロジェクトの作成とAPIの作成

まず、空のASP.NETのプロジェクトを作ります。

f:id:okazuki:20140102152258p:plain

ASP.NET WebAPIに必要なアセンブリを追加します。特に縛りがない限りは最新を利用したほうがいいのでNuGetからMicrosoft ASP.NET Web API 2 Web Hostをインストールします。ぱっと見た感じ以下のアセンブリが参照に追加されました。

  • Nwetonsoft.Json
  • System.Net.Http
  • System.Net.Http.Formatting
  • System.Web.Http
  • System.Web.Http.WebHost

次にプロジェクトに以下のものを追加します。

  • プロジェクトの右クリックメニューから「追加」→「フォルダ」を選びControllersという名前のフォルダを作成
  • Controllerフォルダの右クリックメニューから「追加」→「クラス」でHelloWorldControllerという名前のクラスを作成

WebAPIのコントローラはApiControllerクラスを継承しないといけないので継承します。

ここまでのコードを以下に示します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http; // こいつの追加を忘れずにnamespace HelloWebApi.Controllers
{
    publicclass HelloWorldController : ApiController
    {

    }
}

次に、コントローラを作っただけでは呼ばれないのでAPIが呼ばれるようにルーティングの設定を行います。Global.asax(グローバル アプリケーション クラス)を作成して、Application_Startメソッドに以下の定義を追加します。

using System.Web.Http; // これを頭に追加protectedvoid Application_Start(object sender, EventArgs e)
{
    GlobalConfiguration.Configure(config =>
        {
            config.Routes.MapHttpRoute(
                name:"DefaultApi",
                routeTemplate:"api/{controller}/{id}",
                defaults:new { id = RouteParameter.Optional });
        });
}

routeTemplateで使用している{controller}や{id}は予約語みたいなもので、コントローラ名やURLに含まれるリソースを一意に識別するためのidを指定するためのプレースホルダーみたいなものだと思っておけばOKです。自分でいろいろ細かくカスタマイズすることもできます。詳しくは味噌先生の以下の記事を参照してみてください。

プロジェクトの新規作成のときにWebAPIを使うようにしている場合は、参照設定や、ルートの設定はすでにされています。Application_StartメソッドからWebApiConfigクラスのメソッドが呼ばれていて、そのなかで上記のようなコードが記載されているはずなので確認してみてください。

APIを作ってブラウザから叩いてみよう

APIのControllerクラスは作りましたが、中身がからっぽなので、簡単に以下のような記述を足してGETメソッドに対応するものを作ります。

// /api/HelloWorldpublicstring Get()
{
    return"Hello world";
}

実行してブラウザのURLでhttp://localhost:ポート番号/api/HelloWorldというURLにアクセスすると以下のようなJSONファイルをダウンロードできるはずです。

"Hello world"

では、ブラウザから叩いてみます。生のJavaScriptから叩くのは正直つらいのでNuGetからjQueryをインストールします。プロジェクトにDefualt.htmlという名前のHTMLファイルを作成して、以下のように書きます。jQueryのajaxメソッドで単純にHelloWorldのAPIを叩いて結果をspanの中に流し込んでいます。

<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml"><head><metahttp-equiv="Content-Type"content="text/html; charset=utf-8"/><title></title></head><body><h1>Hello WebAPI</h1><div><!-- ボタンを押すとAPIを呼ぶ --><buttononclick="callHelloWorld()">GET /api/HelloWorld</button><br/><spanid="helloWorldResultPlaceHolder">ここにHelloWorldAPIの結果が入ります</span></div><scriptsrc="Scripts/jquery-2.0.3.min.js"></script><scripttype="text/javascript">function callHelloWorld(){// jQueryを使って呼ぶだけ            $.ajax("/api/HelloWorld").then(function(r){                $("#helloWorldResultPlaceHolder").text(r);});}</script></body></html>

実行すると、以下のような画面が表示されます。

f:id:okazuki:20140102163620p:plain

ボタンを押してしばらくすると、APIから返された文字列が画面に表示されます。

f:id:okazuki:20140102163722p:plain

POSTのメソッドも作ってみよう

次にPOSTのメソッドを受け取るAPIを作ってみます。こいつは、フォームのテキストボックスに入力した内容を送って、それにHello worldをくっつけて返すものにします。POSTで単純に文字列の引数を受け取るだけの方法がちょっとわからなかったので妥協してオブジェクトを受け取るようにしました・・・。

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;

namespace HelloWebApi.Controllers
{
    publicclass HelloWorldController : ApiController
    {
        // /api/HelloWorldpublicstring Get()
        {
            return"Hello world";
        }

        // POST /api/HelloWorldpublicstring Post(Data data)
        {
            return data.Name + " Hello world";
        }
    }

    /// <summary>/// JSONのデータの入れ物/// </summary>publicclass Data
    {
        publicstring Name { get; set; }
    }
}

JavaScriptの呼び出し側は以下の通り。

<div><inputtype="text"id="inputText" /><buttononclick="callPostMethod()">POST /api/HelloWorld</button><br /><spanid="helloWorldPostResultPlaceHolder">ここにHelloWorldAPI(POST)の結果が入ります</span></div><scripttype="text/javascript">function callPostMethod(){var data = {};        data.name = $("#inputText").val();// jQueryを使って呼ぶだけ        $.ajax("/api/HelloWorld", { type: "POST", data: data }).then(function(r){            $("#helloWorldPostResultPlaceHolder").text(r);}, function(e){alert("error: " + e);});}</script>

ちなみに、戻り値もオブジェクトの形にしたい場合は以下のようにします。JavaScriptからアクセスしたときに不自然な名前にならないように、JsonProperty属性をつけて小文字はじまりの名前を指定してあげるとよさげ。

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;

namespace HelloWebApi.Controllers
{
    publicclass HelloWorldController : ApiController
    {
        // /api/HelloWorldpublicstring Get()
        {
            return"Hello world";
        }

        // POST /api/HelloWorldpublic Data Post(Data data)
        {
            returnnew Data { Name = data.Name + " Hello world" };
        }
    }

    /// <summary>/// JSONのデータの入れ物/// </summary>publicclass Data
    {
        [JsonProperty(PropertyName = "name")]
        publicstring Name { get; set; }
    }
}

JavaScriptは以下のようになります。

function callPostMethod() {
    var data = {};
    data.name = $("#inputText").val();
    // jQueryを使って呼ぶだけ
    $.ajax("/api/HelloWorld", { type: "POST", data: data }).then(function (r) {
        $("#helloWorldPostResultPlaceHolder").text(r.name);
    }, function (e) {
        alert("error: " + e);
    });
}

まとめ

Postメソッドをstring Post(string name)みたいにしてJSから呼ぶ方法はどうやるんだろう。[FromBody]とかつけてみたりしたけどあかんかった。

Windows ストアアプリでページの共通の見た目を部品化したい…

$
0
0

すべてのページには、タイトルがあって戻るボタンがあってコンテンツがある。なので、Windowsストアアプリの基本ページとかを作ると自動的に、そこらへんのUIがくっついたものを作成してくれます。

それでも、いいっちゃいいんですが、自分たちでは、こういったヘッダー要素が必要なんです・・・!とかいう要望があった場合どういうふうにして共通化できるかどうか。考えてみました。今回はテンプレートコントロールを作ってみる方法はどうだろう?というのを考えたのでメモっておきます。コードは後日。

テンプレートコントロール

テンプレートコントロールを新規作成すると、Controlを継承したクラスが作られるので、これをContentControlを継承するようにします。

Generic.xamlにあるControlTemplateで、共通の見た目となる部分を組みます。ページによって表示文字列を変えたい箇所は、Bindingを記述しておいて、該当プロパティをもったクラスがDataContextに設定されたときに、表示されるようにしておくとかでいいと思います。

戻るボタンは、NavigationHelperのGoBackCommandとバインドできるようにコントロールにICommand型のプロパティを追加。ControlTemplate内の戻るボタンのCommandプロパティとバインドすることで、いい感じになってくれると思う。

あとは、全ページで共通化したい見た目をぺたぺたと定義しておく。

使うときは、基本ページを作成したら、作ったテンプレートコントロールを全体に広げて置けばOK。カスタムコントロールの中にGridを置いて、各ページのコンテンツを作りこんでいくと個別部分のみに注力できそう。

ほんとうは、マスターページみたいな機能があればいいんだけどなぁと思いつつ、最近かんがえていたことでした。

Streamのコピー

Windows ストア アプリでページ間で共通の見た目を簡単に作りたい

$
0
0

昼間に書き殴ったあれですが、コードに落としてみます。

Windows ストアアプリでページの共通の見た目を部品化したい… - かずきのBlog@hatena

プロジェクトの作成

とりあえず、空のアプリケーションテンプレートを作成。したあとMainPage.xamlを消して、基本ページをMainPageという名前で作成します。CommonフォルダにNavigationHelperとかを使うか聞いてくるので、さくっとOKして作りましょう。

普通は、基本ページかなんらかのページをベースにして見た目を作っていくのですが、ページ間で共通の見た目やちょっとした挙動を作るのってめんどくさいですよね。一応基本ページには、同じような見た目になるようにXAMLは吐かれてるけど、そのままってわけにもいきませんしね。

テンプレートコントロール作成

本当はページのTemplateを差し替えてってやろうと思ったんですがPageはUserControlなのでTemplate差し替えれないって怒られたのでテンプレートコントロールを作りました。名前はとりあえずPageTemplateという名前で。

デフォルトでは、Controlを継承しているので、これをContentControlに変えます。

publicsealedclass PageTemplate : ContentControl
{
    public PageTemplate()
    {
        this.DefaultStyleKey = typeof(PageTemplate);
    }
}

次にThemesフォルダにできてるGeneric.xamlにあるStyleをいじります。Styleの中でControlTemplateが定義されているので、ここに共通の見た目を定義します。とりあえずMainPage.xamlのコードをこぴってちょっとだけカスタマイズします。

必要最低限でいくと、AppNameというStaticResourceがみつからないというエラーが出るので、これは{Binding AppName}のように、DataContextからもらうようにします。次に、Grid.Row="1"の箇所にContentPresenterを置きます。

<ControlTemplate TargetType="local:PageTemplate"><Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><Grid.ChildrenTransitions><TransitionCollection><EntranceThemeTransition/></TransitionCollection></Grid.ChildrenTransitions><Grid.RowDefinitions><RowDefinition Height="140"/><RowDefinition Height="*"/></Grid.RowDefinitions><!-- Back button and page title --><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="120"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions><Button x:Name="backButton"Margin="39,59,39,0"Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"Style="{StaticResource NavigationBackButtonNormalStyle}"VerticalAlignment="Top"AutomationProperties.Name="Back"AutomationProperties.AutomationId="BackButton"AutomationProperties.ItemType="Navigation Button"/><TextBlock x:Name="pageTitle"Text="{Binding AppName}"Style="{StaticResource HeaderTextBlockStyle}"Grid.Column="1"IsHitTestVisible="false"TextWrapping="NoWrap"VerticalAlignment="Bottom"Margin="0,0,30,40"/></Grid><!-- Content is here !! --><ContentPresenter Grid.Row="1" /></Grid></ControlTemplate>

ビルドしてページに置いてみます。

<Pagex:Name="pageRoot"x:Class="TemplateApp.MainPage"DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="using:TemplateApp"xmlns:common="using:TemplateApp.Common"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"mc:Ignorable="d"><local:PageTemplate /></Page>

ページタイトルが表示されないので、とりあえずコードからDefaultViewModelにAppNameを設定するようにします。

// MainPage.xaml.csprivatevoid navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
    this.DefaultViewModel["AppName"] = "My sample app";
}

これで、とりあえずそれっぽく動くようになりました。

f:id:okazuki:20140109001514p:plain

ツールボックスからドキュメントアウトラインのPageTemplateに対してGridをぽとりと落としてレイアウトをリセットすると、あとは普通に画面にコントロールをぽとぺたできます。

f:id:okazuki:20140109001825p:plain

XAMLはこんな感じ。

<Pagex:Name="pageRoot"x:Class="TemplateApp.MainPage"DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="using:TemplateApp"xmlns:common="using:TemplateApp.Common"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"mc:Ignorable="d"><local:PageTemplate ><Grid><Button Content="Button"HorizontalAlignment="Left"Margin="254,106,0,0"VerticalAlignment="Top"/><Button Content="Button"HorizontalAlignment="Left"Margin="331,267,0,0"VerticalAlignment="Top"/><Image HorizontalAlignment="Left"Height="100"Margin="522,87,0,0"VerticalAlignment="Top"Width="100"Source="Assets/Logo.png"/></Grid></local:PageTemplate></Page>

基本ページを、NextPageという名前で作って同じようにPageTemplateを置いて、DefaultViewModelのAppNameにNextPageという文字列を設定したものを作ります。下の図は、適当にTimePickerを置いてみたのですが、コンテンツ部分以外は、MainPageと同じ見た目をさくっと作れます。

f:id:okazuki:20140109002228p:plain

MainPageに適当に置いたボタンをダブルクリックすると普通にコードビハインドにイベントハンドラが作成されることが確認できるので、そこでNextPageへ画面遷移する処理を書いてみました。

privatevoid Button_Click(object sender, RoutedEventArgs e)
{
    this.Frame.Navigate(typeof(NextPage));
}

実行してみると、とりあえずちゃんと動いてるっぽい。

f:id:okazuki:20140109003014p:plain

f:id:okazuki:20140109003214p:plain

MainPageでは戻るボタンが出ずに、NextPageでは出てるあたりちゃんと動いてる感がします。とまぁ昼間思いついたのは以上でした。

あとは、アプリバーとかも共通なものは共通化したいなぁ。あ、あとはシンプルに、ページの中にFrameを置いて、そこの中で画面遷移するってのでもよさそうだけど、既存のNavigationHelperとかとうまいこと連携できるのかは謎。

Windows ストアアプリのページ間で共通の見た目を作りたい その3

$
0
0

先日は、軽く作っただけですが、ちょっと改良してみようと思います。

Windows ストア アプリでページ間で共通の見た目を簡単に作りたい - かずきのBlog@hatena

ページのタイトルをPageTemplateコントロールのプロパティで設定できるようにする

昨日の段階ではXAML内で以下のようにDataContextにAppNameというプロパティがあるという前提でハードコーディングしていました。これだと、ちょっと自由度が低すぎるのでPageTemplateにPageTtleという名前のプロパティを用意して、それを通して設定できるようにしたいと思います。バインドできたほうがMVVM的にも自由がききそうなので依存関係プロパティとしてPageTemplateプロパティに定義します。

publicstring PageTitle
{
    get { return (string)GetValue(PageTitleProperty); }
    set { SetValue(PageTitleProperty, value); }
}

publicstaticreadonly DependencyProperty PageTitleProperty =
    DependencyProperty.Register("PageTitle", 
        typeof(string), 
        typeof(PageTemplate), 
        new PropertyMetadata(null));

そして、Generic.xamlのページタイトルのTextBlockを以下のように書き換えます。

<!-- 書き換え前 --><TextBlock x:Name="pageTitle"Text="{Binding AppName}"Style="{StaticResource HeaderTextBlockStyle}"Grid.Column="1"IsHitTestVisible="false"TextWrapping="NoWrap"VerticalAlignment="Bottom"Margin="0,0,30,40"/><!-- 書き換え後 --><TextBlock x:Name="pageTitle"Text="{TemplateBinding PageTitle}"Style="{StaticResource HeaderTextBlockStyle}"Grid.Column="1"IsHitTestVisible="false"TextWrapping="NoWrap"VerticalAlignment="Bottom"Margin="0,0,30,40"/>

こうすると、このPageTemplateコントロールを使う側で好きなようにXAMLから(またはPageTitleプロパティと何かをバインドすることでViewModelから)設定出来るようになります。

<Pagex:Name="pageRoot"x:Class="TemplateApp.MainPage"DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="using:TemplateApp"xmlns:common="using:TemplateApp.Common"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"mc:Ignorable="d"><local:PageTemplate PageTitle="XAMLから指定したタイトル"><Grid><!-- ここにコンテンツを置く --></Grid></local:PageTemplate></Page>

デザイナ上でも表示を確認できます。

f:id:okazuki:20140109212310p:plain

戻るボタンを押したときの挙動

もう1つコントロール内にハードコーディングされているものとして、戻るボタンのコマンドがあります。

<Button x:Name="backButton"Margin="39,59,39,0"Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"

これもPageTitleと同様に外部から指定できるようにするのがスマートでしょう。ということで、PageTemplateクラスにICommand型のGoBackCommandプロパティを作成します。

public ICommand GoBackCommand
{
    get { return (ICommand)GetValue(GoBackCommandProperty); }
    set { SetValue(GoBackCommandProperty, value); }
}

publicstaticreadonly DependencyProperty GoBackCommandProperty =
    DependencyProperty.Register("GoBackCommand", 
        typeof(ICommand), 
        typeof(PageTemplate), 
        new PropertyMetadata(null));

Generic.xamlの中で戻るボタンをTemplateBindingを使ってバインドするように修正します。

<Button x:Name="backButton"Margin="39,59,39,0"Command="{TemplateBinding GoBackCommand}"

これで、外部から戻るボタンの挙動を差し替えることができます。NavigationHelperクラスに依存しなくなりました。NavigationHelperクラスのGoBackCommandをページからBindingすれば、従来通りの動きをさせることも可能です。

<local:PageTemplate PageTitle="XAMLから指定したタイトル"GoBackCommand="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"><Grid><!-- ここにコンテンツを置く --></Grid></local:PageTemplate>

見た目をカスタマイズしたいのが一か所じゃない場合

今回の例では、タイトルから下のコンテンツ部分がページ固有のカスタマイズ領域ですが場合によっては、ヘッダーの一部の領域もページごとに固有のコンテンツを置きたいということもあると思います。そういうときは、以下のようにすればOKです。

PageTemplateクラスにカスタマイズコンテンツを格納するためのプロパティと、それの表示を定義するDataTemplate型のプロパティを定義してやります。今回はフッター部に一部カスタマイズ領域を設けるという感じで以下のような名前のプロパティを定義しました。

publicobject FooterContent
{
    get { return (object)GetValue(FooterContentProperty); }
    set { SetValue(FooterContentProperty, value); }
}

publicstaticreadonly DependencyProperty FooterContentProperty =
    DependencyProperty.Register("FooterContent", 
        typeof(object), 
        typeof(PageTemplate), 
        new PropertyMetadata(null));

public DataTemplate FooterContentTemplate
{
    get { return (DataTemplate)GetValue(FooterContentTemplateProperty); }
    set { SetValue(FooterContentTemplateProperty, value); }
}

publicstaticreadonly DependencyProperty FooterContentTemplateProperty =
    DependencyProperty.Register("FooterContentTemplate", 
        typeof(DataTemplate), 
        typeof(PageTemplate), 
        new PropertyMetadata(null));

Generic.xamlには、GridのRowを1行たして、そこにBorderあたりでくるんだContentControlを置いて上記で作成したプロパティをContentとContentTemplateにバインドします。

<ControlTemplate TargetType="local:PageTemplate"><Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><Grid.ChildrenTransitions><TransitionCollection><EntranceThemeTransition/></TransitionCollection></Grid.ChildrenTransitions><Grid.RowDefinitions><RowDefinition Height="140"/><RowDefinition Height="*"/><RowDefinition Height="Auto"/><!-- Footer --></Grid.RowDefinitions><!-- Back button and page title --><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="120"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions><Button x:Name="backButton"Margin="39,59,39,0"Command="{TemplateBinding GoBackCommand}"Style="{StaticResource NavigationBackButtonNormalStyle}"VerticalAlignment="Top"AutomationProperties.Name="Back"AutomationProperties.AutomationId="BackButton"AutomationProperties.ItemType="Navigation Button"/><TextBlock x:Name="pageTitle"Text="{TemplateBinding PageTitle}"Style="{StaticResource HeaderTextBlockStyle}"Grid.Column="1"IsHitTestVisible="false"TextWrapping="NoWrap"VerticalAlignment="Bottom"Margin="0,0,30,40"/></Grid><!-- Content is here !! --><ContentPresenter Grid.Row="1" /><!-- Footer area --><Border Grid.Row="2"Background="#FFBD4C4C"><ContentControl Content="{TemplateBinding FooterContent}"ContentTemplate="{TemplateBinding FooterContentTemplate}"HorizontalContentAlignment="Stretch"/></Border></Grid></ControlTemplate>

FooterContentプロパティに適当な値をつっこんだらそれがそのまま表示されますし、FooterContentTemplateをデザイナから編集して、ぽとぺたでカスタマイズすることもできます。

<local:PageTemplate PageTitle="XAMLから指定したタイトル"GoBackCommand="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"><local:PageTemplate.Resources><DataTemplate x:Key="DataTemplate1"><Border BorderBrush="Black"BorderThickness="1"><Grid><Button Content="Button"HorizontalAlignment="Stretch" /></Grid></Border></DataTemplate></local:PageTemplate.Resources><local:PageTemplate.FooterContentTemplate><StaticResource ResourceKey="DataTemplate1"/></local:PageTemplate.FooterContentTemplate><Grid><Button Content="Button"HorizontalAlignment="Left"Margin="115,33,0,0"VerticalAlignment="Top" /></Grid></local:PageTemplate>

f:id:okazuki:20140109220339p:plain

当然ですが実行してみてもデザイナでデザインした通りに画面の下にボタンが出ています。

f:id:okazuki:20140109220458p:plain

Windows 8.1用のPrism for Windows Runtime

$
0
0

最近ウォッチしてなかったので気づいてなかったのですが、Windows 8.1に対応したPrism for Windows Runtimeが出てたみたいです。

NuGetで入手可能。PubSubEventsのほうは、前からPCLだったのでWindows 8.1になっても特に機能に変わりはないみたいです。

最初から作るうえで一番簡単なのは、Visual Studio 2013用のプロジェクトテンプレートとかアイテムテンプレートを使うことなので、入れておくのが吉だと思います。

細かいところはおいおい見ていくとして、基本的にはWindows 8のころと変わってないみたいです。Flyoutとかが標準機能で入ってきたので、そこらへんはPrismで用意しなくてよくなったから消えてるくらいかな?

変わらないが故に嫌なところ

Prism for Windows Runtimeでページのテンプレートとか使って開発してる上では困らないのですが、今までPrism使ってないものにPrismを適用しようとするとページの基本クラスがVisualStateAwarePageに差し替えないといけなかったりして、地味にめんどくさいんですね。

ここらへんは、Windows 8のころのストアアプリのデフォルトのテンプレートがLayoutAwarePageを継承していたのが気に入らなかったものを、NavigationHelperクラスに委譲するかたちにして、ページの基本クラスはPageクラスにしてくれた点が、進歩した点だと思ってます。でも、なんかイマイチなんですけどね…。

基本クラスは出来るだけ縛られたくない…!!という思いから、さくっと作るだけ作ってみました。

PrismAdapter - Home

こういうとき、世界で一番使われてる言語が日本語だといいなと思うんです。書いてることに自信がない…。

使い方

普通のPageにNavigationHelperBehaviorとViewModelLocatorBehaviorをぽとっと落すと、そのページがVisualStateAwarePageを継承してViewModelLocator.AutoAwareViewModel="True"を設定したのと同じような動きをするようになります。

戻るボタンは、NavigationHelperBehaviorにGoBackCommandというプロパティがあるので、それとバインドすればOK。

<!-- pageにこんな風にビヘイビア追加して --><Interactivity:Interaction.Behaviors><PrismAdapter:NavigationHelperBehavior x:Name="navigationHelper"/><PrismAdapter:ViewModelLocatorBehavior /></Interactivity:Interaction.Behaviors><!-- 戻るボタンにバインド --><Button x:Name="backButton"Margin="39,59,39,0"Command="{Binding GoBackCommand, ElementName=navigationHelper}"Style="{StaticResource NavigationBackButtonNormalStyle}"VerticalAlignment="Top"AutomationProperties.Name="Back"AutomationProperties.AutomationId="BackButton"AutomationProperties.ItemType="Navigation Button"/>

あとはPrism for Windows Runtimeの使い方にあわせて使えばOKOK。ページの基本クラスも自分で好きに差し替えたかったら差し替えればいい感じになると思います。

XAMLのコードスニペットの例

$
0
0

Grid内にアイテムを置くときにGrid.RowやGrid.Columnを毎回設定するのってだるいですよね。ということで、そういうのがついたタグを書くのを支援してくれるコードスニペット

<?xml version="1.0" encoding="utf-8"?><CodeSnippet Format="1.0.0"xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"><Header><SnippetTypes><SnippetType>Expansion</SnippetType><SnippetType>SurroundsWith</SnippetType></SnippetTypes><Title>Grid内のタグ</Title><Author>okazuki</Author><Description>新しいタグ用の XAML スニペット</Description><HelpUrl></HelpUrl><Shortcut>gi</Shortcut></Header><Snippet><Declarations><Literal Editable="true"><ID>TagName</ID><ToolTip>タグ名</ToolTip><Default>TextBlock</Default><Function></Function></Literal><Literal Editable="true"><ID>Row</ID><ToolTip>行番号</ToolTip><Default>0</Default><Function></Function></Literal><Literal Editable="true"><ID>Col</ID><ToolTip>列番号</ToolTip><Default>0</Default><Function></Function></Literal></Declarations><Code Language="XAML"><![CDATA[<$TagName$ Grid.Row="$Row$" Grid.Column="$Col$">$selected$$end$</$TagName$>]]></Code></Snippet></CodeSnippet>

griditem.snippetとかいう名前のファイルに保存して、コードスニペットマネージャーからインポートすると使えます。


Prism for .NET4.5.1のbetaが出ました

$
0
0

Prism for .NET 4.5.1のbetaが出ました!!

patterns & practices: Prism - Download: Prism 4.2 for .NET 4.5.1

こちらに、Prism for .NET 4.5.1が今後どんな方向に進んでいくのか書かれています。

Plan for Prism for .NET 4.5.1 - Blaine Wastell - patterns & practices client architecture guidance - Site Home - MSDN Blogs

ざっくりというと、PCL化できるところはPCLにしてしまおうとういう点と、Prism for Windows Runtimeに寄せる感じでMVVMサポートが追加されるっぽいです。

PCLに入れるものは以下のような予定みたいです。

  • INotifyPropertyChangedの実装
  • ICommandの実装
  • Prism for Windows RuntimeのViewModelLocator
  • CompositePresentationEventがPrism.PubSubEventsへ(これは既にPrism for Windows Runtimeとともにリリース済み)

んで、WPF版の重複する機能はさくっと将来的には消すみたいです。今あるPrism for Windows Runtimeも将来的には重複する機能はPCLにもっていくみたいですし、恐らくWindows Phone版もこの流れにのるんじゃないんだろうか?と思います。そうなると、デスクトップ、電話、ストアアプリとかなりのぶぶんを共通化できるようになるんじゃないんだろうかという感じがしますね。

今はMvvmCrossに先をいかれてる感じですが、切磋琢磨してお互いに進歩して使いやすくなってほしいなぁと思う今日この頃でした。

ちなみに

CodePlexからDLできるソースは、QuickstartsにDemonstrate how to construct parent-child Views and ViewModels.とかいてあるのですごい期待してDLして解凍してみてみたらMainPageViewModelクラスが同梱され忘れてるみたいで悲しい気持ちになりました。

Prism for Windows Runtimeで役立ちそうなコードスニペット

$
0
0

ちょっと閃いた。

Prism for Windows Runtime(8.1)では、3つのVisualStateを定義しています。

  • DefaultLayout
    • 普通の横長画面
  • PortraitLayout
    • 縦長画面
  • MinimalLayout
    • 最小幅

これのVSMのひな形をコードスニペットにしておけば便利なんじゃないだろうかということで…。

<?xml version="1.0" encoding="utf-8"?><CodeSnippet Format="1.0.0"xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"><Header><SnippetTypes><SnippetType>Expansion</SnippetType></SnippetTypes><Title>PrismのVisualStateの定義</Title><Author>okazuki</Author><Description>PrismのVisualStateの定義のスニペット</Description><HelpUrl></HelpUrl><Shortcut>prismvsm</Shortcut></Header><Snippet><Code Language="XAML"><![CDATA[<VisualStateGroup x:Name="PrismVisualStateGroup"><VisualState x:Name="DefaultLayout"></VisualState><VisualState x:Name="PortraitLayout"></VisualState><VisualState x:Name="MinimalLayout"></VisualState></VisualStateGroup>]]></Code></Snippet></CodeSnippet>

というのをコードスニペットとして登録しておいた。まぁ無いよりはいいかな。

めとべや東京 3で発表してきました #めとべや東京

INotifyPropertyChangedのイベントを処理したい

$
0
0

みたいな記事をりんちゃさんに拾われてた。

今だとどうするかな~というと制約がなければReactive Extensionsかなぁ。例えば、こんなINotifyPropertyChangedの実装をしたクラスがあるとして

publicclass Person : INotifyPropertyChanged
{

    publicevent PropertyChangedEventHandler PropertyChanged;

    protectedvirtualvoid OnPropertyChanged(string propertyName)
    {
        var h = this.PropertyChanged;
        if (h != null)
        {
            h(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    protectedvirtualbool SetProperty<T>(ref T field, T value, [CallerMemberName]string propertyName = null)
    {
        if (object.Equals(field, value))
        {
            returnfalse;
        }

        field = value;
        this.OnPropertyChanged(propertyName);
        returntrue;
    }

    privatestring name;

    publicstring Name
    {
        get { returnthis.name; }
        set { this.SetProperty(refthis.name, value); }
    }

    privateint age;

    publicint Age
    {
        get { returnthis.age; }
        set { this.SetProperty(refthis.age, value); }
    }

}

Rx-MainをNuGetで追加してPropertyChangedイベントをIOに変換するものを作る。

staticclass NotifyPropertyChangedExtensions
{
    publicstatic IObservable<PropertyChangedEventArgs> PropertyChangedAsObservable(this INotifyPropertyChanged self)
    {
        return Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
            h => (s, e) => h(e),
            h => self.PropertyChanged += h,
            h => self.PropertyChanged -= h);
    }
}

こうすると、Whereでプロパティ名を絞ってからSubscribeしてるので、そこで好きなようにやるかんじ。

var p = new Person();
var propertyChanged = p.PropertyChangedAsObservable();

var token = new CompositeDisposable();
propertyChanged.Where(e => e.PropertyName == "Name")
    .Select(_ => p.Name)
    .Subscribe(name => Console.WriteLine(name)).AddTo(token);
propertyChanged.Where(e => e.PropertyName == "Age")
    .Select(_ => p.Age)
    .Subscribe(age => Console.WriteLine(age)).AddTo(token);

// プロパティを変更したら値が出力される
p.Name = "tanaka";
p.Age = 100;

// 購読解除
token.Dispose();

// イベントの処理やめたので何もおきない
p.Name = "kimura";
p.Age = 120;

もうちょっとプロパティの値の変更に特化して使いたいとかいうなら、ここまでラップしたメソッドを定義してもいいかも?

staticclass NotifyPropertyChangedExtensions
{
    publicstatic IObservable<PropertyChangedEventArgs> PropertyChangedAsObservable(this INotifyPropertyChanged self)
    {
        return Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
            h => (s, e) => h(e),
            h => self.PropertyChanged += h,
            h => self.PropertyChanged -= h);
    }

    publicstatic IObservable<T> PropertyChangedAsObservable<T, TProp>(this T self, Expression<Func<T, TProp>> propertySelector)
        where T : INotifyPropertyChanged
    {
        // プロパティ名を取り出して
        var name = ((MemberExpression)propertySelector.Body).Member.Name;
        return self.PropertyChangedAsObservable()
            // プロパティ名でフィルタリングして
            .Where(e => e.PropertyName == name)
            // 自分自身を返す感じ
            .Select(_ => self);
    }
}

こうしとくと、こういう感じになる。

var p = new Person();

var token = new CompositeDisposable();
p.PropertyChangedAsObservable(o => o.Name)
    .Subscribe(person => Console.WriteLine(person.Name)).AddTo(token);
p.PropertyChangedAsObservable(o => o.Age)
    .Subscribe(person => Console.WriteLine(person.Age)).AddTo(token);

p.Name = "tanaka";
p.Age = 100;

token.Dispose();

p.Name = "kimura";
p.Age = 120;

とりあえず

ReactivePropertyを入れておけば、拡張メソッドが定義されてたりします。PropertyChangdAsObservableメソッドの他にObservePropertyメソッドというのもあって以下のような感じで使えます。

var p = new Person();

var token = new CompositeDisposable();
p.ObserveProperty(o => o.Name).Subscribe(name => Console.WriteLine(name)).AddTo(token);
p.ObserveProperty(o => o.Age).Subscribe(age => Console.WriteLine(age)).AddTo(token);

p.Name = "tanaka";
p.Age = 100;

token.Dispose();

p.Name = "kimura";
p.Age = 120;

素敵なかんじ。ということで、ObserveProperty使うのがよさげ。

StringInfoのSubstringByTextElementsがWindowsストアアプリでは使えない

$
0
0

がりっちさんに4バイト文字の𩹉(とびうお)を送り付けて、がりっちさんが作ってるTwitterクライアントの文字列処理をばぐらせて遊んでいたら、𩹉(とびうお)問題という名前がつきました。

StringInfoクラス使えば便利メソッドあるから簡単じゃん?と思ったら

SubstringByTextElementsという、今回のがりっちさんの中で重要なメソッドがなくなってたみたいです。自力実装されてたコードを見て自分だったら…というのを書いてみました。

publicstaticclass StringExtensions
{
    /// <summary>/// 文字数を返す/// </summary>/// <paramname="self">対象の文字列</param>/// <returns>文字数</returns>publicstaticint LengthInTextElements(thisstring self)
    {
        returnnew StringInfo(self).LengthInTextElements;
    }

    /// <summary>/// 1文字ずつ返す感じのIEを作る/// </summary>/// <paramname="self">対象の文字列</param>/// <paramname="index">開始位置</param>/// <returns>IE</returns>publicstatic IEnumerable<string> GetTextElementEnumerable(thisstring self, int index = 0)
    {
        var e = StringInfo.GetTextElementEnumerator(self, index);
        while (e.MoveNext())
        {
            yieldreturn (string)e.Current;
        }
    }

    /// <summary>/// 指定した範囲の文字列を切り出す/// </summary>/// <paramname="self">対象の文字列</param>/// <paramname="startIndex">開始位置</param>/// <paramname="length">長さ</param>/// <returns></returns>publicstaticstring SubstringByTextElements(thisstring self, int startIndex, int length)
    {
        var sub = self.GetTextElementEnumerable(startIndex).Take(length).ToArray();
        if (sub.Length != length)
        {
            thrownew ArgumentOutOfRangeException();
        }

        returnstring.Concat(sub);
    }
}

とりあえずGetTextElementEnumeratorというメソッドがあるので、それを使ってIEにするメソッドを作ってしまえばSubstringなんて楽勝さ!という発想。以下のテストケースを通してみてグリーンでした。

[TestClass]
publicclass UnitTest1
{
    [TestMethod]
    publicvoid LengthInTextElements()
    {
        var target = "俺が𩹉だ!";
        Assert.AreEqual(5, target.LengthInTextElements());
    }

    [TestMethod]
    publicvoid GetTextElementEnumerable()
    {
        var target = "俺が𩹉だ!";
        var result = target.GetTextElementEnumerable().ToArray();
        Assert.AreEqual("俺", result[0]);
        Assert.AreEqual("が", result[1]);
        Assert.AreEqual("𩹉", result[2]);
        Assert.AreEqual("だ", result[3]);
        Assert.AreEqual("!", result[4]);
    }

    [TestMethod]
    publicvoid SubString()
    {
        var target = "俺が𩹉だ!";
        var result = target.SubstringByTextElements(2, 1);
        Assert.AreEqual("𩹉", result);
    }

    [TestMethod]
    publicvoid SubString_範囲外()
    {
        try
        {
            var target = "俺が𩹉だ!";
            var result = target.SubstringByTextElements(2, 1000);
            Assert.Fail();
        }
        catch (Exception ex)
        {
            // Lengthがはみ出てると例外が出る
            Assert.IsInstanceOfType(ex, typeof(ArgumentOutOfRangeException));
        }
    }
}
Viewing all 1388 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>