frog.raindrop.jp.knowledge > WPF (Windows Presentation Foundation)

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.
添付プロパティ内でイベントハンドラを設定したりするの、添付ビヘイビアって言うんですね。知らなかったです。手法自体は使ったことありましたが。

続きを読む...

July 2, 2009

TextBox に長さ制限を設ける

WPF の TextBox の MaxLength が TSF 環境下で使い物にならない件ですが、Feedback への Microsoft からの回答によると、次期バージョンで何とかなる、のかな?

とはいえ、今抱えてる案件は .NET Framework 3.5 で対応しなきゃいけないわけで。添付プロパティを使って無理矢理対応してみました。時間があったらまた詳しく書きます。
※ バイト数単位で切ることはできません。文字数単位です。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace MaxLength
{
    public class LengthCheck
    {
        /// <summary>
        /// 指定された System.Windows.Controls.TextBox から LengthCheck.MaxLength 添付プロパティの値を取得します。
        /// </summary>
        /// <param name="textBox">プロパティ値の読み取り元の要素。</param>
        /// <returns>LengthCheck.MaxLength 添付プロパティの値。</returns>

        public static int GetMaxLength (TextBox textBox)
        {
            return (int) textBox.GetValue (MaxLengthProperty);
        }


        /// <summary>
        /// LengthCheck.MaxLength 添付プロパティの値を、指定された System.Windows.Controls.TextBox に設定します。
        /// </summary>
        /// <param name="textBox">添付プロパティを設定する要素。</param>
        /// <param name="value">設定するプロパティ値。</param>

        public static void SetMaxLength (TextBox textBox, int value)
        {
            textBox.SetValue (MaxLengthProperty, value);
        }


        /// <summary>
        /// LengthCheck.MaxLength 添付プロパティを識別します。
        /// </summary>

        public static readonly DependencyProperty MaxLengthProperty =
            DependencyProperty.RegisterAttached ("MaxLength", typeof (int), typeof (LengthCheck), new FrameworkPropertyMetadata (0, new PropertyChangedCallback (MaxLengthProperty_Changed)));


        /// <summary>
        /// LengthCheck.MaxLength 添付プロパティの値が変更された際に、コントロール側にイベントハンドラを設定します。
        /// </summary>
        /// <param name="d">添付プロパティが設定された要素。</param>
        /// <param name="e">イベントの引数。</param>

        static void MaxLengthProperty_Changed (DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var textBox = d as TextBox;

            if (textBox != null)
            {
                try
                {
                    int newValue = (int) e.NewValue;

                    // MaxLength プロパティを設定する。
                    textBox.MaxLength = newValue;
                    if (newValue == 0)
                    {
                        // イベントハンドラ解除
                        TextCompositionManager.RemoveTextInputStartHandler (d, new TextCompositionEventHandler (OnTextInputStart));
                        TextCompositionManager.RemovePreviewTextInputHandler (d, new TextCompositionEventHandler (OnPreviewTextInput));
                    }
                    else
                    {
                        // イベントハンドラ設定
                        TextCompositionManager.AddTextInputStartHandler (d, new TextCompositionEventHandler (OnTextInputStart));
                        TextCompositionManager.AddPreviewTextInputHandler (d, new TextCompositionEventHandler (OnPreviewTextInput));
                    }
                }
                catch
                {// 無視する
                }
            }
        }


        /// <summary>
        /// 新しい TextComposition が開始されると発生します。
        /// </summary>
        /// <param name="sender">イベント ハンドラがアタッチされているオブジェクト。</param>
        /// <param name="e">イベントのデータ。</param>

        static void OnTextInputStart (object sender, TextCompositionEventArgs e)
        {
            // IME が ON の場合は MaxLength を無効にする。

            var ime = InputMethod.Current;
            var textBox = e.Source as TextBox;
            if (textBox != null && ime.ImeState == InputMethodState.On)
                textBox.MaxLength = 0;

        }


        /// <summary>
        /// TextComposition が完了したときに発生します。
        /// </summary>
        /// <param name="sender">イベント ハンドラがアタッチされているオブジェクト。</param>
        /// <param name="e">イベントのデータ。</param>

        static void OnPreviewTextInput (object sender, TextCompositionEventArgs e)
        {
            // 入力確定時に MaxLength を再設定する。

            var ime = InputMethod.Current;
            var textBox = e.Source as TextBox;
            if (textBox != null && ime.ImeState == InputMethodState.On)
            {
                textBox.MaxLength = GetMaxLength (textBox);
                textBox.Text = textBox.Text.Substring (0, GetMaxLength (textBox));
            }
        }
    }
}
続きを読む...

