DataBindingのStringFormatって

前の記事で使っているデータバインディングで、StringFormatを使って書式を指定している。

<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Birthday,
Mode=OneWay,
StringFormat=yyyy/MM/dd}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>

このStringFormatはなかなかの曲者で、ソース側のプロパティの型がStringの時は動かないという仕様になっている。
つまり、ItemsSourceに設定されたリストのアイテムとなったクラスで、

private DateTime birthday_;

public DateTime Birthday {
get {return birthday_;}
set {
if (birthday_ != value) {
birthday_ = value;
this.NotifyPropertyChanged("Birthday");
}
}
}
の時は動くけど、

private string birthday_;

public string Birthday {
get {return birthday_;}
set {
if (birthday_ != value) {
birthday_ = value;
this.NotifyPropertyChanged("Birthday");
}
}
}
の時は動かない事になる。StringFormatを指定しているのに、書式通りにならないって時の原因は、だいたいコレ。
StringFormatで指定できない時は、コンバータクラスを使うことになる。
スポンサーサイト
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

日付を編集するときはDatePickerで。

DataGridの列で、日付型を編集する為のクラスってのは特にない。普通はDataGridTextColumnなんだけど、
不正な日付も、日付でなくても入力できてしまう。入力するなら、DatePickerを使いたいところなんだ。
で、それをやろうと思うときに使うのが、DataGridTemplateColumnというクラス。
DataGridTemplateColumnは、セルのテンプレートを自分で指定できる。表示用、編集用の2種類があって、編集が始まると勝手に切り替わってくれる。

<Window x:Class="TawamureDays.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:TawamureDays"
Title="MainWindow" Height="213" Width="385"
local:WindowBehavior.UseMessageCommand="True"
local:FrameworkElementBehavior.OnLoadedCommand="{Binding LoadedCommand}">
<Grid>
<DataGrid AutoGenerateColumns="False"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard"
CanUserAddRows="False"
CanUserDeleteRows="False"
SelectionMode="Extended"
SelectionUnit="CellOrRowHeader"
ItemsSource="{Binding PersonsList}"
AlternatingRowBackground="AliceBlue">
<DataGrid.Columns>
<DataGridCheckBoxColumn IsReadOnly="False"
local:DataGridExtender.ColumnId="ColIsChecked"
/>
<DataGridTextColumn IsReadOnly="True"
Header="名前"
local:DataGridExtender.ColumnId="ColFirstName"
Binding="{Binding FirstName}"
Width="1*"
/>
<DataGridTextColumn IsReadOnly="True"
Header="苗字"
local:DataGridExtender.ColumnId="ColSecondName"
Binding="{Binding SecondName}"
Width="2*"
/>
<local:DataGridTextExColumn IsReadOnly="False"
Header="年齢"
local:DataGridExtender.ColumnId="ColAge"
Binding="{Binding Age}"
Width="SizeToHeader"
HorizontalAlignment="Right"
/>
<DataGridTemplateColumn Header="誕生日" Width="*"
local:DataGridExtender.ColumnId="ColBirthday"
SortMemberPath="Birthday">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Birthday,
Mode=OneWay,
StringFormat=yyyy/MM/dd}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding Birthday,
Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>

通常時は、CellTemplate、編集時は、CellEditingTemplateがテンプレートとして適用される。
ちなみに、データありきのDataTemplateのみ使える。続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGridTextColumnで右詰めにしたい時が多々ある。

DataGridColumnには、テキストを表示するためのクラスとして、DatagGridTextColumnがある。
しかし、このクラス、文字寄せを設定できない。メモ的な内容の文字列であれば、左寄せで十分なんだけど、
数値を表示する時は右寄せで表示したくなる。
それらしきものがないか調べたら、ちゃんとあったりする。
WPF Toolkit DataGrid, Part III – Playing with Columns and Cells
ExtendedTextBoxColumnという名前をちょっと変えて実装してみる。

using System;
using System.Windows;
using System.Windows.Controls;

namespace TawamureDays {

/// <summary>
/// DataGridTextColumn拡張クラス<br/>
/// </summary>
public class DataGridTextExColumn : DataGridTextColumn {

#region コンストラクタ

/// <summary>
/// コンストラクタ
/// </summary>
public DataGridTextExColumn() : base() {

}
}
}

これに、文字用の依存関係プロパティを実装する

/// <summary>
/// 水平方向の位置を取得|設定します。
/// </summary>
public HorizontalAlignment HorizontalAlignment {
get {return (HorizontalAlignment)GetValue(HorizontalAlignmentProperty);}
set {SetValue(HorizontalAlignmentProperty, value);}
}

/// <summary>水平方向の位置</summary>
public static readonly DependencyProperty HorizontalAlignmentProperty =
DependencyProperty.Register("HorizontalAlignment", typeof(HorizontalAlignment),
typeof(DataGridTextExColumn),
new UIPropertyMetadata(HorizontalAlignment.Left));

/// <summary>
/// 垂直方向の位置を取得|設定します。
/// </summary>
public VerticalAlignment VerticalAlignment {
get {return (VerticalAlignment)GetValue(VerticalAlignmentProperty);}
set {SetValue(VerticalAlignmentProperty, value);}
}

/// <summary>垂直方向の位置</summary>
public static readonly DependencyProperty VerticalAlignmentProperty =
DependencyProperty.Register("VerticalAlignment", typeof(VerticalAlignment),
typeof(DataGridTextExColumn),
new UIPropertyMetadata(VerticalAlignment.Center));

これで、XAML上からは設定できる事になる。
では、このプロパティをどう反映させるかが次のコードになる。

