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

Blend for VS2013 RCにXAMLでのBehavior復活!!(ただしWindows 8.1のみのもよう)

$
0
0

Windows ストア アプリを作るうえで最大の不満点。WPF, Silverlight(いい子だったのに…), Windows Phone(一台しか出てないけど…)と歴代のXAMLプラットフォームを触ってきた人にとってはかなりストレスになったと思われるWindows ストア アプリにBehaviorが無いという問題ですが、Windows 8.1のストアアプリでは解決されそうです。 Windows ストア アプリ(8.1)を新規作成してBlendで開いてみるとビヘイビアーが復活しています。

f:id:okazuki:20130915175011p:plain

RCの段階で追加されてるBehaviorは以下の10個です。

  • ○○Action系
    • CallMethodAction
    • ChangePropertyAction
    • ControlStoryboardAction
    • GoToStateAction
    • NavigateGoToPageAction
    • PlaySoundAction
  • ○○Behavior系
    • IncrementalUpdateBehavior
    • DataTriggerBehavior
    • EventTriggerBehavior

今までのBehaviorと違うのは○○Triggerという名前の人がいなくなった点ですね。○○TriggerBehaviorという名前に変わってます。名前が変わった点にさえ気を付ければ、そんなに使う分には困らなさそうです。例えば、Rectangleに、InvokeCommandActionをデザイナ上でドロップすると、以下のように間にEventTriggerBehaviorがはさまれた状態で追加されます。

f:id:okazuki:20130916123440j:plain

EventTriggerBehaviorのプロパティは今までと同じ要領でイベントを設定できます。

f:id:okazuki:20130916123720j:plain

Commandのバインドも今まで通りできる。

f:id:okazuki:20130916123817j:plain

これは、Windows 8.1のストアアプリは、今までの8のときのストアアプリよりもXAMLerにとってストレスなく作れるようになりそうです!!(ストアアプリのBlend for VS2013もExpressについてくることですし)


Windows 8.1のBlendのBehaviorに自作Behaviorを追加する

$
0
0

Blend for VS2013 RCでBehaviorが追加されてテンションが上がってる今日この頃です。Behaviorが追加されたら、自作もしたいという要望ありますよね?ということで、コードレシピにコードをあげておきました。

RCの段階+試行錯誤した結果なので間違ってたらコメントなりくださいm()m

Windows 8.1のBlendのBehaviorに自作Behaviorを追加する

Azureブログを書いてSurfaceRTを貰おうへの参加方法 #azurejp #jazug

$
0
0

さて、表題の通りのイベントが開催されています。Surface RTが貰える…!?ということで盛り上がるかと思ったら5人のエントリしかない…!?倍率二倍以下という状態なので、ちょっと参加方法をば…。

Step1 Blogを書く

書きましょう。3カテゴリあって、Aカテゴリに誰も書いてないので書くだけ書いて逃げ切ってしまえるかもしれません。とりあえず、Azureネタ!さくっと使って気になってることを試してレポートする感じでもOKです。

Azureが17000円ぶんタダで使えるので申し込んでやってみるといいと思います。

Step2 Jazugへ投稿する

Blogへのリンクと簡単なコメントを添えて投稿しましょう。

Japan Windows Azure User Group

Step3 とりあえず皆に知らせる

ここが一番個人的に難しかったです。投稿自体は画面の右側の列にあるJapan Windows Azure User Groupでの他のユーザーからの最近の投稿にあるすべて見るリンクで見れます。

f:id:okazuki:20130918211503j:plain

ここから自分のBlog記事の投稿のみを抽出した、プレゼント当選の指標となるいいね!をつけてもらいたいページを表示して、Twitterや、自分のBlog記事に埋め込んでおきます。じゃないと、Blog自体に設置してあるいいね!ボタンを押してもらっても、恐らく当選の指標となるいいね!にならないので、折角いいね!してもらっても申し訳ない感じになってしまいます。

URL自体は、以下の形式になります。

最後のIDの見かたですが、自分の投稿をシェアするのリンクに入ってます。

f:id:okazuki:20130918212141j:plain

リンクのプロパティあたりからコピペしましょう。

f:id:okazuki:20130918212301j:plain

ということで、私の応募している投稿へのリンクは、こんな感じになります。

Step4 待つ

30日が過ぎるまで待ちましょう。

まとめ

ということで、このイベント自体知らなかった人も、イベントしってたけどめんどくさいな~と思ってた人も、これから書くぜ!って思ってた人にも参考になれば幸いです。

Windows 8.1のストアアプリのBehaviorでTriggerBehaviorを作る時の注意点

$
0
0

Actionsプロパティは依存プロパティとして実装しましょう。

何故?

依存プロパティとして実装しないと、ActionにBindingが出来なくなります。

Actionsが依存プロパティではなく、普通のプロパティの場合、以下のようにActionsの中のActionで{Binding}したときにDataContextが切れてnullが渡ってしまいます。

<Button Content="ClickEventTriggerBehvior and MainPageViewModelMessageDialogAction sample button"HorizontalAlignment="Left"Margin="120,176,0,0"Grid.Row="1"VerticalAlignment="Top"><Interactivity:Interaction.Behaviors><Behaviors:ClickEventTriggerBehavior><Behaviors:ClickEventTriggerBehavior.Actions><Behaviors:MainPageViewModelMessageDialogAction TargetObject="{Binding}"/></Behaviors:ClickEventTriggerBehavior.Actions></Behaviors:ClickEventTriggerBehavior></Interactivity:Interaction.Behaviors></Button>

愚痴

以下のようなプロパティの実装がお手本としてあるから、添付プロパティにしなくてもいいじゃん?って思って普通のプロパティとして実装してしまいがちだけど、そうするとActionでBindingできなくて死ぬ。

public ActionCollection Actions
{
    get
    {
        var actions = (ActionCollection)GetValue(ActionsProperty);
        if (actions == null)
        {
            actions = new ActionCollection();
            this.SetValue(ActionsProperty, actions);
        }
        return actions;
    }
}

いまいち。

Windows 8.1のBehaviorでTriggerBehaviorを作る時の注意点2

$
0
0

Windows 8.1 RTM + Visual Studio 2013 RCの段階での情報です。

TriggerBehaviorには必ず以下のようなActionsプロパティが必要になります。

publicstaticreadonly DependencyProperty ActionsProperty =
    DependencyProperty.Register("Actions", typeof(ActionCollection), typeof(HogehogeTriggerBehavior), new PropertyMetadata(null));

public ActionCollection Actions
{
    get
    {
        var actions = (ActionCollection)GetValue(ActionsProperty);
        if (actions == null)
        {
            actions = new ActionCollection();
            this.SetValue(ActionsProperty, actions);
        }
        return actions;
    }
}

毎回実装するのがめんどくさいので、という理由で基本クラスにまとめるという発想が出てくるので、以下のようなクラスを実装します。

using Microsoft.Xaml.Interactivity;
using Windows.UI.Xaml;

namespace App11
{
    publicclass TriggerBehaviorBase : DependencyObject, IBehavior
    {
        // 毎回実装するのもめんどくさいし、基本クラスで定義しておきますか。publicstaticreadonly DependencyProperty ActionsProperty =
            DependencyProperty.Register(
                "Actions",
                typeof(ActionCollection),
                typeof(TriggerBehaviorBase),
                new PropertyMetadata(null));

        public ActionCollection Actions
        {
            get 
            { 
                var value = (ActionCollection)this.GetValue(ActionsProperty); 
                if (value == null)
                {
                    value = new ActionCollection();
                    this.SetValue(ActionsProperty, value);
                }

                returnvalue;
            }
        }

        // ついでだし、既存のビヘイビアっぽく実装できるようにしただけで、今回の本題ではない。public DependencyObject AssociatedObject { get; private set; }

        publicvoid Attach(DependencyObject associatedObject)
        {
            this.AssociatedObject = associatedObject;
            this.OnAttached();
        }

        publicvoid Detach()
        {
            this.OnDetaching();
            this.AssociatedObject = null;
        }

        protectedvirtualvoid OnAttached()
        {
        }

        protectedvirtualvoid OnDetaching()
        {
        }
    }
}

