frog.raindrop.jp.knowledge > C# プログラミング

October 28, 2009

TreeView の SelectedItemChanged に Command をバインドしたい。

遅ればせながら MVVM パターンの理解を深めるために WPF でエクスプローラーもどきを作っています。TreeView にディレクトリの木構造が表示され、任意のディレクトリを選択すると、その中のディレクトリとファイルの一覧が ListView に表示される、単純なやつです。

WPF の TreeView って初めて使ったんですけど、SelectedItem プロパティが読み取り専用になってて、Binding を設定したり出来ないんですよね。ViewModel 側で選択されたものを知りたいんだけど、なかなか一筋縄では行かない感じ。TreeView や TreeViewItem のイベントで設定すれば望みのことは出来るだろうけど、できれば View から ViewModel にアクセスせずに実現したい。

なんかいい方法ないかなーと思って調べたら、ちょうど TextBox で Enter キーを叩いたときに Command を実行するようなことをしてるのを見つけました。
添付ビヘイビアでTextBoxにCommandを実装してみた - SharpLab.
添付プロパティ内でイベントハンドラを設定したりするの、添付ビヘイビアって言うんですね。知らなかったです。手法自体は使ったことありましたが。

続きを読む...

May 15, 2009

カンマ区切りされた数字を配列として取り出したりとか。

別にカンマ区切りじゃなくていいんやけど。

class Test
{
    static void Main (string [] args)
    {
        var s = "e0,106,aa,12";
        var regex = new System.Text.RegularExpressions.Regex ("\\b(\\d+)\\b");
        var matches = regex.Matches (s);
        var numbers = matches
            .OfType<System.Text.RegularExpressions.Match> ()
            .Select (match => Convert.ToInt32 (match.Groups [1].Value))
            .ToArray ();
        foreach (var number in numbers)
            Console.WriteLine ("{0}:{1}", number, number.GetType ());
    }
}

March 24, 2009

ヘキサダンプクラス

public class Dump
{
    private byte[] _source;

    #region コンストラクタ

    public Dump (string source)
    {
        _source = Encoding.Default.GetBytes (source);
    }
    public Dump (byte [] source)
    {
        _source = source;
    }
    public Dump (double source)
    {
        _source = BitConverter.GetBytes (source);
    }
    public Dump (float source)
    {
        _source = BitConverter.GetBytes (source);
    }
    public Dump (ulong source)
    {
        _source = BitConverter.GetBytes (source);
    }
    public Dump (uint source)
    {
        _source = BitConverter.GetBytes (source);
    }
    public Dump (ushort source)
    {
        _source = BitConverter.GetBytes (source);
    }
    public Dump (long source)
    {
        _source = BitConverter.GetBytes (source);
    }
    public Dump (int source)
    {
        _source = BitConverter.GetBytes (source);
    }
    public Dump (short source)
    {
        _source = BitConverter.GetBytes (source);
    }
    public Dump (char source)
    {
        _source = BitConverter.GetBytes (source);
    }
    public Dump (bool source)
    {
        _source = BitConverter.GetBytes (source);
    }
    #endregion


    public void Write ()
    {
        Console.WriteLine ("     +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F 0123456789ABCDEF");
        StringBuilder builder = new StringBuilder ();
        for (int i = 0; i < _source.Length; i++)
        {
            if ((i % 16) == 0)
            {
                if (i != 0)
                {
                    Console.Write (builder.ToString ());
                    var ascii = _source.Skip (i - 16).Take (16).Select (b => b < (byte) 0x20 ? (byte) 0x2e : b).ToArray ();
                    Console.WriteLine (Encoding.Default.GetString (ascii));
                }
                builder = new StringBuilder ();
                builder.AppendFormat ("{0:X4} ", i);
            }
            builder.AppendFormat ("{0:X2} ", _source [i]);
        }
        var remain = _source.Length % 16;
        if (remain != 0)
        {
            Console.Write ("{0,-53}", builder.ToString ());
            var ascii = _source.Skip (_source.Length - remain).Take (remain).Select (b => b < (byte) 0x20 ? (byte) 0x2e : b).ToArray ();
            Console.WriteLine (Encoding.Default.GetString (ascii));
        }
    }
}

March 19, 2009

環状キュークラス

ジェネリックな環状キューを作ってます。改善点はいろいろありますが、System.Collection.Generic.Queue(T) クラスとメンバ名の同期が取れていないのが一番の改善点でしょうか。なんとなく、ATL の std::queue のほうが頭にあったため、中途半端に混ざってます。