/// <summary>
/// 列の Binding プロパティ値にバインドされた読み取り専用の TextBlock コントロールを取得します。
/// </summary>
/// <remarks>
/// 値が入る前なので、ここで値の編集とかはできない。
/// </remarks>
/// <param name="cell">生成された要素を格納するセル。</param>
/// <param name="dataItem">目的のセルを格納している行によって表されるデータ項目。</param>
/// <returns>列の Binding プロパティ値にバインドされた新しい読み取り専用のテキスト ブロック コントロール</returns>
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) {
var txtBlock = (TextBlock)base.GenerateElement(cell, dataItem);
txtBlock.HorizontalAlignment = this.HorizontalAlignment;
txtBlock.VerticalAlignment = this.VerticalAlignment;
return txtBlock;
}

/// <summary>
/// 列の Binding プロパティ値にバインドされた TextBox コントロールを取得します。
/// </summary>
/// <remarks>
/// 値が入る前なので、ここで値の編集とかはできない。
/// </remarks>
/// <param name="cell">生成された要素を格納するセル。</param>
/// <param name="dataItem">目的のセルを格納している行によって表されるデータ項目。</param>
/// <returns>列の Binding プロパティ値にバインドされた新しい TextBox コントロール</returns>
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem) {
var textBox = (TextBox)base.GenerateEditingElement(cell, dataItem);

//文字寄せを指定します。
textBox.TextAlignment = this.GetTextAlignment();
textBox.VerticalAlignment = this.VerticalAlignment;
//textBox.MaxLength = this.MaxLength;

return textBox;
}

GenerateElementメソッドは、表示に用いられるTextBlockにアクセスできる。
GenerateEditingElementメソッドは、編集時に用いられるTextBoxにアクセスできる。
これらに実装したプロパティを設定している。
ただし、TextBoxのTextAlignmentにHorizontalAlignmentを直接セットできない。
なので、変換用のメソッドが必要となる。

/// <summary>
/// DataGridColumnに設定されたHorizontalAlignmentプロパティから、
/// TextAlignment(TextBoxの文字寄せ)を取得します。
/// </summary>
/// <returns>TextAlignment</returns>
private TextAlignment GetTextAlignment() {
switch (HorizontalAlignment) {
case HorizontalAlignment.Left:
return TextAlignment.Left;
case HorizontalAlignment.Right:
return TextAlignment.Right;
case HorizontalAlignment.Center:
return TextAlignment.Center;
case HorizontalAlignment.Stretch:
return TextAlignment.Justify;
default:
throw new ArgumentOutOfRangeException("HorizontalAlignment");
}
}

という感じで作った物を早速作ってみる。

<Window x:Class="TawamureDays.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:TawamureDays"
Title="MainWindow" Height="213" Width="385"
local:WindowBehavior.UseMessageCommand="True"
local:FrameworkElementBehavior.OnLoadedCommand="{Binding LoadedCommand}">
<Grid>
<DataGrid AutoGenerateColumns="False"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard"
CanUserAddRows="False"
CanUserDeleteRows="False"
SelectionMode="Extended"
SelectionUnit="CellOrRowHeader"
ItemsSource="{Binding PersonsList}"
AlternatingRowBackground="AliceBlue">
<DataGrid.Columns>
<DataGridCheckBoxColumn IsReadOnly="False"
local:DataGridExtender.ColumnId="ColIsChecked"
/>
<DataGridTextColumn IsReadOnly="True"
Header="名前"
local:DataGridExtender.ColumnId="ColFirstName"
Binding="{Binding FirstName}"
Width="1*"
/>
<DataGridTextColumn IsReadOnly="True"
Header="苗字"
local:DataGridExtender.ColumnId="ColSecondName"
Binding="{Binding SecondName}"
Width="2*"
/>
<local:DataGridTextExColumn IsReadOnly="True"
Header="年齢"
local:DataGridExtender.ColumnId="ColAge2"
Binding="{Binding Age}"
Width="Auto"
HorizontalAlignment="Right"
/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>

20120828_1
このクラスに更に、最大文字数(MaxLength)を設定することも可能かな。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGrid スプライト表示

DataGridの行をスプライト(縞々)表示にするのは標準装備。

<DataGrid AutoGenerateColumns="False"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard"
CanUserAddRows="False"
CanUserDeleteRows="False"
SelectionMode="Extended"
SelectionUnit="CellOrRowHeader"
ItemsSource="{Binding PersonsList}"
AlternatingRowBackground="AliceBlue"
>
<DataGrid.Columns>
<DataGridCheckBoxColumn IsReadOnly="False"
local:DataGridExtender.ColumnId="ColIsChecked"
/>
<DataGridTextColumn IsReadOnly="True"
Header="名前"
local:DataGridExtender.ColumnId="ColFirstName"
Binding="{Binding FirstName}"
Width="1*"
/>
<DataGridTextColumn IsReadOnly="True"
Header="苗字"
local:DataGridExtender.ColumnId="ColSecondName"
Binding="{Binding SecondName}"
Width="1*"
/>
</DataGrid.Columns>
</DataGrid>

20120727_1
ただ、奇数行に色をつけるって設定はないんだよね。どうでも良いと言われれば、それまでの話だけども。
(補足1)
ItemsSourceとバインディングしているPersonsListは、DataContext側に用意させている。
リストのアイテムとなるクラスに、FirstNameとSecondNameというプロパティがあれば動く。無名クラスでも大丈夫。
(補足2)
Widthに設定している「1*」というのは、比率設定。上記設定では、名前と苗字の幅が1:1となる。
このあたりは、Gridの設定に似ている。
DataGridColumnのWidthプロパティは、double型ではなく、DataGridLengthという構造体だからできる事。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGridに行番号を表示しよう。

DataGridには、行ヘッダ(RowHeader)というものがあり、デフォルトでは何も表示されない。
仕事で行ったのが、「行番号を表示する」というもの。
表示できたら、現在見ているデータが上から何番目なのかがよくわかったりする。
全部で何件あるかもわかるので、便利といえば、便利。
こういう時のstackoverflow。
Simple way to display row numbers on WPF DataGrid
おおう。流石。添付プロパティで実現できるんだ。ほぼそのままを実装させていただきました(-人-)。
続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGridの列にIDをセットするんだ