こうしておけば、TriggerBehaviorの実装も楽になります。例えばボタンのクリックをトリガーにする場合は以下のような感じです。あぁ楽ちん。

using Microsoft.Xaml.Interactivity;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace App11
{
    publicclass ButtonClickTriggerBehavior : TriggerBehaviorBase
    {
        protectedoverridevoid OnAttached()
        {
            var btn = (Button)this.AssociatedObject;
            btn.Click += this.ButtonClick;
        }

        protectedoverridevoid OnDetaching()
        {
            var btn = (Button)this.AssociatedObject;
            btn.Click -= this.ButtonClick;
        }

        privatevoid ButtonClick(object sender, RoutedEventArgs e)
        {
            Interaction.ExecuteActions(this, this.Actions, e);
        }
    }
}

落とし穴

このTriggerBehaviorに、ActionをBlendからドロップしようとしてもできません。以下のようになります。

f:id:okazuki:20130921172828j:plain

Actionsプロパティは存在するので、プロパティウィンドウからコレクションエディタを使ってActionを追加できますが、これじゃぁ快適に開発できません。

f:id:okazuki:20130921173040j:plain

解決策?(いまいちすぎる…)

とりあえず、TriggerBehaviorクラス自身(親ではなく自分自身)にActionsという名前のプロパティがあればActionをドロップできるみたいなので、newなりなんなりを使って強引にでも定義してやればドロップできるようになります。

using Microsoft.Xaml.Interactivity;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace App11
{
    publicclass ButtonClickTriggerBehavior : TriggerBehaviorBase
    {
        /// <summary>/// えっ・・・。/// </summary>publicnew ActionCollection Actions
        {
            get { returnbase.Actions;  }
        }

        protectedoverridevoid OnAttached()
        {
            var btn = (Button)this.AssociatedObject;
            btn.Click += this.ButtonClick;
        }

        protectedoverridevoid OnDetaching()
        {
            var btn = (Button)this.AssociatedObject;
            btn.Click -= this.ButtonClick;
        }

        privatevoid ButtonClick(object sender, RoutedEventArgs e)
        {
            Interaction.ExecuteActions(this, this.Actions, e);
        }
    }
}

これでプロジェクトをリビルドするとデザイナからぽとぺたでActionを追加できるようになります。

f:id:okazuki:20130921173402j:plain

でも、Actionsプロパティが2個表示されて、とってもイマイチな見た目になってしまいます。

f:id:okazuki:20130921173448j:plain

ううむ…。

Windows 8.1のストアアプリのテンプレートの構造

$
0
0

Windows 8.1 RTM + Visual Studio 2013時点の情報です

Windows ストア アプリの開発は、テンプレートに従ってやるのが一番の近道です。ただ、Windows 8とWindows 8.1でテンプレートの中身が結構変わりました。どれくらい変わったかというと、Commonフォルダの中身が以下のように変わってます。

Windows 8

  • BindableBase.cs
  • BooleanNegationConverter.cs
  • BooleanToVisibilityConverter.cs
  • LayoutAwarePage.cs
  • RichTextColumns.cs
  • StandardStyles.xaml
  • SuspensionManager.cs

Windows 8.1

  • NavigationHelper.cs
  • ObservableDictionary.cs
  • RelayCommand.cs
  • SuspensionManager.cs

何が変わったの?

むしろ何が変わってないのかを探すほうが早い感じですがSuspensionManagerは機能的には同じです。コードのdiffをとったわけではないですが、どちらも258行のコードでした。あとは変わってます。Converterや、LayoutAwarePageやStandardStyles.xamlのことは忘れましょう。

LayoutAwarePageはどこにいった

Windows 8の頃は、基本的にLayoutAwarePageを継承したクラスを使って画面を作っていました。Windows 8.1では、これを辞めて普通のPageクラスを継承したクラスを使います。では、LayoutAwarePageで提供していた画面遷移の履歴の保持etc...の様々な機能はどこにいったのかというと、NavigationHelperクラスに移動されています。

継承から委譲へ

Windows 8のページのテンプレートの困ったところは、LayoutAwarePageに依存しないページを作ろうと思ったらプレーンな何も無い空白のページをベースに作らなければなりませんでした。その他のページは全てLayoutAwarePageを継承しているため、これを書き換えて普通のPageや自前のPageの基本クラスに差し替えるのは結構骨の折れる作業です。LayoutAwarePageに依存しない開発をするなら、アイテムテンプレートを自作しないと現実的じゃないくらいです。

実際にPrism for Windows Runtimeでは、登場当初はテンプレートの書き換え手順を示していましたが、暫くして中の人がアイテムテンプレートとプロジェクトテンプレートを提供してくれました。これのおかげで大分楽に作れるようになりました。

Windows 8.1では、PageクラスからNavigationHelperクラスを使うことで画面遷移の履歴保持などの機能を提供してくれています。参考までに、Windows 8.1の一番シンプルなブランクページのコードを以下に示します。

publicsealedpartialclass BasicPage : Page
{

    private NavigationHelper navigationHelper;
    private ObservableDictionary defaultViewModel = new ObservableDictionary();

    /// <summary>/// これは厳密に型指定されたビュー モデルに変更できます。/// </summary>public ObservableDictionary DefaultViewModel
    {
        get { returnthis.defaultViewModel; }
    }

    /// <summary>/// NavigationHelper は、ナビゲーションおよびプロセス継続時間管理を/// 支援するために、各ページで使用します。/// </summary>public NavigationHelper NavigationHelper
    {
        get { returnthis.navigationHelper; }
    }


    public BasicPage()
    {
        this.InitializeComponent();
        this.navigationHelper = new NavigationHelper(this);
        this.navigationHelper.LoadState += navigationHelper_LoadState;
        this.navigationHelper.SaveState += navigationHelper_SaveState;
    }

    /// <summary>/// このページには、移動中に渡されるコンテンツを設定します。前のセッションからページを/// 再作成する場合は、保存状態も指定されます。/// </summary>/// <paramname="sender">/// イベントのソース (通常、<seecref="NavigationHelper"/>)>/// </param>/// <paramname="e">このページが最初に要求されたときに/// <seecref="Frame.Navigate(Type, Object)"/>に渡されたナビゲーション パラメーターと、/// 前のセッションでこのページによって保存された状態の辞書を提供する/// セッション。ページに初めてアクセスするとき、状態は null になります。</param>privatevoid navigationHelper_LoadState(object sender, LoadStateEventArgs e)
    {
    }

    /// <summary>/// アプリケーションが中断される場合、またはページがナビゲーション キャッシュから破棄される場合、/// このページに関連付けられた状態を保存します。値は、/// <seecref="SuspensionManager.SessionState"/>のシリアル化の要件に準拠する必要があります。/// </summary>/// <paramname="sender">イベントのソース (通常、<seecref="NavigationHelper"/>)</param>/// <paramname="e">シリアル化可能な状態で作成される空のディクショナリを提供するイベント データ///。</param>privatevoid navigationHelper_SaveState(object sender, SaveStateEventArgs e)
    {
    }

    #region NavigationHelper の登録/// このセクションに示したメソッドは、NavigationHelper がページの/// ナビゲーション メソッドに応答できるようにするためにのみ使用します。/// /// ページ固有のロジックは、/// <seecref="GridCS.Common.NavigationHelper.LoadState"/>/// および <seecref="GridCS.Common.NavigationHelper.SaveState"/>のイベント ハンドラーに配置する必要があります。/// LoadState メソッドでは、前のセッションで保存されたページの状態に加え、/// ナビゲーション パラメーターを使用できます。protectedoverridevoid OnNavigatedTo(NavigationEventArgs e)
    {
        navigationHelper.OnNavigatedTo(e);
    }

    protectedoverridevoid OnNavigatedFrom(NavigationEventArgs e)
    {
        navigationHelper.OnNavigatedFrom(e);
    }

    #endregion
}

