泥庭

2015年1月14日

WPFでBingMapを使う

Filed under: .NET, C#, WPF — タグ: , , — yone64 @ 2:29 AM
今回からFont大き目でお届けしたいと思います。少し見やすくなるとよいのですが?

WPFで地図表示がしたい

地図表示といえば、やっぱりGoogleMapですかね。でも、WPF-C#からならBingMapが相性よさそうですよね。(ほら、なんとなくMicrosoft同士だし。)
と思って探しているとやっぱりありました。
Bing Maps WPF Control
しかし、いまどきmsiでインストールしなければいけないとかちょっと残念感が漂う感じ。(NuGetからインストールできるようになるとよいですね。)
というわけで、msiを落としてきてインストールすれば準備完了です。(あ、あとBingMapのAPI Keyも取得しておいてください)

地図表示アプリの作成

インストールが終了したら、さっそくプロジェクトを作成してみましょう。
# なお、ここからの作業はすべて、VisualStudio2013 & Windows8.1で行ってます。
  • 新規プロジェクト作成→参照設定追加
C:\Program Files (x86)\Bing Maps WPF Control\V1\Libraries にインストールされた、Microsoft.Maps.MapControl.WPF.dll への参照を追加します。
キャプチャ

  • コントロール追加
MainWindow.xamlにMapコントロールを追加します。

<Window x:Class="BingMapSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wpf="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <wpf:Map/>
    </Grid>
</Window>
すると、デザインビューで地図が確認できます。
キャプチャ

  • 実行
ここで、一度実行してみましょう。
キャプチャ

地図が表示されましたね。「Invalid Credentials.~」というのはAPI Keyを入力してないがための文字列なのでとりあえずはOKです。

  • API Key設定
では、API Keyを設定して再び実行してみましょう

<wpf:Map CredentialsProvider="ここにAPI Keyを記述"/>
さて、再度実行!
キャプチャ

あれ?地図が表示されない!?

BingMapとの戦い(1回戦)

API Keyを設定したら、地図が表示されなくなってしまいました。仕方がないので、API Keyがない状態に戻して再度実行をしてみます。
やっぱり、地図は表示されません。なんだか不吉な感じがしてきました。ここで試したことを並べていきます。

  • リビルド → ×
  • ソリューションのクリーン → ×
  • Visual Studio再起動 → ×
  • Windows再起動 → ×
OSの再起動までしてダメなら、割ともう打つ手はない感じです。
仕方がないので、別のマシンを取り出して同じことを試してみます。そうこうしているうちに、ある共通点が見つかりました。

OSの1Userにつき、BingMapを表示するアプリケーションは一度目だけ地図が表示される。
これは、VisualStudioからの実行だけでなく、EXEを直接起動した場合も同じである
ということは、ここで一つの仮説が思いつきますね!

BingMapのDLLは、初回実行時にどこかにキャッシュ(?)を作成しており、キャッシュがある場合のみ地図表示に失敗する。(たぶんキャッシュロードのバグか何かで)
比較的善意に解釈してみました。(悪意を持って2回目以降の地図表示を拒否してるとどうしようもないけど、Microsoftのコントロールはそういうことしないよねという前提)
.NETのモジュールがキャッシュファイルを作りそうな箇所はそれほど多くないので探してみます。思いつくのは↓あたりでしょうか

  • C:\ProgramData
  • C:\Users\{各ユーザ}\AppData
で、結局下記にありました。
キャプチャ

IsolatedStorage(分離ストレージ)に保存されていたようです。
【参考】(@IT)分離ストレージを活用するには?[C#、VB]
ファイルを削除することにより、2回目以降の起動時にも地図が表示されることが確認されました。

対策検討会議

というわけで原因を見つけたものの、勝利!とはならないのです。
分離ストレージのファイルを削除すれば地図が表示されるとわかったものの、起動時に乱数で作ったっぽいフォルダ名の中にあるファイルを見つけて削除するというのは、まぁありえないわけです。
ならどうするか?

その前に、ひとつ疑問が。ヒントは2つ。

  • 2回目以降表示されないバグがあれば、さすがにネット検索にヒットしそう
  • 分離ストレージのファイル名末尾に「_JA-JP」とついている
ここから、導き出される答えは?
ひょっとして、日本語環境だけ?
日本語(+α)環境のみの不具合であれば、StackOverflowが引っかかることもないし、まぁありえる話かなと。
これは、英語OSでの実行を試してみるしかないですよね。でも英語OSなんて手元にない、なんてときは、Azureが便利ですよ。
で、結果はBingo。英語OSでは起きていなかったのです。

ここまでくれば、少し光明が見えてきた感じがします。
.NETのDLLが実行時のCultureを判断して何かしてるということは
  • Thread.CurrentThread.CurrentUICulture
  • Thread.CurrentThread.CurrentCulture
のどちらかが絡んでいるはず。(というか、英語OSを試す前に気付きたかったです。)
で、両方とも試した結果、CurrentUICultureが関連していることが判明。アプリケーションの起動時にCultureを変更するコードを入れた結果、無事地図が表示されるようになったのでした。
public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
    }
}
めでたし、めでたし。というわけにはいきませんね。CurrentUICultureがen-US固定になってしまいます。
さらに試行錯誤を加えた結果、次のことが判明しました。