July 1, 2009

RadioButton を ListBox で実装する 4

RadioButton を ListBox で実装する 3 のカスタムコントロールを使ってみる。

まず、コードビハインドで適当なコレクションを突っ込んでみる (例は配列だけど)

using System.Net.NetworkInformation;
using System.Windows;

namespace WpfSample
{
    /// <summary>
    /// Window1.xaml の相互作用ロジック
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1 ()
        {
            this.DataContext = NetworkInterface.GetAllNetworkInterfaces ();
               
            InitializeComponent ();
        }
    }
}

作成したカスタムコントロールをウインドウに配置して、ItemsSource プロパティを設定する。本当は ItemsSource は DataContext じゃなく別の方法で提供すべきだと思うけど、今回は単に表示するのが目的なので。

RadioButton のキャプションとして表示したいメンバのパスを DisplayMemberPath に設定しとく。

<Window x:Class="WpfSample.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfSample"
        Title="RadioButton を ListBox で実装する" SizeToContent="WidthAndHeight">

    <Grid>
        <local:RadioButtonsControl ItemsSource="{Binding}" DisplayMemberPath="Name" />
    </Grid>
</Window>

実行すると、こんな感じ。ちゃんと NIC の Name が表示されている。

radiolist_custom.png

RadioButton を ListBox で実装する 3

RadioButton を ListBox で実装するRadioButton を ListBox で実装する 2の続き。

カスタムコントロールにしてみた。

RadioButtonsControl.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfSample
{
    /// <summary>
    /// 選択可能な項目をラジオボタンで選択するコントロール
    /// </summary>

    public class RadioButtonsControl : ListBox
    {
        /// <summary>
        /// クラスの初期化
        /// </summary>

        static RadioButtonsControl ()
        {
            // プロパティのデフォルト値を上書きする
            DefaultStyleKeyProperty.OverrideMetadata (typeof (RadioButtonsControl), new FrameworkPropertyMetadata (typeof (RadioButtonsControl)));
            BackgroundProperty.OverrideMetadata (typeof (RadioButtonsControl), new FrameworkPropertyMetadata (Brushes.Transparent));
            BorderBrushProperty.OverrideMetadata (typeof (RadioButtonsControl), new FrameworkPropertyMetadata (Brushes.Transparent));
        }


        /// <summary>
        /// コントロール内にラジオボタンを並べる列数を取得または設定します。これは、依存関係プロパティです。
        /// </summary>
        /// <value>コントロール内の列の数。既定値は 0 です。</value>

        public int Columns
        {
            get { return (int) GetValue (ColumnsProperty); }
            set { SetValue (ColumnsProperty, value); }
        }


        /// <summary>
        /// RadioButtonControl.Columns 依存関係プロパティを識別します。
        /// </summary>

        public static readonly DependencyProperty ColumnsProperty =
            DependencyProperty.Register ("Columns", typeof (int), typeof (RadioButtonsControl), new FrameworkPropertyMetadata (0));


        /// <summary>
        /// コントロール内にラジオボタンを並べる行数を取得または設定します。これは、依存関係プロパティです。
        /// </summary>
        /// <value>コントロール内の行の数。既定値は 0 です。</value>

        public int Rows
        {
            get { return (int) GetValue (RowsProperty); }
            set { SetValue (RowsProperty, value); }
        }


        /// <summary>
        /// RadioButtonControl.Rows 依存関係プロパティを識別します。
        /// </summary>

        public static readonly DependencyProperty RowsProperty =
            DependencyProperty.Register ("Rows", typeof (int), typeof (RadioButtonsControl), new FrameworkPropertyMetadata (0));
    }
}