少しずつ解説していきます。

DefaultViewModelプロパティ

DefaultViewModelというObservableDictionary型のプロパティが定義されています。ObservableDictionaryクラスはCommon名前空間で定義されている、変更通知機能を持ったDictionaryです。Windows 8の頃はLayoutAwarePageで持っていたプロパティになりますが、Windows 8.1では、ページに直接定義されるようになりました。

これはただ単に、XAML側でDataContextにBindingされているプロパティになります。該当箇所を以下に示します。

<Page    ... 省略 ...DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"    ... 省略 ...mc:Ignorable="d">

DefaultViewModelのプロパティのコメントにあるとおり、ここの型をObservableDictionary型から変えることで、任意の型をページのViewModelとして割り当てることができます。

private ObservableDictionary defaultViewModel = new ObservableDictionary();

/// <summary>/// これは厳密に型指定されたビュー モデルに変更できます。/// </summary>public ObservableDictionary DefaultViewModel
{
    get { returnthis.defaultViewModel; }
}

例えばMyViewModelというViewModelを定義して、それを使いたい場合は以下のようにコードを書き換えます。

private MyViewModel defaultViewModel = new MyViewModel();

public MyViewModel DefaultViewModel
{
    get { returnthis.defaultViewModel; }
}

基本クラスではなく、ページに定義されるようになったことで、このように簡単に型を変えることができるようになりました。この点はとってもいいと思います。Windows 8のLayoutAwarePageの場合は、ページに独自プロパティを定義して、それをDataContextに紐づけることで同様のことができますが、LayoutAwarePageに定義されたDefaultViewModelプロパティは残りっぱなしなので、なんだか気持ち悪い感じになってました。

NavigationHelper

このクラスは、名前が指し示す通りナビゲーションでWindows ストア アプリが持つべき一般的な動きをページクラスに付与するヘルパークラスです。NavigationHelper.csを開くと、クラスのドキュメントコメントに使用方法が書かれています。そこにある通り、PageクラスにNavigationHelperを組み込むには以下のようなコードをPageクラスに追記します。

private NavigationHelper navigationHelper;

publicコンストラクタ()
{
    this.InitializeComponent();
    this.navigationHelper = new NavigationHelper(this);
    this.navigationHelper.LoadState += navigationHelper_LoadState;
    this.navigationHelper.SaveState += navigationHelper_SaveState;
}

public NavigationHelper NavigationHelper
{
    get { returnthis.navigationHelper; }
}

privatevoid navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
}

privatevoid navigationHelper_SaveState(object sender, SaveStateEventArgs e)
{
}

protectedoverridevoid OnNavigatedTo(NavigationEventArgs e)
{
    navigationHelper.OnNavigatedTo(e);
}

protectedoverridevoid OnNavigatedFrom(NavigationEventArgs e)
{
    navigationHelper.OnNavigatedFrom(e);
}

基本的に必要な操作は、NavigationHelperクラスをフィールドに持ち、コンストラクタでNavigationHelperのインスタンスを作成します。そのときNavigationHelperのコンストラクタにページ自身のインスタンスを渡します。こうすることでページの各種イベントなどを適切に処理してLayoutAwarePageが提供してくれてた機能の一部(ショートカットや戻るボタンなど)を提供してくれます。NavigationHelperクラスにはGoBackCommandというコマンドが提供されていて、それが戻るボタンのCommandとバインドされています。この点もLayoutAwarePage内のGoBackイベントハンドラなどでやっていた処理がコマンドでシンプルにあらわされていて個人的に好きです。(中身はそんなに変わらないですが…)

次に、ページの履歴やページの一時データの保持などですが、LayoutAwarePageのときはLoadStateとSaveStateというメソッドをオーバーライドして利用していました。NavigationHelperでは、LoadStateイベントとSaveStateイベントが定義されていて、それを購読することで同様の処理が行えます。LoadStateイベントとSaveStateイベントは、いまどき珍しく独自のdelegateとして定義されていて、以下のようなシグネチャを持ちます。

/// <summary>/// <seecref="NavigationHelper.LoadState"/>イベントを処理するメソッドを表します/// </summary>publicdelegatevoid LoadStateEventHandler(object sender, LoadStateEventArgs e);
/// <summary>/// <seecref="NavigationHelper.SaveState"/>イベントを処理するメソッドを表します/// </summary>publicdelegatevoid SaveStateEventHandler(object sender, SaveStateEventArgs e);

LoadStateイベントのイベント引数であるLoadStateEventArgsクラスは以下のプロパティを持っています。

  • public object NavigationParameter { get; }
    • 画面遷移で前の画面から渡されたパラメータを取得できます。
  • public Dictionary<string, Object> PageState { get; }
    • 以前にこのページでSaveStateのときに設定した値が入ったDictionaryを取得します。はじめてページにアクセスするときはnullになります。

SaveStateイベントのイベント引数であるSaveStateEventArgsクラスは以下のプロパティを持っています。

  • public Dictionary<string, Object> PageState { get; }
    • ページで一時的に保存したいデータを詰めるDictionaryを取得します。ここで設定した値がLoadStateイベントのときに取得できます。

このイベントを処理することで、LayoutAwarePageのときにLoadStateメソッドとSaveStateメソッドで行っていたのと同じ処理が可能になります。

ObservableDictionaryクラス

通知機能をもったDictionaryです。デフォルトのページのテンプレートでDefaultViewModelプロパティに指定されるためだけに存在してると思われます。

RelayCommandクラス

ICommandインターフェースの、よくある実装です。ExecuteとCanExecuteをデリゲートで指定可能です。NavigationHelperのGoBackCommandプロパティなどで使用されています。

StandardStyles.xamlはどこにいった?

様々な基本的なスタイルが定義されていたStandardStyles.xamlですがいなくなりました。戻るボタンなどのような基本的なよくあるスタイルは、以下のXAMLで定義されています。

C:\Program Files (x86)\Windows Kits\8.1\Include\winrt\xaml\design\generic.xaml

テーマごとの色などの定義や、コントロールのテンプレートも定義されています。AppBarButtonは、専用クラス定義され、そのコントロールにアイコンの設定機能などが追加されています。XAMLでボタンのテンプレートを差し替えるだけでAppBarのボタンを作れるとはいえ、専用クラスが用意されていたほうが楽ですね。

まとめ

プロジェクトテンプレートに含まれていたCommon名前空間のクラス構造が大きく変わった(そもそもCommonってフォルダがあって、そこに絶対必要な機能がプロジェクトテンプレートがあるのがおかしいと思ってる)ので、結構最初は戸惑うと思いますが、提供されてる機能はそんなに変わりありません。スナップとかが8.1になって無くなったぶん覚えることが減ってシンプルになったくらいです。

さらに、NavigationHelperの機能がいらない場合は委譲してる部分のコードを削除するだけでいいのでテンプレートに縛られないページ作りが楽になったので着実に進歩してると思います。とりあえず、Windows 8.1の一般公開及び、Visual Studio 2013の正式版の提供が楽しみですね。

Surface 2 + TypeCover2でRDPするとUS配列のキーボードになる #SurfaceJP

$
0
0