何日か前の記事でも書いたけど、DataGridColumnの列を特定するのに、名前(Name)プロパティを使う事は難しい。
その列を定義したWindowクラスには、その名前を使って何らかの処理はできるけど、以下のような事はできない。

//MyDataGridは、XAML上で設定したDataGridのName
foreach (var col in this.MyDataGrid) {
if (col.Name == "XXXX") {
//列名がXXXXだったら、YYYYをする。
}
}

DataGridColumn自身は、Nameプロパティを持たない。なので、x:Nameで設定するしかないんだけど、x:Nameで設定した値をどうやっても取得できなかった。メソッドがありそうでない。届きそうで届かない。
その対処として、添付プロパティによって名前(というかID)を持たせる事にした。


続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGridメモ

なんだかんだで書く時間がなくなったので、メモ程度
・DataGridでリストを表示するには、DataContextではなく、ItemsSourceプロパティにセットする。
→DataContextにバインディングさせない方が融通が利くときがある。
・ItemsSourceプロパティにデータバインディングするプロパティの型は、IEnumerableよりIListが望ましい。
→IEnumerableは前からの反復のみなので、仮想化に不向き。インデックスを指定できる型が望ましい。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGrid内の構造を知ろうと思う。

DataGridをカスタマイズしようと思うなら、構造を知る必要がある。
そのDataGridの構造をよく知る上で便利な記事が↓。
WPF Toolkit DataGrid, Part II – Custom styling
WPF DataGrid: Dissecting the Visual Layout
VisualTree上の構造、各パーツの名前等がわかり易く載っている。
WPF3.5時代の、Toolkit内にあった頃の話だけど、WPF4でもほぼ同じ構造を引き継いでいる感じなので、陳腐化はしていない。
一読の価値あり。続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGridの基本的な事

DataGrid基本的な設定からメモしておく。

<Window x:Class="TawamureDays.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:TawamureDays"
Title="MainWindow" Height="213" Width="385"
local:WindowBehavior.UseMessageCommand="True"
local:FrameworkElementBehavior.OnLoadedCommand="{Binding LoadedCommand}"
>
<Grid>
<DataGrid AutoGenerateColumns="False"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard"
CanUserAddRows="False"
CanUserDeleteRows="False"
SelectionMode="Extended"
SelectionUnit="CellOrRowHeader">
</DataGrid>
</Grid>
</Window>

○AutoGenerateColumnsプロパティ;False
 列を自動生成するかどうか。便利なときは便利だけど、列ヘッダがプロパティ名そのままで出てしまう。イベントで回避する方法があるけど、MVVMには向かない(めんどくさい)。
 デフォルトでTrueなので、うっかりすると、列の数がすごいことに。

○VirtualizingStackPanel.IsVirtualizingプロパティ:True
 UI仮想化用の添付プロパティ。VirtualizingStackPanel.VirtualizationModeプロパティとセット。
DataGridは内部でVirtualizingStackPanelが利用されているので、設定推奨、とぃうか必須。
 これがないと、DataGridはデータが増えれば増えるほど、駄目な子になります。

○VirtualizingStackPanel.VirtualizationModeプロパティ:Standard
 UI仮想化のモード。Standardは標準。Recyclingは視界から消えたコンテナ(DataGridRowかその中のPresenter系)を使いまわしている感じがする。
 メモリや速度に優しいのはRecycling。だけど、レイアウトが崩れまくるのもRecycling。特に行ヘッダに番号とか表示したら、もう大変。
 ソートとか、全体がリフレッシュされるまで崩れたままという…。

○CanUserAddRowsプロパティ:False
 ユーザが意図的に行追加できるかどうか。表示専用なら、Falseに。編集や追加目的のDataGridなら、当然Trueに。
 デフォルトでTrue。

○CanUserDeleteRowsプロパティ:False
 ユーザが意図的に行削除できるかどうか。表示専用なら、Falseに。編集や追加目的のDataGridなら、当然Trueに。
 デフォルトでTrue。

○SelectionModeプロパティ:Extended
 選択する方法。Singleはセル1つ分のみ選択可能になる。Extendedで複数選択可能。
 セルのコピペ等を考えるとExtended一本になりそう。

○SelectionUnitプロパティ:CellOrRowHeader
 選択するときの単位というか括りというか。
 Cell:セルのみが選択される。
 CellOrRowHeader:通常はセル。行ヘッダをクリックすると行全体が選択される。
 FullRow:常に行全体が選択される。コピーすると、常に行全体がコピーされる。
 選択肢(セルか行全体か)を増やす意味でCellOrRowHeader。
続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

そろそろDataGridをネタにして書こうっと。

WPF4の開発で、一欄表示によく使うのが、DataGrid。DataGrid自身、WPF4からが正式デビュー。
3.5の時代は、WPF Toolkitの中にあった。それまでは、ListViewが主流だったのかもしれないけど、自分は3.5で開発してなかったので、ListViewに関してはまだ疎い。
DataGridを使って便利だと思ったのは、

・ソート。複数列のソートも簡単。
・列の並び替え。ドラグ&ドロップで。
・スタイルによる色設定。コードによる実装が減る。

かな。ただし、良いことばかりではない。パフォーマンス的なネックも存在したりする。余裕のある会社は1から作って速度を売りにしてたりするなぁ。
自分的に、DataGridの中で一際目立つ曲者がいる。それは、DataGridColumnだ。
曲者というか、厄介というか…。

・DataGridColumnからは、DataGridを特定できない(internalでプロパティを持っている)。
・DataGridColumn自身、LogicalTreeやVisualTreeに一切現れない。
・DataGridColumnは、FrameworkElementを継承していないので、Style設定が使えない。
・DataGridColumnは、FrameworkElementを継承していないので、Loadedイベント等もない。
・DataGridColumnは、FrameworkElementを継承していないので、コード上で名前解決ができない。