Mapコントロールをアプリケーションで最初に初期化する時にen-USだったらよい

つまり、いったんCultureをen-USにして、Mapコントロールを初期化したのち、再びCultureを元に戻せばよさそうです。
結局、↓のようなコードを起動時に仕込むことになりました。

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        var old = Thread.CurrentThread.CurrentUICulture;
        Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
        var dummy = new Map();
        Thread.CurrentThread.CurrentUICulture = old;
    }
}

余談

設定するCultureは、en-US以外にもen-GBでもよいようです。その他のCultureもいくつか試してみましたが、ダメでした。
この2つのCultureに共通する点として、一つ気付いたのは、距離の単位です。

キャプチャ

milesになっているのがわかると思います。ちなみに、公式にヤード・ポンド法が使われている国は、英国と米国だけだそうです。
ひょっとするとこのことが関係しているのかもしれません。(裏は取ってないので単なる想像ですが。)

後半へ続く

大きな落とし穴が開いていたBingMapControlですが、ひとまず表示できるようになりました。
ただ、戦いがこれで終わったわけではありません。というわけで、続きは次回。さらに大きな落とし穴が待ち受けています。(死
広告

2014年12月11日

Bindingを整理しよう

Filed under: .NET, WPF — タグ: — yone64 @ 11:08 AM

この記事は、XAML AdventCalendarの11日目の記事です。

皆さんがハードル上げまくってますが、ちょっと基本に立ち返り、XAMLと切っても切れない縁のBindingを整理してみたいと思います。
とはいえ、Bindingだけでも範囲がめっちゃ広いので、どこから値を持ってくるか?という点に絞ります。

基本編

BindingはFrameworkElement.DataContextプロパティーに設定されたオブジェクトを参照します。

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

        this.DataContext = "Hello World!";
    }
}
<Window x:Class="BindingSamples.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>
        <Viewbox>
            <TextBlock Text="{Binding}" />
        </Viewbox>
    </Grid>
</Window>

image
DataContextに設定した文字が、TextBlockのTextとして表示されました。

Path色々編

通常はDataContextに設定したオブジェクトを他のプロパティーに設定しなおすことはほぼなく、オブジェクトのプロパティーを参照することが多いです。

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

        this.DataContext = new
        {
            Text = "Hello World",
            Hoge = new { Fuga = "PIYO"},
            Collection = new[] { "One", "Two", "Three" }
        };
    }
}
<Window x:Class="BindingSamples.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>
        <Viewbox>
            <StackPanel Orientation="Vertical">
                <TextBlock Text="{Binding Text}" />
                <TextBlock Text="{Binding Hoge.Fuga}" />
                <TextBlock Text="{Binding Collection[1]}" />
            </StackPanel>
        </Viewbox>
    </Grid>
</Window>

image
Property名を指定することで、そのオブジェクトのPropertyの値を取得することができます。
また、「.」でプロパティー名を連結することで、階層的に値をたどっていくことも可能です。その他、[ ]を使いIndexerでのアクセスも可能です。
なお、Pathの指定は先頭に限り省略可能で、{Binding Text}と{Biding Path=Text}は同じ意味になります。
「/」を利用することにより、選択されている~といった指定も可能ですが、ここでは割愛。詳しくは↓を参照ください。
http://msdn.microsoft.com/ja-jp/library/ms742451(v=vs.110).aspx

異なるソース編

ここまでのBindingはDataContextプロパティーのオブジェクトを参照していましたが、異なるソースを指定することもできます。