Generic.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfSample">

    <Style TargetType="{x:Type local:RadioButtonsControl}">
        <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
        <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
        <Setter Property="ScrollViewer.CanContentScroll" Value="true" />
        <Setter Property="VerticalContentAlignment" Value="Center" />
        <Setter Property="SelectionMode" Value="Single" />
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <UniformGrid
                            Columns="{Binding Path=Columns, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:RadioButtonsControl}}}"
                            Rows="{Binding Path=Rows, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:RadioButtonsControl}}}" />
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ItemContainerStyle">
            <Setter.Value>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Setter Property="Margin" Value="5" />
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                <RadioButton
                                        IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource TemplatedParent}}">
                                    <ContentPresenter />
                                </RadioButton>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:RadioButtonsControl}">
                    <Border SnapsToDevicePixels="True" x:Name="Bd" Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}" Padding="1">
                        <ScrollViewer Focusable="False" Padding="{TemplateBinding Padding}">
                            <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                        </ScrollViewer>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

June 9, 2009

RadioButton を ListBox で実装する 2

RadioButton を ListBox で実装するの続き。ListBox の IsEnabled を False に設定した際に、ListBox に設定された Background の値が無視されてしまい、なにやら悲しいことに。

以下は、Expression Blend で出力した ListBox のコントロールテンプレート。

<Custom:SolidColorBrush x:Key="ListBorder" Color="#FF7F9DB9"/>
<Style TargetType="{x:Type ListBox}">
    <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
    <Setter Property="BorderBrush" Value="{StaticResource ListBorder}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
    <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
    <Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ListBox}">
                <Border SnapsToDevicePixels="true" x:Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="1">
                    <ScrollViewer Focusable="false" Padding="{TemplateBinding Padding}">
                        <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                    </ScrollViewer>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                    </Trigger>
                    <Trigger Property="IsGrouping" Value="true">
                        <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

つまり、ListBox の ControlTemplate 自体も置き換えてやらない限り、IsEnabled が False の時の ListBox の Background はいじれないってことになる。

ええと、今忙しいのでここまで !

June 8, 2009

RadioButton を ListBox で実装する

MSDN ライブラリのバージョンに関する情報の欄に、なにげに「Microsoft Visual Studio 2010/.NET Framework 4.0」が追加されてます。

http://msdn.microsoft.com/ja-jp/library/system.windows.markup.markupextension.aspx

わたしが .NET Framework 4.0 に一番望むこととは、RadioButton へのデータバインドが正常に機能することですかね…。

フィードバック: RadioButton.IsCheckedProperty losing databindings

なにしろ、RadioButton の IsChecked に何かをバインディングすると、1つの対象に複数の RadioButton から同期を取ろうとするせいか、途中でバインディングが無効になってしまいます。

複数の項目から 1 つを選んで何かにバインドさせるなら、ListBox (というか Selector) が得意とするところですよね。ならば、見た目が RadioButton の ListBox を作ってやれば解決です。こういうのは WPF ならではですね。

続きを読む...

May 15, 2009

Visual Studio 2008 で、XAML デザイナを開くと異常終了する

Visual Studio 2008 で XAML ファイルを開くと VS 自体が異常終了するようになってしまった。以下イベントログ。

ソース: .NET Runtime
種類: エラー
イベント ID: 1023
説明:
.NET Runtime version 2.0.50727.3082 - 致命的な実行エンジン エラーが発生しました (7A2E1132) (0)

VS2008 crashes with "Fatal Execution Engine Error"」に、以下の投稿があった。

Thanks to Paul I found a very strange workaround (I verified it a few times)

  • open the solution
  • unload the project (context menu)
  • edit the csproj (context menu), don't make any changes
  • reload the project (contect menu)