LogicalTree及び、VisualTreeに現れないといのが一番の厄介な点。BindingのElementNameやRelativeSourceだったかな?それらで参照できないという記事もあった。これは検証しておきたい。
後は、名前解決できないって事かなぁ。Nameプロパティないんだよ。どうやって列特定すんの?って感じだもんなぁ。
x:Nameを使えば、名前を設定はできるけど、取得できない。XAMLのコードビハインド内なら、その名前を使ってアクセスできるだけ。
DataGridCiolumnだけではなく、パフォーマンス的な部分でも発展途上なDataGrid。
一応、仕事では活躍中なので、ちょこちょこメモしていく。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

TextBoxフォーカス時における挙動を変えたいと思った。(弐)

で説明できていなかった、カスタム系の話。
任意の位置にカレットを配置させようと考えた時、思いつけたのが

・インデックスを指定しよう。
・特定の文字を指定しよう。

くらいだった。各々の用途向けに、添付プロパティを実装する。

/// <summary>
/// フォーカス取得時のカレット位置を取得します。<br/>
/// </summary>
/// <remarks>
/// BehavoirOnGotFocusプロパティと連携します。<br/>
/// BehaviorOnGotFocusプロパティにカスタム(Optional)が設定された時に使われます<br/>
/// </remarks>
/// <param name="obj">対象オブジェクト</param>
/// <returns>カレット位置</returns>
public static int GetCaretIndexOnFocus(DependencyObject obj) {
return (int)obj.GetValue(CaretIndexOnFocusProperty);
}

/// <summary>
/// フォーカス取得時のカレット位置を設定します。<br/>
/// </summary>
/// <remarks>
/// BehavoirOnGotFocusプロパティと連携します。<br/>
/// BehaviorOnGotFocusプロパティにカスタム(Optional)が設定された時に使われます<br/>
/// </remarks>
/// <param name="obj">対象オブジェクト</param>
/// <param name="value">カレット位置</param>
public static void SetCaretIndexOnFocus(DependencyObject obj, int value) {
obj.SetValue(CaretIndexOnFocusProperty, value);
}

/// <summary>フォーカス取得時のカレット位置(BehaviorOnGotFocus.Optional用)</summary>
public static readonly DependencyProperty CaretIndexOnFocusProperty =
DependencyProperty.RegisterAttached("CaretIndexOnFocus", typeof(int),
typeof(TextBoxBehavior),
new UIPropertyMetadata(-1));

/// <summary>
/// カレット移動時に起点となる文字を取得します。<br/>
/// </summary>
/// <remarks>
/// BehavoirOnGotFocusプロパティと連携します。<br/>
/// BehaviorOnGotFocusプロパティにカスタム(Optional)が設定された時に使われます<br/>
/// </remarks>
/// <param name="obj">対象オブジェクト</param>
/// <returns>起点となる文字</returns>
public static string GetBaseCharOfCaret(DependencyObject obj) {
return (string)obj.GetValue(BaseCharOfCaretProperty);
}

/// <summary>
/// カレット移動時に起点となる文字を設定します。<br/>
/// </summary>
/// <remarks>
/// BehavoirOnGotFocusプロパティと連携します。<br/>
/// BehaviorOnGotFocusプロパティにカスタム(Optional)が設定された時に使われます<br/>
/// </remarks>
/// <param name="obj">対象オブジェクト</param>
/// <param name="value">起点となる文字</param>
public static void SetBaseCharOfCaret(DependencyObject obj, string value) {
obj.SetValue(BaseCharOfCaretProperty, value);
}

/// <summary>カレット移動時に起点となる文字</summary>
public static readonly DependencyProperty BaseCharOfCaretProperty =
DependencyProperty.RegisterAttached("BaseCharOfCaret", typeof(string),
typeof(TextBoxBehavior), new UIPropertyMetadata(null));

TxtBox_GotKeyboardFocusメソッドに実装を追加する。

/// <summary>
/// キーボードフォーカスイベントハンドラ
/// </summary>
/// <param name="sender">イベント発生用</param>
/// <param name="e">イベント引数</param>
private static void TxtBox_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) {
var txtBox = sender as TextBox;

if (txtBox == null || !txtBox.Focusable || !txtBox.IsEnabled ||
txtBox.Visibility != Visibility.Visible) {
//表示中でフォーカス可能で、有効であるときのみ処理可能。
//それ以外は無視です。
return;
}

var behavior = TextBoxBehavior.GetBehavoirOnGotFocus(txtBox);

switch (behavior) {
case BehaviorOnFocus.None:
//何もしない
break;
case BehaviorOnFocus.SelectAll:
//すべて選択
txtBox.SelectAll();
break;
case BehaviorOnFocus.CaretLeft:
//一番左にカレットを移動
txtBox.CaretIndex = 0;
break;
case BehaviorOnFocus.CaretRight:
//一番右にカレットを移動
txtBox.CaretIndex = txtBox.Text.Length;
break;
case BehaviorOnFocus.CaretOptional:
//カスタム
//カレットの位置
var caretIndex = GetCaretIndexOnFocus(txtBox);
//基点とする文字
string baseChar = GetBaseCharOfCaret(txtBox);

if (string.IsNullOrEmpty(baseChar)) {
//基点とする文字が未設定
//→caretIndexの値でカレットを移動させます。
if (caretIndex >= 0) {
txtBox.CaretIndex = caretIndex;
}

} else {
//基点とする文字が設定されている
//テキスト内の文字を検索して、位置を特定します。
//存在すれば、caretIndexをオフセット的な扱いにして移動させます。
var idx = txtBox.Text.IndexOf(baseChar);

if (idx >= 0) {
txtBox.CaretIndex = Math.Max(0, idx + caretIndex);
} else {
txtBox.CaretIndex = Math.Max(0, txtBox.Text.Length);
}
}
break;
default:
break;
}
}