<Window x:Class="BindingSamples.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 x:Name="Grid">
        <Viewbox>
            <StackPanel Orientation="Vertical">
                <TextBlock Text="{Binding Path=MainWindow.Title, Source={x:Static Application.Current}}" />
                <TextBlock Text="{Binding Path=ActualWidth, ElementName=Grid}" />
            </StackPanel>
        </Viewbox>
    </Grid>
</Window>

image

Sourceプロパティーを指定することで、他のオブジェクトを参照することができます。ここには、{x:Static} のほかに{StaticResource}などもよく使います。
また、ElementNameを指定すると、XAML上の他のオブジェクトへの参照を取得することができます。ここでは、最上位のGridが取得できます。

異なるソース編 その2

自分自身から相対参照をもって値を取得することもできます。

<Window x:Class="BindingSamples.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 x:Name="Grid">
        <Viewbox>
            <Grid>
                <StackPanel Orientation="Vertical">
                    <TextBlock Text="{Binding Path=FontFamily, RelativeSource={RelativeSource Self}}" />
                    <TextBlock Text="{Binding Path=Children.Count, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}" />
                    <TextBlock Text="{Binding Path=Children.Count, RelativeSource={RelativeSource AncestorType={x:Type Grid} ,AncestorLevel=2}}" />
                </StackPanel>
            </Grid>
        </Viewbox>
        <Grid/>
    </Grid>
</Window>

image

Selfは文字通り、自分自身ですね。ここでは、TextBlock自身。
AncestorTypeは、FindAncestor(省略可能)モードの場合に使うキーワードで、XAMLをさかのぼって指定された型のオブジェクトを見つけ、参照します。これにAncestorLevelも追加すると見つけた2つ目の~みたいな指定ができるようになります。
あと、RelativeSourceでは特別な場合のみに使える指定があります。

<Window x:Class="BindingSamples.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">
    <Window.Resources>
        <ControlTemplate x:Key="Template" TargetType="{x:Type Button}">
            <TextBlock Text="{Binding Path=Foreground,RelativeSource={RelativeSource TemplatedParent}}"/>
        </ControlTemplate>
    </Window.Resources>
    <Grid x:Name="Grid">
        <Viewbox>
            <StackPanel Orientation="Vertical">
                <Button Foreground="Red" Template="{StaticResource Template}" />
                <Button Foreground="Blue" Template="{StaticResource Template}"/>
            </StackPanel>
        </Viewbox>
    </Grid>
</Window>

image

まずは、Templateの内部でのみ使えるTemplateParent。文字通りTemplateを保持する親コントロールへの参照を取得します。

<Window x:Class="BindingSamples.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 x:Name="Grid">
        <Viewbox>
            <ItemsControl ItemsSource="{Binding Collection}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Border BorderThickness="1" BorderBrush="Black">
		                    <Grid>
		                        <Grid.ColumnDefinitions>
		                            <ColumnDefinition />
		                            <ColumnDefinition />
		                        </Grid.ColumnDefinitions>
		                        <Grid.RowDefinitions>
		                            <RowDefinition />
		                            <RowDefinition />
		                        </Grid.RowDefinitions>
		                        <TextBlock Text="今"/>
		                        <TextBlock Grid.Row="1" Text="前"/>

		                        <TextBlock Grid.Column="1" Text="{Binding}" />
		                        <TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding RelativeSource={RelativeSource PreviousData}}" />
		                    </Grid>
                        </Border>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </Viewbox>
    </Grid>
</Window>

image
あとは、ItemsControl系のDataTemplate内部でのみ使用できるPreviousData。これも文字通りで、ItemsSourceに指定されたCollectionのひとつ前の情報を取得できます。
# しかし、実際に使ったことはないのですよ。(どこで使うんだろう。。。)
というわけで、Bindingは結構柔軟にあちこちからデータを取得することができます。ぜひ活用してください。

2014年12月8日

dynamicに頼らざるを得ない?

Filed under: .NET — タグ: — yone64 @ 11:12 PM

タイトルわかりにくいですね。

C#のGenericにはWildCardがないので、dynamicに頼らざるを得ないパターンがあると思ってます。
例えば、下記パターン

引数で受け取った、IEnumerableがObservableCollection<T>の場合に、Moveメソッドを呼びたい