詳細とか改善とか、また書きます (多分)

/// <summary>
/// 環状キュークラス
/// </summary>
/// <typeparam name="TSource">キューの要素</typeparam>

public class RingQueue<TSource> : IRingQueue<TSource>
{
    protected List<TSource> _collection;
    protected int _currentPos = 0;


    /// <summary>
    /// コンストラクタ
    /// </summary>

    public RingQueue ()
    {
        _collection = new List<TSource> ();
        _currentPos = -1;
    }


    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="collection">新しい RingQueue(T) にコピーする要素のコレクション</param>

    public RingQueue (IEnumerable<TSource> collection)
    {
        _collection = new List<TSource> (collection);
    }


    #region IRingQueue<TSource> メンバ

    /// <summary>
    /// キューに要素を追加する。
    /// </summary>
    /// <param name="item">追加する要素</param>

    public void Push (TSource item)
    {
        lock (_collection)
        {
            //HACK: ゼロを判断しなくてよくならないか?
            var count = _collection.Count;
            if (count == 0)
            {
                _collection.Add (item);
                _currentPos = 0;
            }
            else
            {
                var insertPos = (_currentPos + count - 1) % count + 1;
                _collection.Insert (insertPos, item);
                _currentPos = (insertPos + 1) % _collection.Count;
            }
        }
    }


    /// <summary>
    /// RingQueue(T) の現在の要素を削除して返す。
    /// </summary>
    /// <returns>RingQueue(T) 現在の要素</returns>

    public TSource Pop ()
    {
        lock (_collection)
        {
            //HACK: 要素がない場合の例外が何が返るか確認のこと。適切でなければ再スローすること。
            var item = Peek ();
            _collection.RemoveAt (_currentPos);
            if (_collection.Count == 0)
                _currentPos = -1;
            else
                _currentPos = _currentPos % _collection.Count;
            return item;
        }
    }


    /// <summary>
    /// RingQueue(T) の現在の要素を削除せずに返す。
    /// </summary>
    /// <returns>RingQueue(T) 現在の要素</returns>

    public TSource Peek ()
    {
        //HACK: 要素がない場合の例外が何が返るか確認のこと。適切でなければ再スローすること。
        return _collection [_currentPos];
    }


    /// <summary>
    /// RingQueue(T) を次の要素に進める。
    /// </summary>

    public void MoveNext ()
    {
        if (IsEmpty)
            throw new InvalidOperationException ("キューに要素がありません。");
        _currentPos = (_currentPos + 1) % _collection.Count;
    }


    /// <summary>
    /// RingQueue(T) からすべての要素を削除する。
    /// </summary>

    public void Clear ()
    {
        _collection.Clear ();
        _currentPos = -1;
    }


    /// <summary>
    /// RingQueue(T) に格納されている要素の数を取得する。
    /// </summary>

    public int Count
    {
        get { return _collection.Count; }
    }


    /// <summary>
    /// RingQueue(T) が空かどうかを示す値を取得する。
    /// </summary>

    public bool IsEmpty
    {
        get { return _collection.Count == 0; }
    }

    #endregion


    /// <summary>
    /// 指定したコレクションの要素を RingQueue(T) の末尾に追加する。
    /// </summary>
    /// <param name="collection">追加する要素を含むコレクション</param>

    public void PushRange (IEnumerable<TSource> collection)
    {
        lock (_collection)
        {
            //HACK: ゼロを判断しなくてよくならないか?
            var count = _collection.Count;
            if (count == 0)
            {
                _collection.AddRange (collection);
                _currentPos = 0;
            }
            else
            {
                var addCount = collection.Count ();
                var insertPos = (_currentPos + count - 1) % count + 1;
                _collection.InsertRange (insertPos, collection);
                _currentPos = (insertPos + addCount) % _collection.Count;
            }
        }
    }

    #region IEnumerable<TSource> メンバ

    /// <summary>
    /// RingQueue(T) を反復処理する列挙子を返す。
    /// </summary>
    /// <returns>コレクションの列挙子</returns>

    public IEnumerator<TSource> GetEnumerator ()
    {
        return _collection.Skip (_currentPos).Concat (_collection.Take (_currentPos)).GetEnumerator ();
    }

    #endregion

    #region IEnumerable メンバ