カスタムの使い方的には、

数値用の項目で、フォーカス時のカレット位置を小数点に置きたい

とか。

<Window x:Class="TawamureDays.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:TawamureDays"
Title="MainWindow" Height="213" Width="385"
local:WindowBehavior.UseMessageCommand="True"
local:FrameworkElementBehavior.OnLoadedCommand="{Binding LoadedCommand}"
>
<Grid>
<i:Interaction.Behaviors>
<local:FocusMoveBehavior Key="Enter"/>
</i:Interaction.Behaviors>
<StackPanel>

<TextBlock Text="フォーカス時、全選択"/>
<TextBox Text="Text1"
local:TextBoxBehavior.BehavoirOnGotFocus="SelectAll"
local:FrameworkElementBehavior.IsFocused="True"/>
<TextBlock Text="フォーカス時、最も→にカレット"/>
<TextBox Text="Text2"
local:TextBoxBehavior.BehavoirOnGotFocus="CaretRight"/>
<TextBlock Text="フォーカス時、最も←にカレット"/>
<TextBox Text="Text3"
local:TextBoxBehavior.BehavoirOnGotFocus="CaretLeft"/>
<TextBlock Text="フォーカス時、ドット(.)の位置にカレット"/>

<TextBox Text="Text4"
local:TextBoxBehavior.BehavoirOnGotFocus="CaretOptional"
local:TextBoxBehavior.BaseCharOfCaret="."
local:TextBoxBehavior.CaretIndexOnFocus="0"
/>

<Button local:FocusMoveBehavior.Excluded="True" Content="Button1"/>
</StackPanel>
</Grid>
</Window>


フォーカス時は↓のようになる。
20120713_2
こうすることで、いちいち移動しなくても、整数部の編集に入れるかな~とか。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

TextBoxフォーカス時における挙動を変えたいと思った。(壱)

TextBoxにフォーカス(キーボードフォーカス)が当たった時、

・入力されている文字列を全選択したい。
 →追加ではなく、上書きというアクションをデフォルトとする。
・一番右にカレット(|で点滅する位置)を配置したい。
 →後に追加していくアクションをデフォルトとする
・一番左にカレット(|で点滅する位置)を配置したい。
 →前に追加していくアクションをデフォルトとする
・任意の位置にカレットを配置する。
 →上記以外の特殊な用途。

と色々XAML上から設定できないものかと考えた。まあ、考えただけで済むはずもなく、作ったんだけど。これも添付プロパティでなんとかなったんだけど。
まずは上記用途を選択するための(添付プロパティの値となる)Enumを定義する。

using System;

namespace TawamureDays {

/// <summary>
/// キーボードフォーカス取得時の振る舞いを示すEnum
/// </summary>
/// <remarks>
/// テキストボックスのフォーカス時の動作を決めるためのEnumです。<br/>
/// </remarks>
public enum BehaviorOnFocus : int {
/// <summary>なし(初期値)</summary>
None = 0,
/// <summary>全選択</summary>
SelectAll,
/// <summary>カレットを左に配置</summary>
CaretLeft,
/// <summary>カレットを右に配置</summary>
CaretRight,
/// <summary>カレットを指定位置に配置(カスタム用)</summary>
CaretOptional
}
}

初期値(None)は、XAML上では設定しない。添付プロパティのデフォルト値となる値。とりあえず、一番下(カスタム)以外を実装する。

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace TawamureDays {

/// <summary>
/// TextBoxコントロール用ビヘイビアクラス
/// </summary>
/// <remarks>
/// TextBoxコントロールに対する添付プロパティ(ビヘイビア)を提供するクラスです。<br/>
/// </remarks>
public static class TextBoxBehavior {

/// <summary>
/// フォーカス取得時の振る舞いを取得します。
/// </summary>
/// <param name="obj">対象オブジェクト(TextBox)</param>
/// <returns>振る舞いを示すEnum</returns>
public static BehaviorOnFocus GetBehavoirOnGotFocus(DependencyObject obj) {
return (BehaviorOnFocus)obj.GetValue(BehavoirOnGotFocusProperty);
}

/// <summary>
/// フォーカス取得時の振る舞いを設定します。
/// </summary>
/// <param name="obj">対象オブジェクト(TextBox)</param>
/// <param name="value">振る舞いを示すEnum</param>
public static void SetBehavoirOnGotFocus(DependencyObject obj, BehaviorOnFocus value) {
obj.SetValue(BehavoirOnGotFocusProperty, value);
}

/// <summary>フォーカス取得時の振る舞い</summary>
public static readonly DependencyProperty BehavoirOnGotFocusProperty =
DependencyProperty.RegisterAttached("BehavoirOnGotFocus", typeof(BehaviorOnFocus),
typeof(TextBoxBehavior),
new UIPropertyMetadata(BehaviorOnFocus.None,
OnBehavoirOnGotFocusPropertyChanged));

/// <summary>
/// BehavoirOnGotFocusProperty変更イベントハンドラ
/// </summary>
/// <param name="dpObj">イベント発生元</param>
/// <param name="e">イベント引数</param>
private static void OnBehavoirOnGotFocusPropertyChanged(
DependencyObject dpObj, DependencyPropertyChangedEventArgs e) {
var txtBox = dpObj as TextBox;

if (txtBox == null) {
//TextBox系以外は受け付けません。
return;
}

txtBox.GotKeyboardFocus -=
new KeyboardFocusChangedEventHandler(TxtBox_GotKeyboardFocus);

if (e.NewValue != null && ((BehaviorOnFocus)e.NewValue) != BehaviorOnFocus.None) {
txtBox.GotKeyboardFocus +=
new KeyboardFocusChangedEventHandler(TxtBox_GotKeyboardFocus);

//※当然ながら、画面終了時にイベントからハンドラを削除する実装が必須です。
}

return;
}

/// <summary>
/// キーボードフォーカスイベントハンドラ
/// </summary>
/// <param name="sender">イベント発生用</param>
/// <param name="e">イベント引数</param>
private static void TxtBox_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) {
var txtBox = sender as TextBox;

if (txtBox == null || !txtBox.Focusable || !txtBox.IsEnabled ||
txtBox.Visibility != Visibility.Visible) {
//表示中でフォーカス可能で、有効であるときのみ処理可能。
//それ以外は無視です。
return;
}

var behavior = TextBoxBehavior.GetBehavoirOnGotFocus(txtBox);

switch (behavior) {
case BehaviorOnFocus.None:
//何もしない
break;
case BehaviorOnFocus.SelectAll:
//すべて選択
txtBox.SelectAll();
break;
case BehaviorOnFocus.CaretLeft:
//一番左にカレットを移動
txtBox.CaretIndex = 0;
break;
case BehaviorOnFocus.CaretRight:
//一番右にカレットを移動
txtBox.CaretIndex = txtBox.Text.Length;
break;
case BehaviorOnFocus.CaretOptional:
//カスタムを本当は実装する
break;
default:
break;
}
}
}
}