「そもそも、非GenericなIEnumerableなんて使うなよ。」って話もなくはないのですが、UI層の各コントロールのプロパティーはItemsControl.ItemsSourceをはじめ、軒並み非Genericなので仕方がないのです。しかも、Moveメソッドの引数・戻値はTに非依存なので、Tが静的に決まらなくても、書きたいですよね。

例えば、↓みたいにかけると、とっても嬉しいのです。

// T を指定しないともちろんコンパイルエラー
if (s is ObservableCollection<>)
{
    ((ObservableCollection<>)s).Move(1,2);
}

でも、無理なものは無理なので、あきらめます。GenericのWildCardはいろいろ難しいらしいし。
で、Typeに問い合わせることになります。ObservableCollectionを継承していることを確認するのが面倒ですが、dynamicが使えると割と短く書けます。

private void Move(IEnumerable s, int oldIndex, int newIndex)
{
    var flag = this.IsGenericSubtype(s.GetType(), typeof(ObservableCollection<>));
  if (flag)
    {
        dynamic d = s;
        d.Move(oldIndex, newIndex);
    }
}

private bool IsGenericSubtype(Type targetType, Type baseType)
{
    while (targetType != null)
    {
        if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == baseType)
        {
            return true;
        }
        targetType = targetType.BaseType;
    }
    return false;
}

これをdynamicを使わずに書いてみようと頑張ると、

  1. GenericなWrapperと非GenericなInterfaceを用意して、
  2. 動的にクラスを生成して
  3. 後は、非GenericなInterfaceを通じてアクセス

という手順になるのではないかと。Wrapperをどこかにキャッシュしておいて、後々使いまわすという用途があれば、こちらのほうが有利になるパターンもあるかもしれません。

public interface IObservableCollectioWrapper
{
    void Move(int oldIndex, int newIndex);
}

public class ObservalbeCollectionWrapper<T> : IObservableCollectioWrapper
{
    public ObservableCollection<T> Source { get; private set; }

    public ObservalbeCollectionWrapper(ObservableCollection<T> source)
    {
        this.Source = source;
    }

    public void Move(int oldIndex, int newIndex)
    {
        Source.Move(oldIndex, newIndex);
    }
}

private void Move(IEnumerable s, int oldIndex, int newIndex)
{
    Type[] paramType;
    var flag = this.IsGenericSubtype(s.GetType(), typeof(ObservableCollection<>), out paramType);
    if (flag)
    {
        var wrapper =
            Activator.CreateInstance(typeof(ObservalbeCollectionWrapper<>).MakeGenericType(paramType), s) as
                IObservableCollectioWrapper;
        wrapper.Move(oldIndex, newIndex);
    }
}

private bool IsGenericSubtype(Type targetType, Type baseType, out Type[] paramType)
{
    paramType = null;
    while (targetType != null)
    {
        if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == baseType)
        {
            paramType = targetType.GetGenericArguments();
            return true;
        }
        targetType = targetType.BaseType;
    }
    return false;
}