    /// <summary>
    /// RingQueue(T) を反復処理する列挙子を返す。
    /// </summary>
    /// <returns>コレクションの列挙子</returns>

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
    {
        return GetEnumerator ();
    }

    #endregion
}
続きを読む...

March 13, 2009

プロパティのデリゲート

プロパティのデリゲート欲しい人ー !! みんな欲しくないのー !? 欲しいよねー !?

主に GUI にかかわるプロパティを別スレッドから操作したい場合だと思うんだけど、プロパティ更新するだけのメソッドを用意したりして、なんだかなぁと。試しに以下のように Person クラスを作って、そのメンバを調べてみた。

namespace TestProgram
{
    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public void SayHallo (Person you)
        {
            // 君に挨拶するよ !
            Console.WriteLine (string.Format ("Hello ! {0}, I am {1};", you.Name, this.Name));
        }
    }

    class Test
    {
        static void Main (string [] args)
        {
            var personType = typeof (Person);
            foreach (var member in personType.GetMembers ())
            {
                Console.WriteLine (string.Format ("{0,-12} {1,-24} {2,-12}", member.Name, member.DeclaringType, member.MemberType));
            }
        }
    }
}

実行結果はこんな感じ。へっへ、欲しいもん、ありましたぜダンナ。

get_Name     TestProgram.Person       Method
set_Name     TestProgram.Person       Method
get_Age      TestProgram.Person       Method
set_Age      TestProgram.Person       Method
SayHallo     TestProgram.Person       Method
ToString     System.Object            Method
Equals       System.Object            Method
GetHashCode  System.Object            Method
GetType      System.Object            Method
.ctor        TestProgram.Person       Constructor
Name         TestProgram.Person       Property
Age          TestProgram.Person       Property
続行するには何かキーを押してください . . .

さらに、メソッドの宣言を取得してみた。

static void Main (string [] args)
{
    var personType = typeof (Person);
    foreach (var member in personType.GetMembers ())
    {
        //Console.WriteLine (string.Format ("{0,-12} {1,-24} {2,-12}", member.Name, member.DeclaringType, member.MemberType));
        if (member.MemberType == System.Reflection.MemberTypes.Method)
        {
            var method = personType.GetMethod (member.Name);
            if (method != null)
                Console.WriteLine (string.Format ("{0}", method.GetBaseDefinition ()));
        }
    }
}

実行結果。

System.String get_Name()
Void set_Name(System.String)
Int32 get_Age()
Void set_Age(Int32)
Void SayHallo(TestProgram.Person)
System.String ToString()
Boolean Equals(System.Object)
Int32 GetHashCode()
System.Type GetType()
続行するには何かキーを押してください . . .

なので、プロパティの型と名称が分かれば、こんな拡張メソッドでプロパティのデリゲートが作成できますね。

public static class DelegateExtention
{
    public static Action<TValue> CreatePropertySetDelegate<TValue> (this object obj, string propertyName)
    {
        return Delegate.CreateDelegate (typeof (Action<TValue>), obj, "set_" + propertyName) as Action<TValue>;
    }

    public static Func<TValue> CreatePropertyGetDelegate<TValue> (this object obj, string propertyName)
    {
        return Delegate.CreateDelegate (typeof (Func<TValue>), obj, "get_" + propertyName) as Func<TValue>;
    }
}

March 12, 2009

DoEvents

DoEvents っていうと、VB6 時代に使ったなーとか妙に懐かしいです。C とか C++ だとこんな実装をしてました。

void DoEvents (void)
{
    MSG msg;

    while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
    {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }
}

要するに、メインのメッセージループの内側で、同期的にメッセージループを実行してメッセージキュー内にたまってるメッセージを捌かせるもので、主に同期処理の合間に GUI が固まらないようにする目的で使うやつです。Borland 系では Application->ProcessMessages () がおんなじ様な実装になってたんだと思います。多分。

WPF では DispatcherFrame クラスがメッセージループを表わすらしく、Dispatcher.PushFrame でそれを実行できます。たまってたメッセージを処理し終えた際にループを終了する必要がありますが、ディスパッチスレッドに優先度の低い処理としてメッセージループの終了を登録しておくことで実現します。

実際の実装方法は MSDN のあちこちにありますが、関数一個にした版をとりあえず上げときます。

/// <summary>
/// 現在メッセージ待ち行列の中にある全てのUIメッセージを処理します。
/// </summary>