XAML上の設定

<Window x:Class="TawamureDays.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:TawamureDays"
Title="MainWindow" Height="213" Width="385"
local:WindowBehavior.UseMessageCommand="True"
local:FrameworkElementBehavior.OnLoadedCommand="{Binding LoadedCommand}"
>
<Grid>
<i:Interaction.Behaviors>
<local:FocusMoveBehavior Key="Enter"/>
</i:Interaction.Behaviors>
<StackPanel>
<TextBlock Text="フォーカス時、全選択"/>
<TextBox Text="Text1"
local:TextBoxBehavior.BehavoirOnGotFocus="SelectAll"
local:FrameworkElementBehavior.IsFocused="True"/>
<TextBlock Text="フォーカス時、最も→にカレット"/>
<TextBox Text="Text2"
local:TextBoxBehavior.BehavoirOnGotFocus="CaretRight"/>
<TextBlock Text="フォーカス時、最も←にカレット"/>
<TextBox Text="Text3"
local:TextBoxBehavior.BehavoirOnGotFocus="CaretLeft"/>
<TextBox Text="Text4"/>
<Button local:FocusMoveBehavior.Excluded="True" Content="Button1"/>
</StackPanel>
</Grid>
</Window>

FrameworkElementBehavior.IsFocusedを設定すると、起動時にそのコントロールにフォーカスが当たる。
動きは↓のような感じになる。
20120713
ただし、マウスによるフォーカスには効果がない。あくまでTabキー等を使ってフォーカスをあてた時の挙動が変わるだけだ。
マウス系はどうしようもないので、現在は放ってある。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

Enterキーでフォーカス移動できるようだ。

第10回 WPFの「入力イベントとアニメーション」を学ぼうにある「ビヘイビアの自作」で、サンプルとして挙げられている「FocusMoveBehavior」が、Enterキーによるフォーカス移動を実現している。Expression Blend SDKにある、System.Windows.Inractivity.dllを参照すれば実装できるので、早速組み入れました。が、しかし、上のサンプルだけでは、困ったことになることも判明した。

問題1:ボタンなんかもEnterキーで移動してしまう。
 →いや、それは普通に実行してほしいよね。
問題2:OnDetachingメソッドで終了処理してるけど、画面終了しても、OnDetachingメソッドが実行されないんですけど…
 →いつ通るの?
Enterキーによる移動から除外したい時は結構ある。特にボタンとかDataGridCellとか。
続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

VM側から特定コントロールにフォーカスを当てる(弐)。

VM側から特定コントロールにフォーカスを当てる(壱)。で挙げた課題を解消する方法を考える。
・Styleは使い回しが効かないので、いちいち書く必要がある。
→Styleで実装しなくても良いようにする。コード上で明示的にFocus()が呼べれば良い。
・プロパティ値もfalseに戻してからtrueに設定するという実装を意図的に行う必要がある
→VM側でfalseにするのではなく、コントロール側でfalseに戻るようにすれば良い。
実現できそうなのが、添付プロパティなので、作ることにする。

using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;

namespace TawamureDays {

/// <summary>
/// FrameworkElementビヘイビアクラス
/// </summary>
public static class FrameworkElementBehavior {

/// <summary>
/// フォーカスを当てるかどうかを取得します。
/// </summary>
/// <param name="obj">対象オブジェクト</param>
/// <returns>true:当てる</returns>
public static bool GetIsFocused(DependencyObject obj) {
return (bool)obj.GetValue(IsFocusedProperty);
}

/// <summary>
/// フォーカスを当てるかどうかを設定します。
/// </summary>
/// <param name="obj">対象オブジェクト</param>
/// <param name="value">true:当てる</param>
public static void SetIsFocused(DependencyObject obj, bool value) {
obj.SetValue(IsFocusedProperty, value);
}

/// <summary>フォーカスを当てるかどうか</summary>
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached("IsFocused", typeof(bool),
typeof(FrameworkElementBehavior),
new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));

/// <summary>
/// IsFocusedプロパティ変更イベントハンドラ
/// </summary>
/// <param name="dpObj">イベント発生元</param>
/// <param name="e">イベント引数</param>
private static void OnIsFocusedPropertyChanged(
DependencyObject dpObj, DependencyPropertyChangedEventArgs e) {
var element = dpObj as UIElement;

if (element == null) {
return;
}

if ((bool)e.NewValue) {
if (element.IsEnabled) {
if (element.Focusable) {
element.Focus();
} else {
//自身がフォーカスできなくても、子要素はできる可能性がある。
//次の要素へ移動させます。
element.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}

SetIsFocused(element, false);
}

}
}
}
SetIsFocusedを呼んでいるのは、ソース側の値をfalseにするため。よって、バインディングのモードはTwoWay必須になる。
XAML側