昔(C#3.0時代)は、Reflectionの回数を減らすために、こんなコードを多用してたけど、いまならdynamicでいいよね?
ちなみに、自分でクラス設計する場合は、必ず非ジェネリックなIFを用意するように気をつけています。このパターンも、ObservableCollectionがIObservableCollection(ないけど)を実装していればこんな面倒なことには…

2014年12月5日

DataGridとDataTable(その2)【WPF編】

Filed under: .NET, WPF — タグ: , — yone64 @ 9:39 PM

さて、前回の続き。
DataGridとDataTableは非常に相性がよいのですが、「AutoGenerateColumn=”True”」で使う際困った問題があります。
例えば、前回のソースコードを、次のように変更してみます。

var table = new DataTable();
table.Columns.Add("Column1");
table.Columns.Add("Column.2");
table.Columns.Add("Column3");
table.Columns.Add("Column4");

for (int i = 0; i < 20; i++)
{
    var row = table.NewRow();
    for (int j = 0; j < table.Columns.Count; j++)
    {
        row[j] = "[" + (i + 1) + "," + (j + 1) + "]";
    }
    table.Rows.Add(row);
}
this.DataContext = table;

違いは、Column名に「.」が入ったこと。そうすると
キャプチャ
あら不思議。表示されなくなりました。
これは各方面で報告されている(例えば、ココとかココとか)通り で、WPFのBindingPathの解決の仕組みとColumn名がバッティングしているためです。

回避方法として、提案されているのは

  • Unicodeで似ている文字を使う
  • BindingPathを”[ ]”で括る

です。Unicodeで似ている文字を使うのは論外として、”[ ]”で括るのは、このケースにおいては、一見うまくいっているように見えます。しかし、WPFのBindingPathの予約文字と被ったってことは、考慮しないといけないのは”.”だけではないことは容易に想像できますね。例えば、”[“とかがColumn名に含まれると非常にまずいわけです。(この場合は問答無用で例外が発生します。)

つまり、ClassのProperty名と違って使用不可文字のないDataColumn.ColumnNameを使用してBindingPathとすることはそもそも無理があるということになります。

ひとまず、自分の回避策を載せておきますが、どうするのが正解かはわかっていません。

var table = new DataTable();
table.Columns.Add(new DataColumn { ColumnName = "C" + table.Columns.Count, Caption = "Column1" });
table.Columns.Add(new DataColumn { ColumnName = "C" + table.Columns.Count, Caption = "Column.2" });
table.Columns.Add(new DataColumn { ColumnName = "C" + table.Columns.Count, Caption = "Column3" });
table.Columns.Add(new DataColumn { ColumnName = "C" + table.Columns.Count, Caption = "Column4" });

for (int i = 0; i < 20; i++)
{
    var row = table.NewRow();
    for (int j = 0; j < table.Columns.Count; j++)
    {
        row[j] = "[" + (i + 1) + "," + (j + 1) + "]";
    }
    table.Rows.Add(row);
}
this.DataContext = table;
private void DataGrid_OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    var column = e.Column as DataGridBoundColumn;
    var view = (sender as DataGrid).ItemsSource as DataView;
    if (view != null && column != null)
    {
        var dataColumn =
            view.Table.Columns.OfType<DataColumn>().FirstOrDefault(c => c.ColumnName == e.PropertyName);
        if (dataColumn != null)
        {
            column.Header = dataColumn.Caption;
        }
    }
}

ColumnNameは絶対にBindingPathの予約文字とかぶらないように、プログラム的に一意の文字列をふります。そして、Headerに表示したい文字列はCaptionを利用。その後、Column作成時にCaptionをHeaderに代入します。

キャプチャ

ちなみに、DataTableはIndexで各ColumnとBindingができるので、そもそもAutoGenerateColumnでBindingを作成する際は、Indexでの解決にしてくれればよかったのに。。。と思ったりしないわけでもないのですが、どうなんでしょう。
# もしくは、BindingはColumnNameを使って、表示はCaptionを使うとか。

2014年12月4日

DataGridとDataTable【WPF編】

Filed under: .NET, WPF — タグ: — yone64 @ 5:43 PM

DataTableといえば、遠い昔からあるあれですが、WPFでもDataSourceとして使えます。(DataSetは死んでください)

とりわけ、行と列の数が実行時までわかってないデータをDataGridに表示する際に使用するDataSourceとしては、この子より便利な子はまだない気がしています。
# もちろん、あらかじめ行がわかってる場合はObservableCollection<T>がそれなりに便利です。

さっそく、コードを見てみましょう。

var table = new DataTable();
table.Columns.Add("Column1");
table.Columns.Add("Column2");
table.Columns.Add("Column3");
table.Columns.Add("Column4");

for (int i = 0; i < 20; i++)
{
    var row = table.NewRow();
    for (int j = 0; j < table.Columns.Count; j++)
    {
        row[j] = "[" + (i + 1) + "," + (j + 1) + "]";
    }
    table.Rows.Add(row);
}
this.DataContext = table;
<Window x:Class="WpfApplication5.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>
        <DataGrid ItemsSource="{Binding}" AutoGenerateColumns="True" />
    </Grid>
</Window>

上記で、以下のようなダイアログが表示されます。

キャプチャ

いやぁ、簡単ですね。というわけで、DataTableを使おう(前篇)でした。次回は、闇(!?)をお届けします…

2014年12月3日

条件に合致する連続するデータをグループ化する(LINQ)

Filed under: .NET, LINQ — タグ: , — yone64 @ 11:18 PM

タイトルだけ見てもなんこのとやらな感じはしますが、たとえばこんな感じです。

{ 0, 1, 2, 3, 4, 3, 2, 3, 4, 5, 1, 5, 4, 2 }

のような適当数列があった場合に、「3以上の数値」という条件で連続データをグループ化し、

{ { 3, 4, 3 }, { 3, 4, 5 }, { 5, 4 } }

という、数列グループを取得するものをLINQで作りたいと思います。

以前の、連勝数を数えるSQLに近い感じですね。

var source = new[] { 0, 1, 2, 3, 4, 3, 2, 3, 4, 5, 1, 5, 4, 2 };
var result = source.Select((d, i) => new {d, i})
                    .Where(a => a.d >= 3)
                    .Select((a, i) => new {a.d, i = a.i - i})
                    .GroupBy(a => a.i, a => a.d);

var str = "{" + string.Join(",", result.Select(g => " {" + string.Join(",", g.Select(i => " " + i)) + " }")) + " }";
Console.WriteLine(str);

これで、以下のような出力が得られます。

{ { 3, 4, 3 }, { 3, 4, 5 }, { 5, 4 } }

2014年12月1日

Room metro #28 でしゃべってきました。

Filed under: .NET, WPF, 勉強会, 未分類 — タグ: , , , — yone64 @ 11:12 PM

先日行われた、Room metro #28 「XAML Day」で、しゃべってきました。

ネタかぶりしなさそうなWF(Window Workflow Foundation)がらみのはずなのに、まさかのネタかぶりとかありましたが、実際はWPFのコードしか書いてないのでかぶってないはず(

資料は、↓で公開しています。

また、デモに使ったコードは↓です。

https://github.com/yone64/Metro28

2014年7月27日

ReactivePropertyを使いたい人のための、ReactiveExtensions入門(その4)

Filed under: .NET, LINQ, WPF — タグ: , , , , — yone64 @ 11:28 AM

以前のバージョンは→第1回第2回第3回

今回は、PropertyChanged周りを見てみます。Rx分少なめで…。
ReactivePropertyはMVVM周りで使われるため、INotifyPropertyChangedとは切っても切れない関係があります。

var m = new Model();

var io = Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
    h => (s, e) => h(e),
    h => m.PropertyChanged += h,
    h => m.PropertyChanged -= h);

 this.Name = io.Where(e => e.PropertyName == "Name")
    .Select(e => m.Name)
    .ToReactiveProperty(m.Name);

上記のような、PropertyChangedイベントをIObservableに変換して、ToReactivePropertyするコードは何度も書くことになります。
そのため、Utilityメソッドとして定義しておくとよいでしょう。

public static IObservable<PropertyChangedEventArgs> PropertyChangedAsObservable<T>(this T source) where T : INotifyPropertyChanged
{
    return Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
        h => (s, e) => h(e),
        h => source.PropertyChanged -= h,
        h => source.PropertyChanged += h);
}