public static void DoEvents ()
{
    // うちっかわの DispatcherFrame を作成
    var nestedFrame = new DispatcherFrame ();

    // DispatcherFrame (= 実行ループ) を終了させるコールバック
    DispatcherOperationCallback callback = (frame) => { ((DispatcherFrame) frame).Continue = false; return null;

    // 非同期で実行する
    // 優先度を Background にしているので、このコールバックは
    // ほかに処理するメッセージがなくなったら実行される
    var operation = Dispatcher.CurrentDispatcher.BeginInvoke (DispatcherPriority.Background, callback, nestedFra

    // うちっかわの実行ループを開始する
    Dispatcher.PushFrame (nestedFrame);

    // コールバックのオペレーションが完了していない場合、強制終了する
    if (operation.Status != DispatcherOperationStatus.Completed)
    {
        operation.Abort ();
    }
}

0 個と 1 個

// InvalidOperationException "シーケンスに要素が含まれていません" となる
var s1 = Enumerable.Empty<string> ().Aggregate ((work, next) => work + "," + next);

// これは大丈夫
var s2 = Enumerable.Repeat ("a", 1).Aggregate ((work, next) => work + "," + next);

March 6, 2009

ItemsControl の要素を BindingGroup を使用して一括検証 & 更新する

validate.png

設定ダイアログなんかで、OK ボタンで一括チェックを行って反映、みたいなのを作る場合、個々の Binding に UpdateSourceTrigger="Explicit" を設定しておき、.NET Framework 3.5 から追加された BindingGroup で ValidationRule をまとめて走らせる実装をしてる。でも、ItemsControl の ItemTemplate で Binding を定義した場合、それらの Binding はそのままでは BindingGroup には入らないみたい。

BindingGroup が使えないなんて、項目ごとの BindingExpression を取得して更新するしかないの ? んなわけないよね ? と思って試行錯誤してみた結果、BindingGroup に Name プロパティを設定しておき、同時に更新したい Binding の BindingGroupName プロパティにその値を設定しておくと追加されることが分かった。
※ もちろん、同一のデータソースのものしか追加されないよ

一応、それなりの動作はするんだけど、なぜか最後の項目から順番に ValidationRule が走ってしまう。そんなもんだっけ ?

その他、気づいたこととか、メモとか。

  • BindingGroup と 個々の Binding の両方にうっかり Validation.Error ハンドラを設定してしまうと、両方走ってしまってえらい格好悪い (当たり前か)
  • ひょっとすると、一個の ValidationRule で引っかかった場合に以降の Validation を実行しない方法があるんじゃないか (調査中)
  • 個々の Binding に設定した ValidationRule の ValidationStep プロパティをうっかり UpdatedValue にしてしまうと、ValidationRule::Validate に渡される value は BindingExpression になる。(しかも更新後にわたってくるので意味がない)
  • 値の一意性をチェックするときなんかは、BindingGroup に ValidationStep を UpdatedValue に設定した ValidationRule を仕込んでおくとよい。
    (その場合は BindingGroup のトランザクション機能を利用する必要がある。IEditableObject を実装するんが面倒だけど ><)

というわけで、サンプル。

続きを読む...

March 5, 2009

テキストボックスのフォーカス取得時にテキストを選択する

selectongotfocus.png

こうですか !? わかりません ! w

<Window x:Class="WpfSample.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="テキストボックスのフォーカス取得時にテキストを選択する"
        SizeToContent="Height" Width="300"
        PreviewGotKeyboardFocus="Window_PreviewGotKeyboardFocus">
    <Window.Resources>
        <Style TargetType="Label">
            <Setter Property="Margin" Value="5"/>
            <Setter Property="ContentStringFormat" Value="{}{0}:"/>
        </Style>
        <Style TargetType="TextBox">
            <Setter Property="Margin" Value="5"/>
        </Style>
    </Window.Resources>
    <Grid Margin="5">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <Label Grid.Column="0" Grid.Row="0" Content="名前"/>
        <TextBox Grid.Column="1" Grid.Row="0"/>

        <Label Grid.Column="0" Grid.Row="1" Content="住所"/>
        <TextBox Grid.Column="1" Grid.Row="1"/>

        <Label Grid.Column="0" Grid.Row="2" Content="電話番号"/>
        <TextBox Grid.Column="1" Grid.Row="2"/>

        <Label Grid.Column="0" Grid.Row="3" Content="E-Mail"/>
        <TextBox Grid.Column="1" Grid.Row="3"/>
    </Grid>
</Window>
namespace WpfSample
{
    /// <summary>
    /// Window1.xaml の相互作用ロジック
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1 ()
        {
            InitializeComponent ();
        }

        private void Window_PreviewGotKeyboardFocus (object sender, KeyboardFocusChangedEventArgs e)
        {
            var textBox = e.NewFocus as TextBox;
            if (textBox != null)
                textBox.SelectAll ();
        }
    }
}

March 4, 2009

オブジェクトをシーケンスとして返す拡張メソッド

単一のオブジェクトからシーケンスを作成したい場合ありますよね。引数にシーケンスを取る関数に渡すとか、単一オブジェクトもコレクションも同じ処理をしたいとか…。

なので、こんな拡張メソッドを作っとくと便利かも。Enumerable のメンバとしてほしいくらい !

namespace ExtensionMethods
{
    static class EnumerableExtension
    {
        public static IEnumerable<TSource> ToSequence<TSource> (this TSource source)
        {
            yield return source;
        }
    }

    class Person
    {
        static void Main (string [] args)
        {
            var person = new Person ();
            IEnumerable<Person> people = person.ToSequence ();
        }
    }
}

標準にないのかな…。
-------------
IEnumerable<Person> people = Enumerable.Repeat (person, 1);
ならありますね。

IPv4 でマルチキャストに送出するパケットの TTL を設定する。

IPv4 でマルチキャストのパケットを送出する際、デフォルトのままでは TTL が 1 になってしまう。ルータ越えの必要がある場合は以下のようにして、送信に使用するソケットのオプションを変更する。(下記は TTL を 32 に変更した例)

// #include <Winsock2.h>
int ttl = 32;
int result = setsockopt (socket, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&ttl, sizeof ttl);
if (SOCKET_ERROR == result)
{
    // エラー処理は省略...
}

C# の場合はこんなの。

// using System.Net.Sockets;
int ttl = 32;
socket.SetSocketOption (SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, ttl);

February 25, 2009

ジェネリックの型推論がイマイチいけてない気がする。

class A { }
class B : A { }

class Sample
{
    static void Method1<T> (T obj) where T : A { Method2 (obj); }

    static void Method2 (B obj) { Console.WriteLine ("obj is B"); }

    static void Method2 (A obj) { Console.WriteLine ("obj is A"); }

    static void Main (string [] args)
    {
        var obj = new B ();
        Method1 (obj);
    }
}

上記の実行結果はどうなるでしょうか。

こたえ。

obj is A
続行するには何かキーを押してください . . .


うーん、イマイチ…。

February 16, 2009

空の IEnumerable<T> を返す

戻り値が IEnumerable<T> 型の関数で、null でなく、空のシーケンスを返す方法がわかりませんでした。
Enumerable.Empty<TResult> メソッドってのがあるんですね !

return Enumerable.Empty<Person> ();

February 12, 2009

Enumerable.Aggregate メソッドのオーバーロードを整理してみたい。

Enumerable.Aggregate メソッド には、オーバーロードが 3 つあって、使いどころが若干違うように思います。

public static TSource Aggregate<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, TSource, TSource> func
)