<Window x:Class="TawamureDays.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:TawamureDays"
Title="MainWindow" Height="213" Width="385"
local:WindowBehavior.UseMessageCommand="True"
local:FrameworkElementBehavior.OnLoadedCommand="{Binding LoadedCommand}"
>
<StackPanel>
<TextBox MinWidth="100" Name="Text1"
Text="Text1"
local:FrameworkElementBehavior.IsFocused="{Binding IsText1Focus, Mode=TwoWay}"/>
<TextBox MinWidth="100" Name="Text2"
Text="Text2"
local:FrameworkElementBehavior.IsFocused="{Binding IsText2Focus, Mode=TwoWay}"/>
<TextBox MinWidth="100" Name="Text3"
Text="Text3"
local:FrameworkElementBehavior.IsFocused="{Binding IsText3Focus, Mode=TwoWay}"/>
<Button Content="Text1へフォーカス" Command="{Binding Focus1Command}"/>
<Button Content="Text2へフォーカス" Command="{Binding Focus2Command}"/>
<Button Content="Text3へフォーカス" Command="{Binding Focus3Command}"/>
</StackPanel>
</Window>

・前回よりはすっきりする(スタイル設定がないから)。
・バインディングはTwoWay必須となる(get; set;両方がpublicである必要有り)。
使いやすい分、前よりはマシかな。続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

VM側から特定コントロールにフォーカスを当てる(壱)。

入力チェック等で、どこの項目がエラーになっているかを表示した後で、当該のコントロールにフォーカスを当てると、そのまま入力修正に入る事ができる。
WPFのコントロールにもFocusというメソッドがあるが、IsFocusedというプロパティは残念ながらない。
フォーカスにも、キーボードフォーカスとか、論理フォーカスとかあって、ややこしい。
フォーカスの概要
VM側から制御しようと思うなら、プロパティ値の変更をトリガーにして、フォーカスを当てる、というのが一般的かな。Messengerがあるなら、それも可能かもしれないけど。とりあえず
プロパティを用意する。
用意したプロパティをトリガーにしてフォーカスを当てるようにする。
を実装しようと思う。
XAML側

<Window x:Class="TawamureDays.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:TawamureDays"
Title="MainWindow" Height="213" Width="385"
local:WindowBehavior.UseMessageCommand="True"
local:FrameworkElementBehavior.OnLoadedCommand="{Binding LoadedCommand}"
>
<StackPanel>
<TextBox MinWidth="100" Name="Text1" Text="Text1">
<TextBox MinWidth="100" Name="Text2" Text="Text2">
<TextBox MinWidth="100" Name="Text3" Text="Text3">
<Button Content="Text1へフォーカス" Command="{Binding Focus1Command}"/>
<Button Content="Text2へフォーカス" Command="{Binding Focus2Command}"/>
<Button Content="Text3へフォーカス" Command="{Binding Focus3Command}"/>
</StackPanel>
</Window>
Textとボタンをそれぞれ3つ用意し、各ボタンを押すことで、対応するテキストボックスにフォーカスを当てるように実装する。
○プロパティを用意

/// <summary>Text1フォーカス用変数</summary>
private bool isText1Focus_;

/// <summary>
/// Text1フォーカス用変数を取得|設定します。
/// </summary>
public bool IsText1Focus {
get {return isText1Focus_;}
set {
if (isText1Focus_ != value) {
isText1Focus_ = value;
this.NotifyPropertyChanged("IsText1Focus");
}
}
}
これを1~3用に用意する。
○コマンド実装
各々のボタン用にコマンドを用意する。

/// <summary>Text1へフォーカス</summary>
private RelayCommand focus1Command_;

/// <summary>
/// Text1へフォーカス用コマンドを取得します。
/// </summary>
public ICommand Focus1Command {
get {return focus1Command_;}
}
これを1~3まで用意する。
○Window用VM生成時に、コマンドのインスタンスも生成する。

focus1Command_ = new RelayCommand(this.ExecuteFocus1);
focus2Command_ = new RelayCommand(this.ExecuteFocus2);
focus3Command_ = new RelayCommand(this.ExecuteFocus3);
○各々のメソッドで、フォーカス用変数の値を変える。

/// <summary>
/// 「Text1へフォーカス」コマンド実行用メソッド
/// </summary>
/// <param name="parameter">コマンドパラメータ</param>
private void ExecuteFocus1(object parameter) {
IsText1Focus = false;
IsText1Focus = true;
return;
}
一旦falseにしてtrueに変えるのは、trueからfalseに変えるタイミングが見つからなかったので。
○このプロパティに反応するStyleを定義する。

<TextBox MinWidth="100" Name="Text1" Text="Text1">
<TextBox.Resources>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding IsText1Focus}" Value="True">
<Setter Property="FocusManager.FocusedElement"
Value="{Binding ElementName=Text1}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Resources>
</TextBox>
FocusManagerというクラスは、名前の通り、フォーカスを管理するクラス。
FocusManager クラス
FocusManagerが持つFocusedElementプロパティを使う事で実現できる。データバインディングを使って、フォーカスを当てたいコントロールそのものをValueに渡す。
まとめと課題
・VM側で用意したプロパティ値の変更をトリガーにフォーカスを当てる。
・トリガーに使うだけであって、現在のフォーカス状態を示しているわけではない。
・Styleは使い回しが効かないので、いちいち書く必要がある。
・プロパティ値もfalseに戻してからtrueに設定するという実装を意図的に行う必要がある。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 2 | トラックバック: 0

Enterキー押下で(DataBindingの)ソースを更新しよう。

DataBindingのUpdateSourceTriggerには、

LostFocus(フォーカスが外れたら)
PropertyChanged(プロパティ値が変わったら)
Explicit(明示的にメソッドを呼ばないと)
Default(各プロパティの既定値)