とりあえず、Event→IO<T>変換拡張メソッドは作りますよね。で、次はPropertyNameでフィルタリングするのですが、文字列で指定するのは、いろいろと嫌なのでExpressionを引数にとれるようにします。

public static IObservable<PropertyChangedEventArgs> PropertyChangedAsObservable<TObj, TProp>(this TObj source, Expression<Func<TObj, TProp>> expression)
    where TObj : INotifyPropertyChanged
{
    var name = ((MemberExpression)expression.Body).Member.Name;

    return Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
        h => (s, e) => h(e),
        h => source.PropertyChanged -= h,
        h => source.PropertyChanged += h)
        .Where(e => e.PropertyName == name);
}

PropertyChangedにおいてはEvetArgsが帰ってきてもうれしいことは少ないので、実際に変更された値を返却するように変更します。引数にデリゲートを追加してもよいのですが、せっかくなので既に引数に追加されているExpressionを利用したいと思います。
Expression<Func<TObj, TProp>>からFunc<TObj,TProp>を作るには、ReflectionとExpression.Compileがありますが、ここは処理速度的に有利なReflectionを使いたいと思います。(dynamicがこの目的では使えないのが残念ですね)

// .net4 まではこちら 
var func = (Func<TObj, TProp>)Delegate.CreateDelegate(typeof(Func<TObj, TProp>), typeof(TObj).GetProperty(name).GetGetMethod());

// PCL だとこっち
var func2 = (Func<TObj, TProp>)typeof(TObj).GetTypeInfo().GetRuntimeProperty(name).GetMethod.CreateDelegate(typeof(Func<TObj, TProp>));

Reflection周りは、ストアアプリ・PCL関連で大きく手が入っているので、.NET4までとPCLとでは書き方が大きく異なります。(.NET4.5だとどっちでもOK)