一番シンプルなやつ。

string[] beans = { "ささげ", "いんげん", "そらまめ", "えんどう", "だいず", };
Console.WriteLine (
    beans.Aggregate ((work, next) => work + "/" + next)
    );

ポイントは、func が実行される回数が、source.Count () - 1 回だということ。上の例では一発目は work が "ささげ"、next が "いんげん" となります。区切り文字を入れて文字列を連結したりするのにぴったり。

public static TAccumulate Aggregate<TSource, TAccumulate>(
    this IEnumerable<TSource> source,
    TAccumulate seed,
    Func<TAccumulate, TSource, TAccumulate> func
)

初期値のあるやつ。

string[] beans = { "ささげ", "いんげん", "そらまめ", "えんどう", "だいず", };
Console.WriteLine (
    beans.Aggregate (0, (work, next) => work += next.Length)
    );

func は source.Count () 回実行されます。例の一発目は work が 0、next が "ささげ" です。全要素に対して同様の評価を行う必要がある場合に向いてると思います。

public static TResult Aggregate<TSource, TAccumulate, TResult>(
    this IEnumerable<TSource> source,
    TAccumulate seed,
    Func<TAccumulate, TSource, TAccumulate> func,
    Func<TAccumulate, TResult> resultSelector
)

初期値があり、さらに結果に対してごにょごにょすることができるやつ。