Surface 2になって解像度も上がり、Windows Azure上の仮想マシンをのぞき込んだり、自宅の母艦をRDPでのぞき込んだりするのに、より最適な端末となったSurface 2ですが、RDPで繋いでみるとJIS配列じゃなくてUS配列として認識されてしまう事態になってしまいました。

解決策はSurface 2のレジストリに日本語キーボードだよ!っていう設定を作ってやればOKでした。

上記参考サイトの通りレジストリを設定して再起動してやればOKです。Surface 2固有の注意点としては、HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\i8042prt\Parametersに、いくつか設定するとあるのですが、i8042prtの項目自体がありません。なのでi8042prtとParametersから作るようにします。

この設定をしてから再起動してRDP接続をしたところ、JIS配列のまま接続できてます。

あと、今回の件とは全く関係ありませんがWin8.1になったころからTypeCoverやTouchCoverでFn+CapsLockでFn固定が日本語キーボードでもできるようになってるあたり地味に改善されてますね。

ASP.NETの新しい認証が気になる

$
0
0

なんかVS2013でASP.NETのプロジェクトのひな形のソースを眺めてると、認証の処理が今までのMembershipProviderじゃなくて別のAPIを使ってるっぽいことに気づきました。MembershipProviderやRoleProviderの各種Providerは、自分で実装するにはえぐい感じのAPI群なので、新しいやつに期待ですね!!

とりあえず、今後ここらへんを読み進めてみようお思います。目指せ最小限の実装サンプル! - Authentication and Identify

個人的には、プログラミング ASP.NET 4.5?とかあたりが出てきて、ここら辺を詳しく解説してくれることを期待してたり。


.NETでファイルにテキスト書き込むときのBOMの有無

PowerPivotの計算式メモ

$
0
0

その行に列の値があるかどうか確認して有る場合は1, 無い場合は0

=IF(COUNTA([列名]) > 0, 1, 0)

その行のある列の値が何種類あるか数える。

=COUNTROWS(DISTINCT(テーブル名[列名]))

ASP.NET Identityカスタマイズに挑戦

$
0
0

ASP.NETの新しい認証に興味津々だったので、ちょっと遊んでみました。

デフォルトで提供されてるEntity Frameworkを使用したものやGoogleやMSアカウントなどを使ったものは無視して、自前で用意した認証情報でログインできるところまでやってみました。

プロジェクトの作成と参照の追加

空のASP.NETアプリケーションを作成して、以下の参照をNuGetから追加します。

  • Microsoft.Owin.Host.SystemWeb
    • OwinをIISで有効にするために必要みたい。(これが無くてOwinのStartupが動かない…!ってはまった)
  • Microsoft.AspNet.Identity.Owin
    • 認証に必要なライブラリをごそっとひっぱってくるために追加。

認証に必要な初期化コードの追加

空で認証もなんもなしの状態のASP.NETアプリケーションで作ったので、認証に必要な初期化コードを追加します。新規作成で、OWIN Startup クラスを追加します。名前はなんでもいいのですが、Startupという名前にしておきました。そこに、IAppBuilderのUseCookieAuthenticationメソッド(正確にはOwin.CookieAuthenticationExtensionsクラスに定義されている拡張メソッド)を使って認証の設定をします。

using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;

[assembly: OwinStartup(typeof(ASPNETIdentitySample.Startup))]

namespace ASPNETIdentitySample
{
    publicclass Startup
    {
        publicvoid Configuration(IAppBuilder app)
        {
            // クッキーベースの認証で、認証用のURLは/Auth/Login
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Auth/Login")
            });
        }
    }
}

ここでは、認証されてない状態で認証が必要なページに飛んできたら/Auth/Loginに飛ぶようにしました。

ユーザー情報のクラスを作成

認証したユーザーの情報を保持するためのクラスを用意します。ユーザーを表すクラスを作るには、Microsoft.AspNet.Identity.IUserインターフェースを実装したクラスを作成します。こいつは、IdとUserNameを持つだけのシンプルなインターフェースなのでさくっと実装しましょう。このクラスにプロパティを追加することで、色々保持させる情報を追加できそうな気もしますが、今回はシンプルにIdとUserNameだけを実装します。Auth.csというファイルをプロジェクトの直下に作って、そこに以下のようなクラスを作成します。

using Microsoft.AspNet.Identity;

namespace ASPNETIdentitySample
{
    publicclass AppUser : IUser
    {
        publicstring Id { get; set; }

        publicstring UserName { get; set; }
    }
}

ユーザー情報を保持したり、パスワードを管理するクラスの作成

次に、ユーザーの情報の管理やパスワードの管理をするクラスを作成します。ユーザー管理は、Microsoft.AspNet.Identity.IUserStore<T> where T : IUserというインターフェースを実装します。パスワード管理には、 Microsoft.AspNet.Identity.IUserPasswordStore<T> where T : IUserというインターフェースを実装します。型引数には、先ほどIUserを実装したAppUserクラスを指定します。Auth.csにAppUserStoreという名前のクラスで、2つのインターフェースを実装したクラスを作ります。

publicclass AppUserStore : IUserStore<AppUser>, IUserPasswordStore<AppUser>
{
    privatestatic List<AppUser> users = new List<AppUser>
    {
        new AppUser { Id = "user1-id", UserName = "user1" }
    };

    public Task CreateAsync(AppUser user)
    {
        users.Add(user);
        return Task.Delay(0);
    }

    public async Task DeleteAsync(AppUser user)
    {
        var target = await this.FindByIdAsync(user.Id);
        if (target == null)
        {
            return;
        }

        users.Remove(target);
    }

    public Task<AppUser> FindByIdAsync(string userId)
    {
        return Task.FromResult(users.FirstOrDefault(u => u.Id == userId));
    }

    public Task<AppUser> FindByNameAsync(string userName)
    {
        return Task.FromResult(users.FirstOrDefault(u => u.UserName == userName));
    }

    public async Task UpdateAsync(AppUser user)
    {
        var target = await this.FindByIdAsync(user.Id);
        if (target == null)
        {
            return;
        }

        target.UserName = user.UserName;
    }

    publicvoid Dispose()
    {
    }

    public Task<string> GetPasswordHashAsync(AppUser user)
    {
        return Task.FromResult(new PasswordHasher().HashPassword(user.UserName));
    }

    public Task<bool> HasPasswordAsync(AppUser user)
    {
        return Task.FromResult(true);
    }

    public Task SetPasswordHashAsync(AppUser user, string passwordHash)
    {
        return Task.Delay(0);
    }
}

基本的にCRUDの処理なので悩むことはないと思います。今回は簡単にするためにパスワードをAppUserクラスのUserNameと同じものだとOKとなるようにしています。そのときに、ちょっと悩んだのがパスワードを取得するためのGetPasswordHashAsyncメソッドで返す値です。どうも、このIUserPasswordStoreインターフェースは、ハッシュ化されたパスワードを前提としているようで、ハッシュ化されたパスワードを返す必要があります。普通は、SetPasswordHashAsyncでユーザーを作る時に、ハッシュ化されたパスワードが渡されてくるので、ユーザーとパスワードの紐づけを行っておけば、それをそのまま返すだけでいいのですが、今回は手抜きのためにUserNameをハッシュ化した値を返さないといけませんでした。んで、ASP.NET Identifyのクラスの中にはIPasswordHasherというインターフェースがいて、ハッシュ化の方法をカスタマイズできるようになっています。デフォルトの実装としてPasswordHasherというクラスがいるので、こいつを使ってUserNameをハッシュ化して返しました。

ASP.NET MVCの追加

HomeControllerを追加します。Controllersフォルダをプロジェクトの下に作ってHomeControllerを作成します。HomeControllerを追加すると、今まで綺麗だったプロジェクトにASP.NET MVC用のクラス達がガシガシと追加されて若干穢された気持ちになりますが、気にしないで行きます。