public static IObservable<TProp> PropertyChangedAsObservable<TObj, TProp>(this TObj source, Expression<Func<TObj, TProp>> expression)
    where TObj : INotifyPropertyChanged
{
    var name = ((MemberExpression)expression.Body).Member.Name;
    var func = (Func<TObj, TProp>)Delegate.CreateDelegate(typeof(Func<TObj, TProp>), typeof(TObj).GetProperty(name).GetGetMethod());

    return Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
        h => (s, e) => h(e),
        h => source.PropertyChanged -= h,
        h => source.PropertyChanged += h)
        .Where(e => e.PropertyName == name)
        .Select(e => func(source));
}

CreateDelegateはそこそこ重い処理なので、実際に使うときはキャッシュしておきましょう。
あとは、ToReactivePropertyの引数で与えていた初期値をRx側で対応します。

public static IObservable<TProp> PropertyChangedAsObservable<TObj, TProp>(this TObj source, Expression<Func<TObj, TProp>> expression)
    where TObj : INotifyPropertyChanged
{
    var name = ((MemberExpression)expression.Body).Member.Name;
    var func = (Func<TObj, TProp>)Delegate.CreateDelegate(typeof(Func<TObj, TProp>), typeof(TObj).GetProperty(name).GetGetMethod());

    return Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
        h => (s, e) => h(e),
        h => source.PropertyChanged -= h,
        h => source.PropertyChanged += h)
        .Where(e => e.PropertyName == name)
        .Select(e => func(source))
        .StartWith(func(source));
}

こういうのを一つ作っておくと、ReactivePropertyの活用がはかどりますね。
とここまで書いて、ReactivePropertyのDLLにも同じようなメソッドがあることに気付きました…

INotifyPropertyChangedExtensions.ObserveProperty

Observable.Deferメソッドで囲んであったり少し違いますが、ほぼほぼ同じですよね。(きっと
# この場合、Deferで囲むとどう動作が変わるか分かってない

2014年7月13日

Reactive Extensions

Filed under: .NET, LINQ — タグ: , — yone64 @ 12:37 AM

Room Metro #26で、ReactiveExtensionsについて発表してきました。
北陸のC#MVPに軽いテクハラを受けたりしましたが、それも今となってはいい思い出。

ソースコード一式はこちらからどうぞ
https://github.com/yone64/RoomMetro26

2014年7月1日

ReactivePropertyを使いたい人のための、ReactiveExtensions入門(その3)

Filed under: .NET, LINQ, WPF — タグ: , , , — yone64 @ 10:21 PM

以前のバージョンは → 第1回第2回
サブタイトルをつけておけばよかったなと思いつつ、適当なサブタイトルが思いつかないので今回もそのまま行きます。

さて、MVVM方式(?)でアプリケーションを作成していると、ViewModelが親子構造になることはよくあります。
で、その場合に親ViewModelが子ViewModelのProperty変更を監視したいということも少なからずあります。
ちなみに、非ReactivePropertyな世界だと、PropertyのSetterにイベントの着脱を仕込むのが一般的なのではないでしょうか(と勝手に思ってます)。

private SubContentViewModel _subContent;
public SubContentViewModel SubContent
{
    get
    {
        return _subContent;
    }
    set
    {
        if (_subContent == value) return;
        if (_subContent != null) _subContent.PropertyChanged -= PropertyChanged;
        _subContent = value;
        if (_subContent != null) _subContent.PropertyChanged += PropertyChanged;
    }
}

これを、ReactivePropertyを利用して実現してみます。何も考えないと以下のようにSelectManyで簡単に実現できるように思えます。
# SubContentとTextはそれぞれReactivePropertyです。

this.SubContent
    .Where(s => s != null)
    .SelectMany(s => s.Text)
    .Subscribe(t => Console.WriteLine(t));

しかし、これだと一度購読を開始したSubContentがいつまでたっても解放されないため、メモリリークなどの原因になってしまいます。
これを以下のように記述することで、親ViewModelから参照されなくなった子ViewModelの変更購読を解放することができるようになります。

this.SubContent
    .Select(s => s == null ? null : s.Text.Subscribe(t => Console.WriteLine(t)))
    .Scan(new Tuple<IDisposable, IDisposable>(null, null), (a, b) => Tuple.Create(a.Item2, b))
    .Subscribe(t => { if (t.Item1 != null) t.Item1.Dispose(); });

長いな…

« Newer PostsOlder Posts »

WordPress.com Blog.