string[] beans = { "ささげ", "いんげん", "そらまめ", "えんどう", "だいず", };
Console.WriteLine (
    beans.Aggregate (
        new StringBuilder (), (work, next) => work.AppendFormat ("/{0}類", next), builder => builder.ToString ())
    );

初期値のあるやつと同じで、func は source.Count () 回実行されます。例の一発目は work が StringBuilder ("")、next が "ささげ" です。例はちょっとミスってて、区切りのスラッシュが一個余分な感じです。

February 5, 2009

System.Type より、型のインスタンスを生成する

System.Type から型のインスタンスを作成する方法があるはずだと思ってましたが、実際に必要になったので探してみました。System.Activator クラスCreateInstance メソッドで、型からインスタンスを作成することができます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CSharpSample
{
    class Sample
    {
        static void Main (string [] args)
        {
            var sample = new Sample ();
            var obj = Activator.CreateInstance (sample.GetType ());
            Console.Write ("{0}\n", obj);
        }
    }
}

実行結果です。

CSharpSample.Sample
続行するには何かキーを押してください . . .

CreateInstance メソッドには、なにやらいっぱいオーバーロードがあります。また読んどくことにします。

January 28, 2009

NULL 許容型 (Nullable)

null 許容型って、間違えやすいけど参照型になるわけじゃないんだ。値型だけど null 値を持ちうるようになるだけ。

だからこんなプログラムの実行結果は…

struct Data
{
    public string str;
    public int number;
}

class Program
{
    static void Main (string [] args)
    {
        Data data1;
        Data? data2 = null;

        data1.number = 1;
        data1.str = "最初の値";

        data2 = data1;
        Console.WriteLine ("{0}:{1}", data2.Value.number, data2.Value.str);

        data1.number = 2;
        data1.str = "値を変更してみた";

        Console.WriteLine ("{0}:{1}", data2.Value.number, data2.Value.str);
    }
}

こうなる。

1:最初の値
1:最初の値
続行するには何かキーを押してください . . .

それと、null を代入しても、おんなじだけのスタックを消費します。値型だから。

連番のプロパティを持つ Items を Grid 上に整列させるコンバータ

public class NumberGridConverter: DependencyObject, IValueConverter
{
    /// <summary>
    /// <c>BaseNumber</c> 依存関係プロパティ
    /// </summary>
    /// <value><c>NumberGridConverter.BaseNumber</c> 属性値</value>
    public int BaseNumber
    {
        get { return (int) GetValue (BaseNumberProperty); }
        set { SetValue (BaseNumberProperty, value); }
    }

    /// <summary><c>BaseNumber</c> 依存関係プロパティを識別します。</summary>
    public static readonly DependencyProperty BaseNumberProperty =
        DependencyProperty.Register ("BaseNumber", typeof (int), typeof (NumberGridConverter), new FrameworkPropertyMetadata ((int) 0, FrameworkPropertyMetadataOptions.AffectsRender));

    /// <summary>
    /// <c>Columns</c> 依存関係プロパティ
    /// </summary>
    /// <value><c>NumberGridConverter.Columns</c> 属性値</value>
    public int Columns
    {
        get { return (int) GetValue (ColumnsProperty); }
        set { SetValue (ColumnsProperty, value); }
    }

    /// <summary><c>Columns</c> 依存関係プロパティを識別します。</summary>
    public static readonly DependencyProperty ColumnsProperty =
        DependencyProperty.Register ("Columns", typeof (int), typeof (NumberGridConverter), new FrameworkPropertyMetadata ((int) 1, FrameworkPropertyMetadataOptions.AffectsRender));

    /// <summary>
    /// <c>Rows</c> 依存関係プロパティ
    /// </summary>
    /// <value><c>NumberGridConverter.Rows</c> 属性値</value>
    public int Rows
    {
        get { return (int) GetValue (RowsProperty); }
        set { SetValue (RowsProperty, value); }
    }

    /// <summary><c>Rows</c> 依存関係プロパティを識別します。</summary>
    public static readonly DependencyProperty RowsProperty =
        DependencyProperty.Register ("Rows", typeof (int), typeof (NumberGridConverter), new FrameworkPropertyMetadata ((int) 1, FrameworkPropertyMetadataOptions.AffectsRender));

    /// <summary>
    /// <c>Orientation</c> 依存関係プロパティ
    /// </summary>
    /// <value><c>NumberGridConverter.Orientation</c> 属性値</value>
    public System.Windows.Controls.Orientation Orientation
    {
        get { return (System.Windows.Controls.Orientation) GetValue (OrientationProperty); }
        set { SetValue (OrientationProperty, value); }
    }