Indexアクションに対応するViewも追加して、Index.cshtmlの中身を以下のようにします。

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

Hello @(User.Identity.Name).

そして、この手順で作成したApp_StartのRouteConfigにはデフォルトのコントローラが設定されてないので、HomeControllerをデフォルトに設定するようにコードを変えます。routes.MapRouteのdefaultsにcontrollerを追加します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace ASPNETIdentitySample
{
    publicclass RouteConfig
    {
        publicstaticvoid RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name:"Default",
                url:"{controller}/{action}/{id}",
                defaults:new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}

この状態で実行して、以下のようにユーザー名が出ない状態のページが表示されることを確認します。

f:id:okazuki:20131110180709p:plain

デフォルトで認証が必要なように構成

次に、基本全ページに認証がいるようにしようと思うのでFilterConfig.csにAuthorizeAttributeを追加します。

using System.Web;
using System.Web.Mvc;

namespace ASPNETIdentitySample
{
    publicclass FilterConfig
    {
        publicstaticvoid RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());
            // デフォで認証が必要
            filters.Add(new AuthorizeAttribute());
        }
    }
}

この状態で実行して、Auth/Loginへリダイレクトされることを確認します。

f:id:okazuki:20131110180949p:plain

ReturnUrlというパラメータで、戻るべきページがわたってきてることも確認できます。

認証用コントローラの作成

ついに認証処理です。AuthControllerを作成してLoginというアクションを追加します。returnUrlをパラメータから受け取りViewBagにつめてページにわたすのと、LoginViewModelというユーザー名とパスワードを保持するクラスをViewに渡します。あと、このコントローラは認証いらないので、AllowAnonymous属性を追加しておきます。

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace ASPNETIdentitySample.Controllers
{
    // このコントローラは認証いらない
    [AllowAnonymous]
    publicclass AuthController : Controller
    {
        //// GET: /Auth/Loginpublic ActionResult Login(string returnUrl)
        {
            ViewBag.ReturnUrl = returnUrl;
            return View(new LoginViewModel());
        }
    }

    publicclass LoginViewModel
    {
        [Required(ErrorMessage = "ユーザー名を入れてください")]
        [Display(Name = "ユーザー名")]
        publicstring UserName { get; set; }

        [Required(ErrorMessage = "パスワードを入れてください")]
        [Display(Name = "パスワード")]
        publicstring Password { get; set; }
    }
}

LoginViewModelクラスをmodelにした空のViewを作成して、以下のようにログイン用のフォームを作ります。

@model ASPNETIdentitySample.Controllers.LoginViewModel

@{
    ViewBag.Title = "Login";
}

<h2>Login</h2>
@Html.ValidationSummary(true)

@* ReturnUrlをパラメータに渡すようにしたフォームを作る *@
@using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl }))
{
    <div class="form-group">
        @Html.LabelFor(m => m.UserName)
        @Html.TextBoxFor(m => m.UserName)
        @Html.ValidationMessageFor(m => m.UserName)
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Password)
        @Html.PasswordFor(m => m.Password)
        @Html.ValidationMessageFor(m => m.Password)
    </div>
    
    <input type="submit"value="ログイン"class="btn-default" />
}

UserManager<T>クラスを使って認証処理を行います。型引数にはIUserの実装クラス(今回の場合はAppUserクラス)を渡して、コンストラクタの引数には、IUserStore<T>の実装クラス(今回の場合はAppUserStoreクラス)を渡すことでインスタンス化できます。こいつのFindAsyncメソッドにユーザ名とパスワードを渡すことで、対象のユーザーが取得できます。nullの場合はログインに失敗したときの処理をすればOKです。

ユーザー情報がとれたら、認証情報をクッキーに保持するための処理をします。UserManagerクラスのCreateIdentityAsyncでクレームベースのIDが作れるみたいです(よくわかってない)。あとは、HttpContextからOwinContextを取得して、そこからAuthenticationプロパティを使ってIAuthenticationManagerのインスタンスを取得します。こいつのSignInメソッドに先ほど作成したクレームベースのIDを渡してやることで、無事認証情報された状態になります。

あとは、ReturnUrlで渡されてきたページへリダイレクトすればOKです。(正しいURLなのかどうかとかはチェックしたほうがいいと思うけど…)

//// POST: /Auth/Login
[HttpPost]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (!this.ModelState.IsValid)
    {
        ViewBag.ReturnUrl = returnUrl;
        return View(model);
    }

    // 認証
    var userManager = new UserManager<AppUser>(new AppUserStore());
    var user = await userManager.FindAsync(model.UserName, model.Password);
    if (user == null)
    {
        // 認証失敗したらエラーメッセージを設定してログイン画面を表示するthis.ModelState.AddModelError("", "ユーザ名かパスワードが違います");
        ViewBag.ReturnUrl = returnUrl;
        return View(model);
    }

    // クレームベースのIDを作って
    var identify = await userManager.CreateIdentityAsync(
        user, 
        DefaultAuthenticationTypes.ApplicationCookie);

    // 認証情報を設定
    var authentication = this.HttpContext.GetOwinContext().Authentication;
    authentication.SignIn(identify);

    // 元のページへリダイレクトreturn Redirect(returnUrl);
}

動作確認

アプリケーションを起動すると、Home/Indexを表示しようと試みますが、Auth/Loginページへリダイレクトされてログインページが表示されます。

f:id:okazuki:20131110185218p:plain

そして、ユーザー名にuser1、パスワードにuser1と入力してログインを押すとHome/Indexへリダイレクトされて以下のようにユーザー名が表示されます。

f:id:okazuki:20131110185337p:plain

ユーザー名やパスワードを間違えるともちろんログインできません。

f:id:okazuki:20131110185440p:plain

まとめと感想

情報があんまりなかったので手探りでしたが、結局はIUserインターフェースとIUserStoreインターフェースとIUserPasswordStoreインターフェースを実装するだけで、どうやってユーザーを認証するかという方法をカスタマイズできることがわかりました。MembershipProviderみたいにカオスな感じじゃなくて、実装するのもさほど苦じゃないので、とっととこっちを使うように世の中がなればいいなと思いました。

ロールとか、そっち方面も調べないとな…。

プロジェクトのダウンロード

このプロジェクトファイルは以下からダウンロードできます。

PowerPivot for Excelの行で集計しちゃう・・・?

$
0
0

非常に非効率なのでおすすめしませんが、列に以下のような式を入れることで集計できるみたいです。

=COUNTROWS(FILTER(data, EARLIER([UserId]) = [UserId] && EARLIER([Hour]) = [Hour]))

ポイントはFILTERでEARLIERを使うことで現在の行の値を使ってテーブルをフィルタできることかな?あまりしないほうがいい。性能的に。

Excel 2010で作って2003で開くと…

ASP.NET MVCでページャーつきテーブルを表示する

$
0
0

WebGrid使えば何も考えなくてもしてくれるのですが、何も考えない方法だと1000件のデータを50件ずつ表示する場合でも、いったん1000件とってこないといけなくて、とっても非効率的。ページに表示する必要最低限のデータだけ用意したいというんじょが世の中の常ですよね。

ページに渡すデータの作成

Controllerで適当に作りました。