If I do that before I start the debugger VS will not crash.

つまり

  1. ソリューションを開く
  2. コンテキストメニューより [プロジェクトのアンロード]
  3. コンテキストメニューより [編集 WpfApplication1.csproj] して、何も編集を行わない。
  4. コンテキストメニューより [プロジェクトの再読み込み]

嘘みたいだが、これで本当に解決。

May 12, 2009

System.Windows.Input.InputMethod

System.Windows.Input.InputMethod の添付プロパティの挙動がよくわかんない。

<Window x:Class="WpfSample.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="テキストボックスで IME の ON/OFF を指定する"
        SizeToContent="Height" Width="300">
    <Window.Resources>
        <Style TargetType="TextBox">
            <Setter Property="Margin" Value="5"/>
        </Style>
        <Style TargetType="Label">
            <Setter Property="VerticalContentAlignment" Value="Center"/>
        </Style>
    </Window.Resources>    
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <Label Content="IME ON:" Grid.Row="0"/>
        <TextBox InputMethod.PreferredImeState="On" Grid.Column="1" Grid.Row="0" Text="On のはず"/>
        <Label Content="IME OFF:" Grid.Row="1"/>
        <TextBox InputMethod.PreferredImeState="Off" Grid.Column="1" Grid.Row="1" Text="Off のはず"/>
    </Grid>
</Window>

とりあえず、これで意図したとおりにうごくっぽい。

April 10, 2009

メモ: OneWayToSource だと FrameworkPropertyMetadata に設定した PropertyChangedCallback が走らないのだ

TwoWay にするしかないのね…。はい、せえの、

「みっともないけど、これも仕事 !」

April 9, 2009

メモ: WPF で SelectedItems プロパティにデータバインドする方法

Functional Fun: How to Databind to a SelectedItems property in WPF

メモ: SelectedItems

Binding a ViewModel to ListBox SelectedItems

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 ();
    }
}

March 6, 2009

メモ: ValidationRule.ValidationStep プロパティによって ValidationRule に渡される値の型は異なる。

ValidationRule.ValidationStep プロパティ (System.Windows.Controls)

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 3, 2009

メモ: RadioButton.IsCheckedProperty のデータバインディングがぶっ飛んじゃうよ。

フィードバック: RadioButton.IsCheckedProperty losing databindings

以下、例によって訳はヘナチョコなんで、信用しないように。

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1613789&SiteID=1

I'm currently fooling around with databinding in the wpf framework and I'm stumped as to why my bindings to the RadioButton.IsCheckedProperty are being removed. My goal is to create a yes no groupbox where the group box contains 2 radio buttons. The radio buttons would have their IsChecked property bound to the same property (In my example, both radiobuttons are bound the the "CurrentValue" property of the same datasource.).

私は現在、wpf フレームワークのデータバインディングをいじくってて、RadioButton.IsCheckedProperty へ設定したバインディングがなぜ削除されてしまうのかについて途方にくれています。私の目的は yes no のグループボックスを作ることで、グループボックスには 2 つのラジオボタンを含みます。そのラジオボタンはそれぞれの IsChecked プロパティを同一のプロパティにバインドしたいです。(私の例では、両方のラジオボタンを同じデータソースの "CurrentValue" プロパティにバインドします。)

Microsoft からコメントついてる。