がある。けれども、日本で比較的良く使われる、Enterキー押下でっていうのがない。PropertyChangedは、反応が良すぎる。入力してる最中でもどんどん反応する。Enterキー押下のみに反応しないものか?と調べた結果、良い記事を見つけた。
WPF TextBox DataBind on EnterKey press
なんとそのまんまだった。
続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

CommandBindingsにやられました。

WPF4には、ビルトインコマンドというものがある。その中の1つにApplicationCommands(System.Windows.Input)があり、以下のようなものを持つ。

・Cut(切り取り)
・Paste(貼り付け)
・Copy(コピー)
・Help(ヘルプファイル表示)
・Undo(1つ前の作業戻す)
・Redo(1つ後の作業実行)
・Close(閉じる)

これらのコマンドは、RoutedUICommandインターフェースを実装し、Routedイベントの様に子から親へ伝播する。
これらのコマンドと、自分が実行したいメソッドを結びつけるには、WindowクラスのCommandBindingsに登録する必要がある。
XAML上で書くとき

<Window x:Class="TawamureDays.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:nfc="http://nichia.jp/sys/foundation/component/gui"
xmlns:local="clr-namespace:TawamureDays"
Title="MainWindow" Height="213" Width="385">
<Window.CommandBindings>
<CommandBinding Command="New" Executed="ExecuteNewItemCommand"/>
</Window.CommandBindings>
<StackPanel/>
</Window>

Command="New"で、ApplicationCommands.Newを使っている事と同じになる。だからビルトイン。
Executedのメソッドは、コードビハインドで実装する必要がある。

/// <summary>
/// New(新規)コマンド実行用メソッド
/// </summary>
/// <param name="sender">発生元</param>
/// <param name="e">イベント引数</param>
private void ExecuteNewItemCommand(object sender, ExecutedRoutedEventArgs e) {
//新規作成で行う処理が実装されます。
return;
}

仕事上、添付プロパティを使って、このビルトインコマンドを使おうとすると、XAMLからの登録ではなく、コード上で登録/削除を実行する。
例えば、DataGridで貼り付けコマンド(Paste)を扱う時とかは、以下のようになる。
追加時

//貼付用コマンドをDataGridに登録します。
dataGrid.CommandBindings.Add(new CommandBinding(
ApplicationCommands.Paste,
CommandHelper.ExecutePastingData,
CommandHelper.CanExecutePastingData););

削除時

//貼付用コマンドをDataGridに削除します。
dataGrid.CommandBindings.Remove(new CommandBinding(
ApplicationCommands.Paste,
CommandHelper.ExecutePastingData,
CommandHelper.CanExecutePastingData););

しかし、実はこの実装には問題がある。通常のイベントハンドラにおいては、インスタンスを新たに作ってもきっちり削除してくれた。しかし、上記コードでは、CommandBindingsからは削除されてくれない事が最近わかった。Orz。
追加した時と同じインスタンスでないと、正常に削除されない。
なので、
追加時

var pasteBinding = new CommandBinding(
ApplicationCommands.Paste,
CommandHelper.ExecutePastingData,
CommandHelper.CanExecutePastingData);
//追加
dataGrid.CommandBindings.Add(pasteBinding);
//!このpasteBindingインスタンスをどこかに保管する。

削除時

//追加時に使ったインスタンス(pasteBinding)を取得する。
//削除
dataGrid.CommandBindings.Remove(pasteBinding);
とするしかない。CommandBindingsのClearメソッドを使ってしまうと、削除したくないものまで削除してしまいそうで怖い…。
正常に削除しないと、リークの元ネタとなって降り積もっていく…。
仕事上では一応解決はしたんだけどね…。ちゃんとデバッグで削除できていることを確認したらと思ったよ。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

添付プロパティの注意点

MVVMパターンで、イベントをどうにかしよう。
等々で、添付プロパティを使ってきたけれども、一部実装をめんどくさくて書いていないところがある。それは、バインディングの解除(削除)を実行するコードだ。書いてはいないけれども、仕事(開発)上では非常に重要なので、メモをしておく。
データバインディングを削除するには、BindingOperationsクラスのClearBindingメソッドが使われる。BindingOperationsクラスは、System.Windows.Data名前空間にある。なぜ、削除が必要か?それはメモリリークの元になる危険性があるからだ。添付プロパティの値変更イベント内で登録したイベントハンドラはもっとあからさまにリークの元になる。
イベントにハンドラ用メソッドを登録すると、明示的に削除しない限り、そのハンドラ用メソッドがそのコントロールを参照し続ける事になる。「参照し続ける」という事は、たとえ画面が終了して、GC(ガベージコレクト)が発生しても回収されない事を意味する。1回や小さい画面ならまだしも、数十個の綱目を持つ登録画面や、十数個、数千件の一欄を表示する検索画面等がリークすると、1回の起動あたりで20MByteくらいずつ増えていく事になる。
では、どうするか?
・画面終了時にかならず登録したイベントからハンドラメソッドを削除する。
・少なくともSetBinding等でバインドしたプロパティは、画面終了時にバインディングを削除しておく。
・CommandBinding等も同じ。Addすれば、画面終了時はかならずRemoveする。
リークに関する記事があったので貼っておく。
参考URL:
Finding Memory Leaks in WPF-based applications
.NET3.5時代の記事ではあるけど、おそらくWPF4にも同じ事が言えると思う。上の記事では、TextBlockのTextPropertyは必ずClearBindinしておけと書いてある。そうなのかと思い余って、いつインスタンスが不要になるかわからないDataGrid内で使われるTextBlockに、ClearBindingを実行する添付プロパティをつけて、余計にメモリリークしたのは、苦い思い出だ。
何にせよ、イベントや添付プロパティは放ったらかしにしても、問題ないような顔をしているけど、裏ではしっかりリークしている事があると思った方が良いという事かな。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

サイドバー背後固定表示サンプル

当ブログに書かれたソースコードは流用自由です。

バグ、スペルミス等はありうる事です。

ご利用の際は自己責任でお願いしますm(_ _)m