using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace WebApplication1.Controllers
{
    publicclass HomeController : Controller
    {
        privatestaticreadonlyint MaxCount = 100;
        privatestaticreadonlyint PageSize = 10;

        // 引数にpageという名前のパラメータを受け取るようにする。// WebGridのページ切り替えのリンクで押されたページ番号がくるっぽいpublic ActionResult Index(int page = 1)
        {
            var current = page * PageSize;
            if (current > MaxCount)
            {
                // ふつうはそれなりなページ返すほうがいいかなthrownew HttpException(400, "ページ範囲外です");
            }

            // 必要なデータだけつめてページに引き渡すreturn View(new PagerViewModel
            {
                People = Enumerable
                    .Range(current, PageSize)
                    .Select(i => new Person { Name = "tanaka" + i, Age = i % 30 }),
                MaxCount = MaxCount,
                PageSize = PageSize,
                CurrentPage = page
            });
        }

    }

    publicclass PagerViewModel
    {
        public IEnumerable<Person> People { get; set; }
        publicint MaxCount { get; set; }
        publicint CurrentPage { get; set; }
        publicint PageSize { get; set; }
    }

    publicclass Person
    {
        publicstring Name { get; set; }

        publicint Age { get; set; }
    }
}

WebGridでページ切り替えつきでデータを表示

WebGridのコンストラクタに表示したいデータを渡すのが一番簡単な方法ですが、今回は簡単な方法でよきにはからってくれることが邪魔なのでBindメソッドを使います。Bindメソッドを使うと、自動でページングとかをOffにできます。あとは、Controllerで設定した全体の行数とかを設定すればOK.

@{
    ViewBag.Title = "Home Page";
}
@model WebApplication1.Controllers.PagerViewModel
@{
    // WebGridを作って
    var grid = new WebGrid();
    // Bindでいろいろ指定する
    grid.Bind(
        // 表示するデータ        source:this.Model.People,
        // 表示する列        columnNames:new[] { "Name", "Age" },
        // 自動でやってくれるページングやソートを切る        autoSortAndPage:false,
        // 全体の行数を設定する        rowCount:this.Model.MaxCount);
}

<div class="jumbotron">
    <h1>ASP.NET</h1>
    <p class="lead">ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS and JavaScript.</p>
    <p><a href="http://asp.net"class="btn btn-primary btn-large">Learn more &raquo;</a></p>
</div>

<div class="row">
    <div class="col-lg-12">
        @* formでくくらないとpageのパラメータが飛んでいかない *@
        @using (Html.BeginForm())
        {
            <div>
                @(this.Model.CurrentPage)Page.
            </div>
            @* あとは普通通りGetHtml *@
            @grid.GetHtml()
        }
    </div>
</div>

これで、ページ切り替えしつつ、データは必要最低限しかとらないページの完成です。

ASP.NET MVCのWebGrid + ReStable(うまくいかなかった)

$
0
0

ここ数日HTMLのtableをレスポンシブに対応する軽量なスクリプトのReStableをちらほらみるので触ってみました。NuGetでは配布されてないのでGitHubのページからDLします。

DLしたファイルから、以下の2ファイルをプロジェクトにコピーします。jsはScriptsフォルダに、cssはContentフォルダにコピーしました。

  • jquery.restable.js
  • jquery.restable.css

本当はReleaseビルドのときは.min.js, .min.cssが読み込まれるようにBundleConfig.csを構成するのがきれいなんですが、今回はおためしということでmin.jsとmin.cssは考えないでいってみます。

とりあえず、先日作ったASP.NET MVCでページャーつきテーブルを表示する に適用してみます。_Layout.cshtmlのheadタグにcssを、bodyの最後のほうにある@RenderSection("scripts", required: false)の前にjsを読み込ませるタグを追加します。

<!-- headタグの中 --><linkhref="~/Content/jquery.restable.css"rel="stylesheet" /><!-- bodyタグの最後にある@RenderSection("scripts", required: false)の前 --><scriptsrc="~/Scripts/jquery.restable.js"></script>

あとは、Index.cshtmlでReStableを使うだけ。とりあえず書いてみた。

@section scripts {
<scripttype="text/javascript">    $(function(){        $("table").ReStable();});</script>
}

結論からいうと、サイズが小さくなるとそれっぽく動くけど、テーブルのフッターにあるページャーのリンクが死んで使い物にならなかった。ちゃんと使おうと思ったら、もうちょい工夫しないといけなさそう。今日は時間切れなのでここまで。


WPF 4.5での新機能 リアルタイムソート、グルーピング、フィルタリング(LiveShaping)

$
0
0

これは、XAML Advent Calendar 2013の7日目の記事です。なんだか、またカレンダーのハードルが高めになってきたのでここで一気に普通に戻します!

最近は、HTML5に注力してたり、XAMLでもWindows ストアアプリに注力してたりで目玉!というような新機能が少ないWPFですが、WPF4.5でも地味な新機能が追加されてたりします。今回はその中で、データの値変更に伴うデータの再配置(ライブ形成)を紹介しようと思います。

新機能の前に

新機能の前にデフォルトの挙動を確認します。DataGridに以下のクラスを表示します。 DataGridなどのItemsControl系のクラスには、CollectionViewを使ったソートやグルーピングの機能がデフォルトで組み込まれています。たとえば、以下ようなクラスがあるとします。

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace LiveSharpingSample
{
    /// <summary>/// お約束のINotifyPropertyChangedを実装したベースクラス/// </summary>publicclass BindableBase : INotifyPropertyChanged
    {
        publicevent PropertyChangedEventHandler PropertyChanged;
        protectedvirtualvoid RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
            var h = this.PropertyChanged;
            if (h != null)
            {
                h(this, new PropertyChangedEventArgs(propertyName));
            }
        }

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

            store = value;
            this.RaisePropertyChanged(propertyName);
            returntrue;
        }
    }

    /// <summary>/// DataGridに表示するクラス/// </summary>publicclass Person : BindableBase
    {
        privateint salary;

        publicint Salary
        {
            get { returnthis.salary; }
            set { this.SetProperty(refthis.salary, value); }
        }

        privatestring name;

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

    }
}

このクラスをDataGridに表示してみます。MainPage.xamlを以下のようにさくっと定義します。

<Window x:Class="LiveSharpingSample.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="MainWindow"Height="350"Width="525"><Grid><Grid.RowDefinitions><RowDefinition Height="Auto" /><RowDefinition /></Grid.RowDefinitions><StackPanel><Button Content="生成"Click="GenerateButton_Click" /><Button Content="シャッフル"Click="ShuffleButton_Click" /></StackPanel><DataGrid x:Name="dataGrid"Grid.Row="1"AutoGenerateColumns="False"><DataGrid.Columns><DataGridTextColumn Header="名前"Binding="{Binding Name}" /><DataGridTextColumn Header="給料"Binding="{Binding Salary}" /></DataGrid.Columns></DataGrid></Grid></Window>

MainPage.xaml.csは、以下のようになります。適当なPersonクラスのインスタンスを15件作成してDataGridのItemsSourceプロパティに設定しています。

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Data;

namespace LiveSharpingSample
{
    /// <summary>/// MainWindow.xaml の相互作用ロジック/// </summary>publicpartialclass MainWindow : Window
    {
        private ObservableCollection<Person> people;

        public MainWindow()
        {
            InitializeComponent();
        }

        privatevoid GenerateButton_Click(object sender, RoutedEventArgs e)
        {
            var r = new Random();
            // 適当なデータを15件作成
            people = new ObservableCollection<Person>(Enumerable
                .Range(1, 15)
                .Select(i => new Person
                {
                    Name = "tanaka" + i,
                    // Salaryは1-500000の間
                    Salary = r.Next(500000)
                }));
            // dataGridに設定this.dataGrid.ItemsSource = people;

            // Salaryプロパティでソート
            var view = CollectionViewSource.GetDefaultView(this.people);
            view.SortDescriptions.Add(new SortDescription("Salary", ListSortDirection.Descending));
        }

        privatevoid ShuffleButton_Click(object sender, RoutedEventArgs e)
        {
            // Salaryプロパティの値を適当に設定しなおすif (this.people == null)
            {
                return;
            }

            var r = new Random();
            foreach (var p inthis.people)
            {
                p.Salary = r.Next(500000);
            }
        }
    }
}