    /// <summary><c>Orientation</c> 依存関係プロパティを識別します。</summary>
    public static readonly DependencyProperty OrientationProperty =
        DependencyProperty.Register ("Orientation", typeof (System.Windows.Controls.Orientation), typeof (NumberGridConverter), new FrameworkPropertyMetadata (System.Windows.Controls.Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsRender));


    #region IValueConverter メンバ

    public object Convert (object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var param = parameter as string;
        var number = (int) value - this.BaseNumber;
        if (param != null)
        {
            switch (param.ToUpper ())
            {
                case "COLUMN":
                    switch (this.Orientation)
                    {
                        case System.Windows.Controls.Orientation.Horizontal:
                            return (number % this.Columns);
                        case System.Windows.Controls.Orientation.Vertical:
                            return (number / this.Rows);
                        default:
                            break;
                    }
                    break;

                case "ROW":
                    switch (this.Orientation)
                    {
                        case System.Windows.Controls.Orientation.Horizontal:
                            return (number / this.Columns);
                        case System.Windows.Controls.Orientation.Vertical:
                            return (number % this.Rows);
                        default:
                            break;
                    }
                    break;

                default:
                    break;
            }
        }
        return DependencyProperty.UnsetValue;
    }

    public object ConvertBack (object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException ();
    }

    #endregion
}

January 27, 2009

Binding の StringFormat プロパティのカルチャって ?

String.Format () でカルチャに依存する書式を指定した場合、カルチャを指定しないとそのスレッドのカルチャ情報が使用されるらしいです。具体的には System.Threading.Thread.CurrentThread.CurrentCulture です。

が、BindingBase の StringFormat で指定した場合、カレントスレッドのカルチャ情報と異なるように見えますがー。なんか私間違ってますかー ? 何か指定がいるのー ?

Binding の結果
String.Format の結果

<Window
    x:Class="SampleApp.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    xmlns:th="clr-namespace:System.Threading;assembly=mscorlib"
    Title="Binding の StringFormat のカルチャって?"
    SizeToContent="WidthAndHeight">
    <StackPanel Grid.IsSharedSizeScope="True">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" SharedSizeGroup="Caption"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Margin="5" Text="StringFormat なし"/>
            <TextBlock Grid.Column="1" Margin="5" Text="{Binding Source={x:Static sys:DateTime.Now}}"/>
        </Grid>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" SharedSizeGroup="Caption"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Margin="5" Text="StringFormat あり"/>
            <TextBlock Grid.Column="1" Margin="5" Text="{Binding Source={x:Static sys:DateTime.Now}, StringFormat={}{0:F}}"/>
        </Grid>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" SharedSizeGroup="Caption"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Margin="5" Text="CurrentUICulture の設定"/>
            <TextBlock Grid.Column="1" Margin="5" Text="{Binding Source={x:Static th:Thread.CurrentThread}, Path=CurrentUICulture.DateTimeFormat.FullDateTimePattern}"/>
        </Grid>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" SharedSizeGroup="Caption"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Margin="5" Text="CurrentCulture の設定"/>
            <TextBlock Grid.Column="1" Margin="5" Text="{Binding Source={x:Static th:Thread.CurrentThread}, Path=CurrentCulture.DateTimeFormat.FullDateTimePattern}"/>
        </Grid>
        <Button Margin="5" Click="Button_Click">String.Format の結果</Button>
    </StackPanel>
</Window>
namespace SampleApp
{
    /// <summary>
    /// Window1.xaml の相互作用ロジック
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1 ()
        {
            InitializeComponent ();
        }
 
        private void Button_Click (object sender, RoutedEventArgs e)
        {
            MessageBox.Show (String.Format ("{0:F}", System.DateTime.Now));
        }
    }
}

January 23, 2009

続:最大化ボタン・最小化ボタン・システムメニューのないウインドウ

最大化ボタン・最小化ボタン・システムメニューのないウインドウの実装を考え中。

これもありか?

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