Thank you for your feedback. We are currently investigating. If this issue is urgent, please call support directly (see http://support.microsoft.com). Thank you, Visual Studio Product Team.

投稿者: Microsoft、投稿日時: 2007/05/26 18:11

フィードバックありがとう。我々は現在調査しています。もし問題が緊急なら、サポートに直接電話してください(http://support.microsoft.com 参照) ありがとう、Visual Studio Product Team より。

Thanks for your feedback. We have reproduced this bug on Win2003 SP2 and OrcasBeta1VSTS, and we are sending this bug to the appropriate group within the Visual Studio Product Team for triage and resolution. Thank you, Visual Studio Product Team.

投稿者: Microsoft、投稿日時: 2007/05/26 18:12

フィードバックありがとう。このバグを、Windows 2003 SP2 と OrcasBeta1VSTS 上で再現させ、順位付けと解決のために、このバグを Visual Studio Product Team 内部の適切なグループに送っています。ありがとう、Visual Studio Product Team より。

February 12, 2009

TODO: 依存関係プロパティ

DependencyObject.GetValue メソッド (System.Windows) DependencyProperty は DependencyObject 以外にじっそうすることは可能か ? 添付プロパティは可能だが、あれは値の保存先が DependencyObject だから。 Dependency.Object.SetValue/Dependency.Object.GetValue は内部的には何をしてるの ? たとえば、それっぽいIなんとか(インターフェース)がないかさがす。

February 2, 2009

続:添付プロパティにしちゃえばいいんじゃない ?

添付プロパティにしちゃえばいいんじゃない ?」の続きです。

要するに、SortDescription を設定することで ListView をソートする場合、ヘッダクリックした際にどのプロパティをキーにしてソートすればよいかを保持しておく必要があります。GridViewColumn や GridViewColumnHeader にそれを保持しようとすると「A Sortable GridView (I mean ListView) Control in WPF(翻訳中)」などのように GridViewColumn を継承したクラスを定義したりオオゴトになってしまうのですが、WPF には添付プロパティってものがあるやんって話です。

なので、こんなクラスで。

using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace SampleApp
{
    public class GridViewSortHelper
    {
        protected ListView  listView;
        protected GridViewColumnHeader lastSortedHeader;
        protected ListSortDirection lastSortDirection = ListSortDirection.Ascending;

        /// <summary>
        /// <c>GridViewSortHelper.SortKey</c> 添付プロパティ
        /// </summary>
        /// <value>ソートに使用するキー文字列</value>
        public static readonly DependencyProperty SortKeyProperty =
            DependencyProperty.RegisterAttached ("SortKey", typeof (string), typeof (GridViewSortHelper), new FrameworkPropertyMetadata (""));

        /// <summary>
        /// 要素に <c>GridViewSortHelper.SortKey</c> 添付プロパティを設定します。
        /// </summary>
        /// <param name="element">プロパティ値の書き込み対象の要素。</param>
        /// <param name="value">要素の <c>GridViewSortHelper.SortKey</c> 属性値</param>
        public static void SetSortKey (GridViewColumn element, string value)
        {
            element.SetValue (SortKeyProperty, value);
        }

        /// <summary>
        /// 要素の <c>GridViewSortHelper.SortKey</c> 添付プロパティを取得します
        /// </summary>
        /// <param name="element"> プロパティ値の読み取り元の要素。</param>
        /// <returns>要素の <c>GridViewSortHelper.SortKey</c> 属性値</returns>
        public static string GetSortKey (GridViewColumn element)
        {
            return (string) element.GetValue (SortKeyProperty);
        }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="listView">ソート対象の ListView</param>
        public GridViewSortHelper (ListView listView)
        {
            this.listView = listView;
        }

        /// <summary>
        /// ヘッダクリック時処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void OnHeaderClicked (object sender, RoutedEventArgs e)
        {
            var headerClicked = e.OriginalSource as GridViewColumnHeader;
            if (headerClicked != null)
            {
                var direction = ListSortDirection.Ascending;
                if (headerClicked == lastSortedHeader)
                {
                    if (lastSortDirection == ListSortDirection.Ascending)
                    {
                        direction = ListSortDirection.Descending;
                    }
                }
                var key = GetSortKey (headerClicked.Column);

                var dataView = CollectionViewSource.GetDefaultView (this.listView.ItemsSource);
                dataView.SortDescriptions.Clear ();
                var sortDescription = new SortDescription (key, direction);
                dataView.SortDescriptions.Add (sortDescription);
                dataView.Refresh ();

                lastSortedHeader = headerClicked;
                lastSortDirection = direction;
            }
        }
    }
}
続きを読む...

January 28, 2009

連番のプロパティを持つ 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));
        }
    }
}