このコードのポイントは、peopleをDataGridに設定したあとにGetDefaultViewメソッドでICollectionViewを取得してSalaryプロパティでソートしている箇所です。こうすることで、WPFの組み込みのソート機能を使うことができます。もう1つポイントとなるのは、ShuffleButton_Clickメソッドです。このコードは、peopleフィールド内のPeopleクラスのインスタンスのSalaryプロパティをランダムに書き換えます。

実行してみよう

このプログラムを実行して、生成ボタンをクリックすると15件のデータが画面に表示されます。

f:id:okazuki:20131201225433p:plain

この状態でシャッフルボタンをクリックしてみます。

f:id:okazuki:20131201225613p:plain

せっかくソートされていたデータが台無しになってしまいました。これが、デフォルトの動作になります。

プロパティを書き換えた後でソートを再実行する方法

データの書き換えが終わったタイミングでICollectionViewのRefreshメソッドを呼びましょう。

privatevoid ShuffleButton_Click(object sender, RoutedEventArgs e)
{
    // Salaryプロパティの値を適当に設定しなおすif (this.people == null)
    {
        return;
    }

    var r = new Random();
    foreach (var p inthis.people)
    {
        p.Salary = r.Next(500000);
    }
    // Viewをリフレッシュする
    var view = CollectionViewSource.GetDefaultView(this.people);
    view.Refresh();
}

これで、ソートが再実行されます。

WPF 4.5の新機能のLiveShapingを使った場合

WPF 4.5の新機能のLiveShapingは、グルーピング、フィルタリング、ソートをデータが書き換わった後にリアルタイムで見た目に反映する機能を提供してくれます。LiveSharping機能は、ICollectionViewLiveShapingインターフェースによって提供されます。このインターフェースは、以下のようなプロパティが定義されています。

  • CanChangeLive(Filtering|Grouping|Sorting)
    • リアルタイムフィルタリング, グルーピング, ソーティングが可能かどうかを返します
  • IsLive(Filtering|Grouping|Sorting)
    • リアルタイムフィルタリング, グルーピング, ソーティングを有効かどうかを取得または設定します。
  • Live(Filtering|Grouping|Sorting)Properties
    • リアルタイムフィルタリング, グルーピング, ソーティングを有効にするプロパティ名を指定するコレクションを取得します。

ICollectionViewLiveShapingインターフェースは、ICollectionViewに対してas演算子でキャストすることで取得できます。(正確にはICollectionViewを実装している具象クラスが実装しているかどうかで使えるか使えないか決まります)

例えば、先ほどのSalaryプロパティでリアルタイムソートを行うようにするためには、データ生成箇所のコードにICollectionViewLiveShapingインターフェースを使った以下のようなコードを追加します。

privatevoid GenerateButton_Click(object sender, RoutedEventArgs e)
{
    var r = new Random();
    // 適当なデータを15件作成
    people = new ObservableCollection<Person>(Enumerable
        .Range(1, 15)
        .Select(i => new Person
        {
            Name = "tanaka" + i,
            // Salaryは1-500000の間
            Salary = r.Next(500000)
        }));
    // dataGridに設定this.dataGrid.ItemsSource = people;

    // Salaryプロパティでソート
    var view = CollectionViewSource.GetDefaultView(this.people);
    view.SortDescriptions.Add(new SortDescription("Salary", ListSortDirection.Descending));

    // Salaryのソートをリアルタイムソートに設定する
    var liveShaping = view as ICollectionViewLiveShaping;
    if (liveShaping == null)
    {
        // ICollectionViewLiveShapingを実装していない場合は何もしないreturn;
    }

    // リアルタイムソートをサポートしているか確認するif (liveShaping.CanChangeLiveSorting)
    {
        // リアルタイムソートをサポートしている場合は対象のプロパティにSalaryを追加して// リアルタイムソートを有効にする。
        liveShaping.LiveSortingProperties.Add("Salary");
        liveShaping.IsLiveSorting = true;
    }
}

こうすることで、Salaryプロパティの値の変化に応じてソートが自動的に行われるようになります。

実行して生成ボタンを押した直後の画面

f:id:okazuki:20131201231326p:plain

シャッフルボタンをおしたところ、Salaryプロパティでソートが維持されていることが確認できます。

f:id:okazuki:20131201231533p:plain

今回は、ソート機能を使いましたが、フィルタリングやグルーピングに対しても同様の手順でリアルタイムに反映するようにすることができます。細かい改善点ですがなかなかいいんじゃないでしょうか。

WPF4.5の新機能LiveShapingのコードをコードレシピにあげました

ストアアプリでマスター詳細シナリオの記事を見て

$
0
0

なんとなく、ContentControlのContentへのBindingのPathにCurrentItemを割り当てれば、なんとなく解決しそうなきがしたので後で、ちゃんと検証してみようと思うなどした。DataTemplateSelectorは、まだ未検証だけど、変なBindingがらみのエラーはでなくなった風。

<ListBox Grid.Row="1"ItemsSource="{Binding Source={StaticResource source}}"ItemTemplate="{StaticResource PersonTemplate}"/><ContentControl Grid.Column="1"Grid.Row="1"Content="{Binding CurrentItem, Source={StaticResource source}, TargetNullValue={StaticResource NullPerson}}"ContentTemplate="{StaticResource PersonTemplate}" />

ストアアプリでのマスター詳細バインディングためしてみた(エラーでないか)

$
0
0

ちょいとCollectionViewSourceを使ったパターンを試してみました。CollectionViewSourceに適当なデータを突っ込みます。

<!-- MainPage.xaml --><CollectionViewSourcex:Name="source" />
// MainPage.xaml.csprivate ObservableCollection<Person> people = new ObservableCollection<Person>();

privatevoid navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
    this.source.Source = people;
}

DataTemplateSelectorを使いたいので以下のような感じで適当なのを作りました。

using MasterDetailApp.DataModel;
using System;
using System.Collections.Generic;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace MasterDetailApp.Common
{
    publicclass PersonDataTemplateSelector : DataTemplateSelector
    {
        public IList<DataTemplate> Templates { get; private set; }

        public PersonDataTemplateSelector()
        {
            this.Templates = new List<DataTemplate>();
        }

        protectedoverride DataTemplate SelectTemplateCore(object item, DependencyObject container)
        {
            if(item == null)
            {
                returnnull;
            }

            var p = (Person)item;
            returnthis.Templates[Math.Abs(p.Age) % this.Templates.Count];
        }
    }
}

MainPage.xamlのリソースにNull用のPersonオブジェクトとともに定義をしておきます。

<dataModel:Person x:Key="NullPerson"Name="NullPerson"Age="-1" /><common:PersonDataTemplateSelector x:Key="personTemplateSelector"><common:PersonDataTemplateSelector.Templates><DataTemplate><Border Padding="10"><Grid><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="Auto"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions><TextBlock Style="{StaticResource BaseTextBlockStyle}"TextWrapping="Wrap"Text="名前:"/><TextBlock Style="{StaticResource BaseTextBlockStyle}"Grid.Row="1"TextWrapping="Wrap"Text="年齢:"/><TextBlock Style="{StaticResource BaseTextBlockStyle}"Grid.Column="1"TextWrapping="Wrap"Text="{Binding Name}"/><TextBlock Style="{StaticResource BaseTextBlockStyle}"Grid.Column="1"Grid.Row="1"TextWrapping="Wrap"Text="{Binding Age}"/></Grid></Border></DataTemplate><DataTemplate><Border Background="Red"Padding="10"><Grid><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="Auto"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions><TextBlock Style="{StaticResource BaseTextBlockStyle}"TextWrapping="Wrap"Text="名前:"/><TextBlock Style="{StaticResource BaseTextBlockStyle}"Grid.Row="1"TextWrapping="Wrap"Text="年齢:"/><TextBlock Style="{StaticResource BaseTextBlockStyle}"Grid.Column="1"TextWrapping="Wrap"Text="{Binding Name}"/><TextBlock Style="{StaticResource BaseTextBlockStyle}"Grid.Column="1"Grid.Row="1"TextWrapping="Wrap"Text="{Binding Age}"/></Grid></Border></DataTemplate></common:PersonDataTemplateSelector.Templates></common:PersonDataTemplateSelector>