namespace WindowStyleProject { public abstract class StyledWindow : Window { const int GWL_STYLE = -16;
/// <summary> /// ウインドウスタイルを上書きする /// 継承先でオーバーライドする /// </summary> /// <param name="windowStyle">現在のウインドウスタイル</param> /// <returns>新たに設定するウインドウスタイル</returns> protected virtual WindowStyleEnum OverrideWindowStyle (WindowStyleEnum windowStyle) { return windowStyle; // デフォルトは変更なし }
/// <summary> /// ウインドウスタイルを設定する /// </summary> /// <param name="e"></param> protected override void OnSourceInitialized (EventArgs e) { base.OnSourceInitialized (e);
// 現在のウインドウスタイルを取得 IntPtr handle = (new WindowInteropHelper (this)).Handle; var originalWindowStyle = (WindowStyleEnum) GetWindowLong (handle, GWL_STYLE);
// 継承先で更新された値を取得 var newWindowStyle = this.OverrideWindowStyle (originalWindowStyle);
// 上書き if (originalWindowStyle != newWindowStyle) SetWindowLong (handle, GWL_STYLE, (uint) newWindowStyle); }
[DllImport ("user32.dll")] static extern uint GetWindowLong (IntPtr hWnd, int nIndex);
[DllImport ("user32.dll")] static extern uint SetWindowLong (IntPtr hWnd, int nIndex, uint dwNewLong);
}
public enum WindowStyleEnum : uint { // // Window Styles // WS_OVERLAPPED = 0x00000000, WS_POPUP = 0x80000000, WS_CHILD = 0x40000000, WS_MINIMIZE = 0x20000000, WS_VISIBLE = 0x10000000, WS_DISABLED = 0x08000000, WS_CLIPSIBLINGS = 0x04000000, WS_CLIPCHILDREN = 0x02000000, WS_MAXIMIZE = 0x01000000, WS_CAPTION = 0x00C00000, // WS_BORDER | WS_DLGFRAME WS_BORDER = 0x00800000, WS_DLGFRAME = 0x00400000, WS_VSCROLL = 0x00200000, WS_HSCROLL = 0x00100000, WS_SYSMENU = 0x00080000, WS_THICKFRAME = 0x00040000, WS_GROUP = 0x00020000, WS_TABSTOP = 0x00010000,
WS_MINIMIZEBOX = 0x00020000, WS_MAXIMIZEBOX = 0x00010000,
WS_TILED = WS_OVERLAPPED, WS_ICONIC = WS_MINIMIZE, WS_SIZEBOX = WS_THICKFRAME, WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW,
// // Common Window Styles // WS_OVERLAPPEDWINDOW = (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX),
WS_POPUPWINDOW = (WS_POPUP | WS_BORDER | WS_SYSMENU),
WS_CHILDWINDOW = (WS_CHILD), }
}

使い方は、StyledWindow を継承して、OverrideWindowStyle をオーバーライドする。

// Window1.xaml
namespace WindowStyleProject
{
    /// <summary>
    /// Window1.xaml の相互作用ロジック
    /// </summary>
    public partial class Window1 : StyledWindow
    {
        public Window1 ()
        {
            InitializeComponent ();
        }

protected override WindowStyleEnum OverrideWindowStyle (WindowStyleEnum windowStyle) { return windowStyle & (~WindowStyleEnum.WS_SYSMENU); } } }

あーでも、XAML のほうも書き換えなきゃな。これがうざい。

<local:StyledWindow x:Class="WindowStyleProject.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WindowStyleProject"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <!-- 中略 -->
    </Grid>
</local:StyledWindow>

January 22, 2009

開くダイアログ

ファイルを開くダイアログの WPF 版は、Microsoft.Win32.OpenFileDialog クラス

private void Button_Click (object sender, RoutedEventArgs e)
{
    var openFileDialog = new Microsoft.Win32.OpenFileDialog ();
    openFileDialog.Filter = "C# ソースファイル|*.cs|すべてのファイル|*.*";
    openFileDialog.InitialDirectory = System.Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
    openFileDialog.CheckFileExists = true;
    openFileDialog.Multiselect = true;
    openFileDialog.ShowReadOnly = false;
    openFileDialog.Title = "ソースファイルの選択";
    var result= openFileDialog.ShowDialog ();
    if (result ?? false)
    {
        MessageBox.Show (openFileDialog.FileNames.Aggregate ((str, nextstr) => str + "\n" + nextstr));
    }
}

上の例は複数ファイルを選択できるダイアログを表示するが、選択された結果は FileNames プロパティより string[] 型として取得できる。ちゃんとフルパス。でも、このダイアログは Vista で実行しても XP スタイルになっちゃうらしい (未確認)

保存するほうは、同様に SaveFileDialog クラスというのがある。