背景色なしと、背景色赤の2パターンのDataTemplateから年齢をもとに選択して返します。

んで、MasterとなるListBoxとDetailとなるContentControlをさくっと定義します。ContentControlのほうのBindingはPathにCurrentItemを指定するようにしておきました。こうすることで、現在選択中のアイテムを強制的にわたせるかな。

<ListBox Grid.Row="1"Margin="15"ItemsSource="{Binding Source={StaticResource source}}"ItemTemplateSelector="{StaticResource personTemplateSelector}"><ListBox.ItemContainerStyle><Style TargetType="ListBoxItem"><Setter Property="HorizontalContentAlignment"Value="Stretch" /><Setter Property="VerticalContentAlignment"Value="Stretch" /></Style></ListBox.ItemContainerStyle></ListBox><ContentControl Grid.Column="1"Grid.Row="1"Margin="15"Content="{Binding CurrentItem, Source={StaticResource source}, TargetNullValue={StaticResource NullPerson}}"ContentTemplateSelector="{StaticResource personTemplateSelector}"HorizontalContentAlignment="Stretch"VerticalContentAlignment="Stretch" />

あとは、AppBarに適当にボタンをおいて要素の追加や削除や選択解除などの処理を書いてみました。

privatevoid CreateData()
{
    // 適当にデータをつくる
    var items = Enumerable.Range(1, 20)
            .Select(i => new Person
            {
                Name = "田中" + i,
                Age = i
            });
    foreach (var item in items)
    {
        this.people.Add(item);
    }
}


privatevoid ClearData()
{
    // データクリアthis.people.Clear();
}

privatevoid AddData()
{
    // 適当にデータを追加this.people.Add(new Person { Name = "tanaka" + this.people.Count, Age = 100 });
}

privatevoid AppBarButton_Click(object sender, RoutedEventArgs e)
{
    this.ClearData();
}

privatevoid AppBarButton_Click_1(object sender, RoutedEventArgs e)
{
    this.AddData();
}

privatevoid AppBarButton_Click_2(object sender, RoutedEventArgs e)
{
    this.ClearData();
    this.CreateData();
}

privatevoid AppBarButton_Click_3(object sender, RoutedEventArgs e)
{
    // 選択解除
    ICollectionView v = this.source.View;
    v.MoveCurrentTo(null);
}

privatevoid AppBarButton_Click_4(object sender, RoutedEventArgs e)
{
    // 選択中の要素を削除if (source.View.CurrentItem == null)
    {
        return;
    }

    source.View.Remove(source.View.CurrentItem);
}

実行して動かしてみると。 起動直後。

f:id:okazuki:20131212083812p:plain

データ作ってみたところ

f:id:okazuki:20131212083927p:plain

選択してみたところ

f:id:okazuki:20131212084019p:plain

選択したものを削除したところ

f:id:okazuki:20131212084124p:plain

データを選択してからクリアしたところ

f:id:okazuki:20131212084232p:plain

ここまでの一連の操作をしたときのVSのデバッグ出力

'MasterDetailApp.exe' (CLR v4.0.30319: DefaultDomain): 'C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: DefaultDomain): 'c:\users\kazuki\documents\visual studio 2013\Projects\MasterDetailApp\MasterDetailApp\bin\Debug\AppX\MasterDetailApp.exe' が読み込まれました。シンボルが読み込まれました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\system32\WinMetadata\Windows.UI.Xaml.winmd' が読み込まれました。モジュールがシンボルなしでビルドされました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.InteropServices.WindowsRuntime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.InteropServices.WindowsRuntime.dll' が読み込まれました。モジュールがシンボルなしでビルドされました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\system32\WinMetadata\Windows.ApplicationModel.winmd' が読み込まれました。モジュールがシンボルなしでビルドされました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Threading.Tasks\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.Tasks.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\system32\WinMetadata\Windows.UI.winmd' が読み込まれました。モジュールがシンボルなしでビルドされました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\system32\WinMetadata\Windows.Foundation.winmd' が読み込まれました。モジュールがシンボルなしでビルドされました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.WindowsRuntime\v4.0_4.0.0.0__b77a5c561934e089\System.Runtime.WindowsRuntime.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Diagnostics.Debug\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Diagnostics.Debug.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.WindowsRuntime.UI.Xaml\v4.0_4.0.0.0__b77a5c561934e089\System.Runtime.WindowsRuntime.UI.Xaml.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\system32\WinMetadata\Windows.Globalization.winmd' が読み込まれました。モジュールがシンボルなしでビルドされました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Collections\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'c:\users\kazuki\documents\visual studio 2013\Projects\MasterDetailApp\MasterDetailApp\bin\Debug\AppX\GalaSoft.MvvmLight.DLL' が読み込まれました。PDB ファイルを開けないか、ファイルが見つかりません。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.ObjectModel\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.ObjectModel.dll' が読み込まれました。モジュールがシンボルなしでビルドされました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Extensions.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Threading\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\system32\WinMetadata\Windows.System.winmd' が読み込まれました。モジュールがシンボルなしでビルドされました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Linq\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Linq.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。

あやしげなバインド系のエラーは出てなさそう。ということで、ストアアプリでマスター詳細シナリオで出てたエラーはContentControlのBindingにCurrentItem渡してやればエラーでなくなりそう・・・?

ReactivePropertyのPCL版をnugetに置いてみました(まだベータ)

$
0
0

のいえさん(id:neuecc)が作った素敵なReactivePropertyをPCL対応にしてみました。CodePlexのプロジェクトのメンバーにじんぐるさんと共に追加してもらって、とりあえず動く感じまでやってみました。

ターゲットプラットフォームは以下のものをサポートしています。

  • .NET Framework 4.5
  • Windows store app(Windows 8.1)
  • Windows Phone 8
  • Xamarin iOS(試して無いけどできそう)
  • Xamarin Android(試して無いけどできそう)

インストール方法

Visual Studioのパッケージマネージャー コンソールで以下のコマンドでインストールできます。

PM> Install-Package ReactiveProperty-PCL -Pre

コードスニペット

packages\ReactiveProperty-PCL.0.4.0.1-beta\Snippetにコードスニペットが入ってるので追加しておくと捗ると思います。

お試し

WPFでこんな感じでDataContextにセットするViewModelが書けるのが気持ちいい。

using Codeplex.Reactive;
using System;
using System.Reactive.Linq;

namespace WpfApplication1
{
    publicclass MainWindowViewModel
    {
        public ReactiveProperty<string> Input { get; private set; }
        public ReactiveProperty<string> Output { get; private set; }
        public ReactiveCommand ResetInputCommand { get; private set; }

        public MainWindowViewModel()
        {
            this.Input = new ReactiveProperty<string>();
            this.Output = this.Input
                .Where(s => !string.IsNullOrWhiteSpace(s))
                .Delay(TimeSpan.FromSeconds(5))
                .Select(s => s.ToUpper())
                .ToReactiveProperty();

            this.ResetInputCommand = new ReactiveCommand();
            this.ResetInputCommand.Subscribe(_ => this.Input.Value = "Hello world.");
        }
    }
}
Viewing all 1387 articles
Browse latest View live


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