スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: スポンサー広告

マスターページならぬマスターウィンドウが欲しい。

ASP.NETにマスターページなるものがある。
ASP.NET マスター ページの概要
2.0の頃の話なので、今も盛んに活用されているかどうかは怪しいけど。
レイアウトに一貫性を持たせる為の手段の1つであった。この概念はおそらく、今も何かしらの技術を以って実現されている。と思う。
ASP.NETにかぎらず、Windows.Formsでも、似たような考えで、基底のクラスを作り、それを継承して開発していた。
という事は当然、WPFでもほしくなったわけで。
まず、そのベースとなるレイアウトを考えた。
1.メニューバー
 古臭いとは言われても、これは欲しい。リボンなんていらねー。
2.ステータスバー
 これはなかなかなくならないだろ。メッセージを表示したり、時計を表示したり。
 遊びなら、CPUの使用率とか、メモリの使用率も良いかも。
3.ボデー
 ここが各画面で変わる部分。何が入るかなんてわかりません。
4.ヘッダーとフッター
 タイトルだったり、ボタンだったり。操作系のパネル的な扱いかな。
だいたいこんなもんだと。ありふれているけど、それが良い。なにせユーザさんの中には、横文字が嫌いとか、PC嫌いとかいう人がいるので、奇抜(良く言えば斬新)すぎると、受け入れてくれない確率があがってしまう。で、以上を実査に画面に起こしてみた(まだマスター化はしてない)。
続きを読む
スポンサーサイト
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

要注意

未修正の「Java 7」脆弱性を狙うゼロデイ攻撃が拡大か
まだパッチ出てないよね?
ブラウザのJavaプラグインにも当然ある脆弱性。
要注意というところかな。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: 未分類 | コメント: 0 | トラックバック: 0

TextBlock内で改行しよう。

TextBlockは、編集しない表示のみの文字列を表示するのに良く使われる。
バインディングの場合は、素直に改行コード(System.Environment.NewLine)を設定すれば、改行してくれる。
あまりやらないけど、画面上に説明文的な文字列をベタで載せるようなときには、どうすれば良いか?をメモしておく。
<Window x:Class="TawamureDays.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<TextBlock>
TextBlock内での改行は、<LineBreak/>
&lt;LineBreak/&gt;を使います。
</TextBlock>
</StackPanel>
</Grid>
</Window>
サンプルにも書いてある通り、LineBreakで改行できる。

20120830

TextBlockブロックで囲まれた部分(文字列)は、TextPropertyではなく。Inlineプロパティに設定される。このInlinesプロパティ内で使えるのは、

Run
Underline
Hyperlink
Bold
Italic
Span

等がある。上のサンプルに追加してみた。
<Window x:Class="TawamureDays.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<TextBlock>
TextBlock内での改行は、<LineBreak/>
&lt;LineBreak/&gt;を使います。
</TextBlock>
<TextBlock>
<Span Background="Aquamarine">Spanの中にある。</Span><LineBreak/>
<Bold FontFamily="Arial">Boldの中にある。</Bold><LineBreak/>
<Italic>Boldの中にある。</Italic><LineBreak/>
<Hyperlink>http://tawamuredays.blog.fc2.com/</Hyperlink><LineBreak/>
<Underline FontSize="20">Underlineの中にある。</Underline><LineBreak/>
<Run Foreground="Aqua">Runの中にある</Run>
</TextBlock>
</StackPanel>
</Grid>
</Window>

実行すると↓のような感じ。
メニュー画面とかの説明文や、更新履歴を表示するときなんかに使えそう?
20120830_2
ちなみに、上のSpanやらItalicやらは、C#コード上でも使うことができる。
TextBlock.Inlines Property
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DaraGridのイベントからセルの情報をVMで活用する。(参)

DaraGridのイベントからセルの情報をVMで活用する。(壱)
DaraGridのイベントからセルの情報をVMで活用する。(弐)
で実装した添付プロパティは、ちょっとした癖がある。

CellClicked:
 ・任意セルをクリック→別のセルをクリックでは発生しない。
 ・↑で同じセルをもう一度押すと発生する。
 ・編集可能なセルをクリックしても発生しない。
CellDoubleClicked
 ・そんなに癖はない。ダブルクリックしたら普通に発生する(筈)。
 ・CellClickedでブレークポイントを仕掛けてしまうと発生しない。(開発上の注意)
CellChanged
 ・任意セルをクリック→別のセルをクリックで発生する。
 ・カレンセル無し→任意のセルをクリックも発生する。
 ・同じセルをもう一度押しても発生しない(カレントセルが変わってないから)。
SelectionChanged
 ・アイテム(行)選択変更で発生する。
 ・コマンドパラメータは、増減分のみ入っている。
 ・DataGridのSelectionUnitが「Cell」の時は発生しない。


試しに全部設定してみた。
<DataGrid Name="PeoplesList"
AutoGenerateColumns="False"
local:DataGridExtender.CellClickedCommand="{Binding CellClickedCommand}"
local:DataGridExtender.CellDoubleClickedCommand="{Binding CellDoubleClickedCommand}"
local:DataGridExtender.CellChangedCommand="{Binding CellChangedCommand}"
local:DataGridExtender.SelectionChangedCommand="{Binding SelectionChangedCommand}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard"
CanUserAddRows="False"
CanUserDeleteRows="False"
SelectionMode="Extended"
SelectionUnit="CellOrRowHeader"
ItemsSource="{Binding PersonsList}"
AlternatingRowBackground="AliceBlue"
>
<DataGrid.Columns>
(中略)
</DataGrid.Columns>
</DataGrid>

これに対するVM側の実装は省略する。弐を参考にする事。
実際にデバッグ実行してみた。続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DaraGridのイベントからセルの情報をVMで活用する。(弐)

壱でCellClieckedとCellDoubleClickedを書いたので、CellChangedとSelectionChanged用の添付プロパティを実装する。
【注意】
なお、前の2つもふくめ、この記事における実装は、終了時の処理(ハンドラ削除等)を省いてある。このまま実装すると、ほぼ確実にメモリリークになるので、Window終了時に登録したイベントからハンドラメソッドを削除する処理を入れる必要がある。続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DaraGridのイベントからセルの情報をVMで活用する。(壱)

DaraGridが持つセル選択、移動系イベントは以下の3つがある。

・CurrentCellChanged: 現在セル(CurrentCell)が変更された
・SelectionChanged: 選択項目が変更された
・SelectedCellsChanged: SelectedCellsコレクションが変更された


仕事で欲しかったのは、

・CellCliecked:セルがクリックされた
・CellDoubleClicked:セルがダブルクリックされた
・CurrentCellChanged:セルが移動した
・SelectionChanged:選択項目が変更された


で、微妙に求めるものがあったりなかったりする。特にダブルクリックは欲しかった。シングルクリックと区別したかったから。
シングルクリックには反応しないけど、ダブルクリックには反応するなんて機能をよく実装するし。
これらのイベントをMVVMで処理したいなーと思ったら、前に書いてた。
MVVMパターンで、イベントをどうにかしよう。
ただし、上の実装でも、どの"セル"がクリックされたか?なんて具体的な情報は、自動でセットされるわけではない。仕事では、各イベントに対応する添付プロパティを実装し、コマンドパラメータに具体的な情報を渡すようにした。続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

ウィンドウの位置やサイズを記憶しよう。

開発において、あたりまえに使用するウィンドウは、基本的にユーザが位置やサイズを自由に変更できる。
しかし、ユーザが変えた位置やサイズは、「実行時に」設定された値であるため、意図的に記憶しない限りは、前のまま(ビルド時に設定した値)が適用され、起動される。ユーザによっては、これを煩わしく感じるので、当然ながら、「記憶してくれ!」という要望が出てくる。
記憶する為には、
1.画面終了時に、サイズ、位置をファイルに保存する。
2.次回起動時に、1で記憶したサイズや位置を読み込んで、反映させる。
となる。仕事で使ったロジックをシンプル化して、添付プロパティで実現してみた。
続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DatePickerで、IsReadOnlyを設定したい。

DatePickerで、IMEをOFFにする。で、DatePickerを拡張したんだけど、もうちょっと拡張する必要が出てきた。
このDatePicker、入力系のコントロールなのに、IsReadOnlyプロパティがない
なんでないんだ?必要だろ~。
というわけで、IsReadOnlyな状態を作れるように拡張しようと思った次第。
添付プロパティでもできなくはないけど、今回は拡張という形で実装する。

○依存関係プロパティを実装
namespace TawamureDays {

/// <summary>
/// DatePicker拡張
/// </summary>
public class DatePickerEx : DatePicker {

/// <summary>
/// 読み取り専用かどうかを取得|設定します。
/// </summary>
public bool IsReadOnly {
get { return (bool)GetValue(IsReadOnlyProperty); }
set { SetValue(IsReadOnlyProperty, value); }
}

/// <summary>読み取り専用かどうか</summary>
public static readonly DependencyProperty IsReadOnlyProperty =
DependencyProperty.Register("IsReadOnly", typeof(bool),
typeof(DatePickerEx),
new UIPropertyMetadata(false, OnIsReadOnlyPropertyChanged));

}
}

○プロパティ変更通知用コールバックに設定したメソッドを実装する。
namespace TawamureDays {

/// <summary>
/// DatePicker拡張
/// </summary>
public class DatePickerEx : DatePicker {

/// <summary>
/// IsReadOnlyプロパティ変更イベントメソッド
/// </summary>
/// <param name="dpObj">変更発生元</param>
/// <param name="e">イベント引数</param>
private static void OnIsReadOnlyPropertyChanged(DependencyObject dpObj, DependencyPropertyChangedEventArgs e) {
var datePicker = dpObj as DatePickerEx;

if (datePicker == null || !datePicker.IsLoaded || datePicker.Template == null) {
return;
}

//入力用テキストボックスを検索
var textBox = datePicker.Template.
FindName("PART_TextBox", datePicker) as DatePickerTextBox;

if (textBox != null) {
textBox.IsReadOnly = (bool)e.NewValue;
}

//カレンダー表示用ボタンを検索
var button = datePicker.Template.FindName("PART_Button", datePicker) as Button;
if (button != null) {
button.Visibility = (bool)e.NewValue ?
Visibility.Collapsed : Visibility.Visible;
}

return;
}
}
}

○OnApplyTemplateメソッドをオーバーライド

namespace TawamureDays {

/// <summary>
/// DatePicker拡張
/// </summary>
public class DatePickerEx : DatePicker {

/// <summary>
/// テンプレート適用時の処理
/// </summary>
public override void OnApplyTemplate() {
base.OnApplyTemplate();

var textBox = Template.FindName("PART_TextBox", this) as DatePickerTextBox;

if (textBox != null) {
InputMethod.SetPreferredImeState(textBox, InputMethodState.Off);
textBox.IsReadOnly = this.IsReadOnly;
}

var button = Template.FindName("PART_Button", this) as Button;
if (button != null) {
button.Visibility = this.IsReadOnly ?
Visibility.Collapsed : Visibility.Visible;
}

return;
}

}
}

これでIsReadOnly設定可能なDatePickerになる。かな?
キー入力に対して特殊な操作("日"にカレットがあって、↑押下で翌日、とか)を行うような実装をしようとすると、このIsReadOnlyのチェックが欠かせなくなるかな。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

マークアップ拡張(x:)

マークアップ拡張機能と XAML
x:プレフィックスにはお世話になってます。

○x:Type
タイプを指定する時に使う。StyleのTargetTypeやDataTemplateのDataType、BindingのRelativeSourceなんかにも使う。

<Window.Resources>
<Style TargetType="{x:Type Label}">
...
</Style>
</Window.Resources>

↑は以下のようにもできる。

<Window.Resources>
<Style TargetType="Label">
...
</Style>
</Window.Resources>

自分が作ったコントロールとかは、確実にx:Typeを使う必要がある。

○x:Static
staticな変数を扱う。System名前空間にある。staticな変数を扱う事もできる。

<Window x:Class="TawamureDays.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:local="clr-namespace:TawamureDays"
Title="TawamureDays" Height="300" Width="300">
<StackPanel>
<Label Content="{x:Static sys:decimal.Zero}"/>
<Label Content="{x:Static sys:DateTime.Today}"/>
</StackPanel>
</Window>

自分が作ったstaticな変数を呼び出す事もできる。

○x:Null
nullを設定する時に使う。時々使う。

○x:Array
配列を扱う。でもあまり使った覚えはない。

○x:Key
リソースに対するキーとなる。

○x:Name
コントロールに名前を設定するときに使う。でもコード側から設定した値を取るには苦労する。
XAML上、BindingのElementNameに指定できるので重宝される。

XAML2009からは、x:Stringやx:Int32なんかを使えるらしい。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

各コントロール用のViewModelを用意しよう。

MVVMパターンでは、VからVMのプロパティに対してデータバインディングを行う。
コントロールの数だけ、そしてバインディングするプロパティの数だけ、ViewModel側にもそのプロパティを用意する必要がある。TextBlockであれば、Textプロパティのみでも良い。でも、TextBoxだとそれ(Textプロパティ)だけというのは、開発上あまりない。
・IsReadOnlyプロパティとバインディングしたい。
・IsEnabledも必要があればバインディングしたい。
・Focus系とも連携させたい。
・エラーテンプレートとも連動したくなる時があるかもしれない。
ViewModel側でやりたいことはいくらでも出てくる。
画面用ViewModelのプロパティで、TextBoxとデータバインディングさせるとどうなるだろう?

<Window x:Class="TawamureDays.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TawamureDays" Height="300" Width="300">
<StackPanel>
<TextBox MinWidth="100"
Name="Text1"
IsReadOnly="{Binding IsText1ReadOnly}"
IsEnabled="{Binding IsText1Enabled}"
/>
</StackPanel>
</Window>

ViewModel側には、IsTextReadOnlyとIsTextEnabledプロパティの2つを用意する必要がある。
これに
ViewModel側から特定コントロールにフォーカスを当てる(弐)。
で書いたような添付プロパティも使おうとすると、

<Window x:Class="TawamureDays.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TawamureDays"
Title="TawamureDays" Height="300" Width="300">
<StackPanel>
<TextBox MinWidth="100"
Name="Text1"
IsReadOnly="{Binding IsText1ReadOnly}"
IsEnabled="{Binding IsText1Enabled}"
local:FrameworkElementBehavior.IsFocused="{Binding IsText1Focus}"
/>
</StackPanel>
</Window>
と、1つのTextBoxにつき3つのプロパティを用意する必要がある。
仮に1画面で、n個のTextBoxが必要なら、3n個のプロパティを用意する事になる。
n=10なら、30個…。それも似たようなのが一杯。そして、それらの変更を監視しよう(PropertyChangedのイベントをフックしよう)と思うなら、switch文に各々のcaseを書いて…。
キー!めんどくせ!
となってしまう。まあ、実際作ってみて、これは、毎度毎度似たようなプロパティをちくちく用意するのは大変というのを実感した。
あと、バインディングって、ミスってもそうそう落ちはしないし、ビルドエラーにもならない。でも、ほうっておくと、確実にパフォーマンスに影響する。
沈黙の臓器か!と言いたくなる。
というわけで、ウィンドウ用のViewModelを用意すると共に、各コントロールについてのViewModelも用意した方が良いという結論に至った。全部用意するだけの時間はなかったので、高頻度で使うコントロールについてViewModelを用意した。
用意したコントロール。

TextBox
DatePicker
ComboBox
DataGrid
ProgressBar


用意しなかったコントロール。

ListBox:あまり使わなかった。ListViewもDataGridで代用。
CheckBox:IsChekcedくらいとしかバインディングしないので作っていない。
Button:Commandくらいとしかバインディングしないので(ry。


ウィンドウ用ViewModelを実装→各コントロール用ViewModelを実装を経て、ようやく画面開発に入れた感じかな。
当然、一番実装に苦労したのは、DataGrid用のViewModelだけどね。
理由もわからず動かないとか、1回だけ動いて2度目はないとか…。苦労したなぁ(遠い目)。
まあ、別に1人で苦労したわけではなく、チーム全員で苦労したんだけど。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

インスタンス生成あれこれ

ここでは、

public class ....等で定義される物を「クラス」
このクラスをnew xxxx()で作られた物を「インスタンス」

と呼ぶことにする。
このインスタンスを生成する方法には、大きくわけて、「newを使う」か、「リフレクションを使うか」がある。
・前者は、作成するクラスが、コーディング段階で具体的に決まっている場合、
・後者は、コーディング段階では決まらない(実行時に初めて決まる)場合
によく使われる。
で、3番目の選択肢として、「式木を使う」というのがC#4から(厳密には3から)出てきた。
式木を使う時の用途は、リフレクションを使う時のそれに近い。実行時に初めて型がわかる時。
これらのインスタンス生成に関する方法を何通りか実装して、比較する。
実装方法は違うけど、求める結果(Output)は同じ。インスタンスを生成すること。
方法に関する比較項目は、生成する際の時間的コスト≒「速さ」とする。
続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: C# | コメント: 0 | トラックバック: 0

背景色をじわじわ定期的に変えてみた。

WPF4が持つアニメーション系機能の習作。そのまま仕事で使えるわけでない。
背景色を青→白→青…。という感じで変わるだけ。

<Window x:Class="TwAnime1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TwAnime1" Height="300" Width="300">
<Grid>
<Grid.Resources>
<Style TargetType="Grid">
<Setter Property="Background" Value="SkyBlue"/>
</Style>
</Grid.Resources>
<Grid.Triggers>
<EventTrigger RoutedEvent="Grid.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard TargetProperty="Background.Color">
<ColorAnimation From="White" To="Blue"
Duration="0:0:3"
AutoReverse="True"
RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Grid.Triggers>
</Grid>
</Window>

ここで初めて使うのがTrigger3人衆最後の1人であるEventTrigger。
プロパティである「RoutedEvent」は、その名の通り、RoutedなEventクラスしか指定できない。
今回は、起動時(Loaded)にアニメーションが実行されるように設定している。
アニメーションの設定は、
Grid.Triggers→EventTrigger.Actions→BeginStoryboard→Storyboard
と、階層の深いところが、なんとなく面倒くささを感じる。
StoryboardのTargetPropertyは、アニメーションさせるプロパティ。
最初、「Background」を設定してたんだけど、動かなかった。あれ?と思い、Loadedではなく、MouseEnterで試すと、以下のような例外を発生して落ちた。Orz

System.InvalidOperationException: 'System.Windows.Media.Animation.ColorAnimation' アニメーション オブジェクトは、互換性のない型 'System.Windows.Media.Brush' であるため、プロパティ 'Background' をアニメーションで表示するためには使用できません。

まあ、あれだ。Backgroundプロパティには使えないと。じゃあってんで、Background.Colorにしたら、動いた次第。
ColorAnimationは、色用のAnimationクラス。数値系ならDoubleAnimation。
数値系は、幅(Width)、高さ(Height)、透明度(Opacity)に使えそう。
このAnimation系には、色々な設定ができる。
○FromとTo:始まりの値と終了時の値。省略可能だったりする。
○Duration:FromからToに至るまでに掛ける時間。
 指定方式は、「H:m:s.f」(fはミリ秒)
○By:FromやToではなく、振れ幅で指定できる。
 From=50 By=50の場合は、50から始まり、100で終わる。
○AutoReverse:自動でFromに戻るかどうか。
 Trueにすると、1つのアニメーションに掛かる時間的コストが倍になる。
○RepeatBehavior:繰り返し回数あるいは、繰り返し時間。Foreverを設定すると、無限になる。

他にもあるけど、使ってみないとピンと来ないのがあったりする…。
Windows7でWPF4を使うと、TextBoxやButtonに結構アニメーションが使われている。
WPF Themesにあるリソース用XAMLにそれっぽいのがあるんだけど、あれ、いっぱい記述されてて、読む気になれない…。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

式木なるものを半ば無理矢理使う。

C#4.0(厳密には3.0)から式木というものがある。
何ができるの?と言われれば、
「ラムダ式を動的に作れる」
とか
「メソッドを動的に作れる」
とかになる。
↓で勉強させていただきました。
式木(Expression Trees)
「動的に作れる事」で、リフレクションでやっていた事
・クラスタイプからインスタンスを生成する。
・インスタンスから、文字列で指定されたプロパティ名の値を取得又は設定する。
を式木で実行できる。
何の意味があるのか?
→リフレクションで実行するよりパフォーマンスが良い(速い)。
 ※ただし、キャッシュ機能必須
まあ、これもどれくらい速いかと言われたら、ほんのちょっとなんだけど。
DataGrid等の表示処理なんかは、こういう「ほんのちょっと」が地味に効くかも?とか思いつつ使ってみました。
上のサイトの中では「桁が違う」くらい遅いらしいんだけど。
まあ、そこらへんの速さの比較も書いていこうかな。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: C# | コメント: 0 | トラックバック: 0

DataGridTextColumnを更に拡張する。

DataGridTextColumnで右詰めにしたい時が多々ある。で拡張したDataGridTextColumnを更に拡張する。
拡張1:最大文字数を設定できるようにする。
→MaxLengthという添付プロパティを実装する。

/// <summary>
/// 入力可能な最大文字数を取得/設定します。
/// </summary>
public int MaxLength {
get {return (int)GetValue(MaxLengthProperty);}
set {SetValue(MaxLengthProperty, value);}
}

/// <summary>入力可能な最大文字数</summary>
public static readonly DependencyProperty MaxLengthProperty =
DependencyProperty.Register("MaxLength", typeof(int),
typeof(DataGridTextExColumn), new UIPropertyMetadata(0));
上記記事内のGenerateEditingElementメソッド内で、このMaxLengthを設定する。

/// <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;
}


拡張2:改行コードを入力できるようにする。
通常は、エンターキー押下でセル編集が確定してしまうので、させないようにする必要がある。
まずは、添付プロパティ実装

/// <summary>
/// 複数行入力を可能にするかどうかを取得/設定します。
/// </summary>
public bool MultiLine {
get {return (bool)GetValue(MultiLineProperty);}
set {SetValue(MultiLineProperty, value);}
}

/// <summary>複数行入力?</summary>
public static readonly DependencyProperty MultiLineProperty =
DependencyProperty.Register("MultiLine", typeof(bool),
typeof(DataGridTextExColumn), new UIPropertyMetadata(false));

次に、GenerateEditingElementメソッドの実装を変更する。

/// <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;

if (this.MultiLine) {
textBox.AcceptsReturn = false;
textBox.PreviewKeyDown += new KeyEventHandler(TextBox_PreviewKeyDown);
textBox.Unloaded += new RoutedEventHandler(TextBox_Unloaded);

//エンターキー押下によるフォーカス移動から除外
FocusMoveBehavior.SetExcluded(textBox, true);
}

return textBox;
}
PreviewKeyDown(キーダウン直前イベント)実装が重要かな

/// <summary>
/// 編集用TextBox キーダウン前イベントハンドラ
/// </summary>
/// <param name="sender">イベント発生元</param>
/// <param name="e">イベント引数</param>
private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e) {

if (Keyboard.IsKeyDown(Key.Enter) || Keyboard.IsKeyDown(Key.Return)) {
var val = ModifierKeys.Alt & e.KeyboardDevice.Modifiers;

if (val > 0) {
//①Alt+Enterで改行を挿入しましょう。
var textBox = sender as TextBox;
var caret = textBox.CaretIndex;
textBox.Text = textBox.Text.Insert(caret, Environment.NewLine);
textBox.CaretIndex = caret + 1;

//②行の高さを調整しましょう。
var cellsPresenter = textBox.FindAncestor<DataGridCellsPresenter>();

if (cellsPresenter != null) {
//改行コードの数をカウントします。
var lineCnt = Utils.GetMatchStringCount(
textBox.Text, Environment.NewLine) + 1;

//標準で設定されている行の高さを取得します。
//DataGridRowに対する高さ(Height)が未設定の場合のみ、
//DataGridのRowHeightプロパティを取得します。
var dataGridRow = cellsPresenter.FindAncestor<DataGridRow>();
var height = dataGridRow.Height;

if (double.IsNaN(height)) {
var ownerDataGrid = dataGridRow.FindAncestor<DataGrid>();
height = ownerDataGrid.RowHeight;

if (double.IsNaN(height)) {
height = 22D;//みつからなければ、強制的に22で
}
}

cellsPresenter.Height = (double)(lineCnt * height);
}

e.Handled = true;//これにて処理は終了
}
}

return;
}

①で、Alt+Enter押下で改行コードを現在のカレット位置に挿入する
②で、行の高さを調整している。態々DataGridCellsPresenterというクラスを検索して、そいつに高さを設定しているのは、そうしないと期待どおりにならないから。
 高さの基準をDataGridRow→DataGridの順に探している。
Unloadedイベント時の処理は後始末。

/// <summary>
/// 編集用TextBoxアンロードイベントハンドラ
/// </summary>
/// <param name="sender">イベント発生元</param>
/// <param name="e">イベント引数</param>
private void TextBox_Unloaded(object sender, RoutedEventArgs e) {
var textBox = sender as TextBox;

textBox.PreviewKeyDown -= new KeyEventHandler(TextBox_PreviewKeyDown);
textBox.Unloaded -= new RoutedEventHandler(TextBox_Unloaded);
BindingOperations.ClearBinding(textBox,
FocusMoveBehavior.ExcludedProperty);

return;
}

GetMatchStringCountメソッドは、特定文字列の数を検索するユーティリティなメソッド。

/// <summary>
/// 指定された文字列から、指定の文字列が何文字含まれているかを返します。<br/>
/// </summary>
/// <remarks>
/// 指定された文字列内に、指定の文字列が何文字含まれているかを返します。<br/>
/// 大文字小文字を区別するかどうかは、引数で指定できます。<br/>
/// 文字列の検索は、正規表現で実装されています。<br/>
/// </remarks>
/// <param name="source">文字列</param>
/// <param name="search">指定の文字列</param>
/// <param name="ignoreCase">true:大文字小文字を無視して検索します。</param>
/// <returns>文字数</returns>
public static int GetMatchStringCount(string source, string search, bool ignoreCase = false) {
string resultString = string.Empty;

//using System.Text.RegularExpressions;が必要。
Regex reg = null;

if (ignoreCase) {
reg = new Regex(search, RegexOptions.IgnoreCase);
} else {
reg = new Regex(search);
}

var m = reg.Match(source);
var counter = TawamureClosure.CountUp();

while (m.Success) {
counter();
m = m.NextMatch();
}

return counter();
}

実際に動作させるとこんな感じ。
20120812_1
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

JSON形式でオブジェクトをデシリアライズする

JSON形式でオブジェクトをシリアライズする
で、クラスをシリアライズする方法をメモったけど、逆に取り込む方を書いていなかった。
操作的には逆、というか、Writeメソッドの代わりにReadメソッドを使うだけ。

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Runtime.Serialization.Json;

namespace TawamureDays {

public static class Utils {

/// <summary>
/// 指定されたファイル(JSON形式)から、内容をデシリアライズして
/// オブジェクトを取得します。<br/>
/// </summary>
/// <remarks>
/// 指定されたファイルの内容(JSON形式)をデシリアライズし、
/// 指定した型のオブジェクトとして取得します。<br/>
/// 注意:<br/>
/// ここで使用するシリアライザは、DataContract属性及びDataMember属性を
/// 付けているクラス、プロパティのみに有効に働きます。<br/>
/// 上記属性を使う場合は、System.Runtime.Serialization.dllを参照設定に加え、
/// System.Runtime.Serializationをusingしてください。<br/>
/// </remarks>
/// <typeparam name="T">デシリアライズ対象の型宣言</typeparam>
/// <param name="filePath">読込元のファイルパス</param>
/// <returns>デシリアライズされたオブジェクト</returns>
/// <exception cref="FileNotFoundException">指定されたファイルパスが存在しないとき</exception>
/// <exception cref="InvalidOperationException">オブジェクトをデシリアライズできない時</exception>
public static T DeserializeFromJson<T>(string filePath) {
if (!File.Exists(filePath)) {
throw new System.IO.FileNotFoundException(
"指定のファイルが存在しません", filePath);
}


//デシリアライザセット
var serializer = new DataContractJsonSerializer(typeof(T));
T obj = default(T);

if (new FileInfo(filePath).Length == 0) {
return obj;
}

//出力
MemoryStream memStr = null;

try {
//メモリストリームに取り込んでからデシリアライズを行います。
using (memStr = new MemoryStream(
Encoding.UTF8.GetBytes(
File.ReadAllText(filePath, Encoding.UTF8)))) {

System.Xml.XmlDictionaryReader xmlReader = null;

try {
using(xmlReader =
JsonReaderWriterFactory.
CreateJsonReader(memStr,
System.Xml.XmlDictionaryReaderQuotas.Max)) {
obj = (T)serializer.ReadObject(xmlReader);
}

} finally {
if (xmlReader != null) {
GC.SuppressFinalize(xmlReader);
xmlReader = null;
}
}
}

} finally {
serializer = null;
}

return obj;
}
}
}

・シリアライズ/デシリアライズしたいクラスにDataContract(System.Runtime.Serialization)属性をつける必要がある。
・プロパティには更にDataMember(System.Runtime.Serialization)属性をつける必要がある。付けていないプロパティは対象外になる。
・後付けで追加したプロパティは、DataMemberのIsRequiredプロパティをfalseに設定しておいたほうが無難。
だいたい↓みたいな感じ

[DataContract(Namespace="TawamureDays")]
public class PersonData : BaseViewModel {

[DataMember(IsRequired=true, Order=0)]
public string FirstName { get; set; }

[DataMember(IsRequired=true, Order=2)]
public string SecondName { get; set; }

[DataMember(IsRequired=true, Order=2)]
public int Age { get; set; }

[DataMember(IsRequired=true, Order=3)]
public DateTime Birthday {get; set;}
}

使い方

//ファイルパス(拡張子は適当)
var PersonsList = new List(3) {
new PersonData {FirstName = "太郎",
SecondName = "田和牟礼",
Age=20, Birthday = DateTime.Today.AddYears(-20)}
,new PersonData {FirstName = "二郎",
SecondName = "田和牟礼",
Age=16, Birthday = DateTime.Today.AddYears(-16)}
,new PersonData {FirstName = "三郎",
SecondName = "田和牟礼",
Age=12, Birthday = DateTime.Today.AddYears(-12)}
};

var path = @"C:\Users\Tawamure\Desktop\PersonsList.json";
//シリアライズ(ファイルへの書き出し)
//主に画面終了時とかに使いたい。
Utils.SerializeToJson(PersonsList.ToArray(), path);
...
//デシリアライズ(ファイルから取り込み)
//主に画面起動時に使いたい。
var list = Utils.DeserializeFromJson(path);

画面のサイズや位置、DataGridの列位置や幅等を記憶したい時に使った。
別にXMLファイルでも良かったんだけどね。どうせならってんでJSONにしてみた。続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: C# | コメント: 0 | トラックバック: 0

Viewを更新中のUI更新を後回しにしたいんだ

Viewを更新させるとき、何も処置をしないとリアルタイムでビューが更新されるようで。パフォーマンス的に非常によろしくない。実用に耐えられないぐらい。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;

...
//ビュー(既定)を取得する。
var listView = CollectionViewSource.GetDefaultView(PersonsList) as ListCollectionView;

//FirstNameプロパティで昇順にソート
listView.SortDescriptions.Add(
new SortDescription("FirstName", ListSortDirection.Ascending);


↑のように何もせずにソートを実行したりすると、もう大変だ。100件程度ならどうって事はない。ただ、これが数千件になると、望まない「溜め」を生む事になる。
ソートすると、一旦すべてのLoad中のDataGridRowをアンロードして、必要な分のDataGridRowをロードするようで。UI仮想化してないともう悲惨です。UI仮想化していても大変です。
その更新を後回しにできるメソッドがビューにはある。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;

...
//ビュー(既定)を取得する。
var listView = CollectionViewSource.GetDefaultView(PersonsList) as ListCollectionView;

//FirstNameプロパティで昇順にソート
using (listView.DeferRefresh()) {
listView.SortDescriptions.Add(
new SortDescription("FirstName", ListSortDirection.Ascending);
}

DeferRefreshメソッドで更新を待ってもらう。usingを抜けた後で更新される。
こうすることでパフォーマンスを上げることができる。ただし、このメソッド、いつでも使えるわけではない。
アイテムを追加していたり、セル編集中にソートを実行して、このDeferRefreshを読んだりすると、落ちるんだ。
なので、事前に確認する必要がある。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;

...
//ビュー(既定)を取得する。
var listView = CollectionViewSource.GetDefaultView(PersonsList) as ListCollectionView;
//編集中ならキャンセル
if (listView.IsEditingItem) {
listView.CancelEdit();
}
//追加中ならキャンセル
if (listView.IsAddingNew) {
listView.CancelNew();
}

//FirstNameプロパティで昇順にソート
using (listView.DeferRefresh()) {
listView.SortDescriptions.Add(
new SortDescription("FirstName", ListSortDirection.Ascending);
}

列クリックによるソート等は、対策がなされているの(編集中にソートするとキャンセルされている)で、自分がViewModel側からソートする時のみ注意が必要となる。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

Listをバインディングしても、実際に連動するのはView

DataGridのItemsSourceプロパティにIList<>型をデータバインディングさせ、画面上で列をクリックしてソートを行っても、バインディングしたリスト内の順序がそのとおりになっているか?というとそんな事はない。連動するものだと思ってたら、あれ?となる。
これは、Listそのものではなく、そこから作ったViewをDataGridと連動させているから。なので、ViewModel側からソートしてたり、フィルタリングやグルーピングを行いたいときは、このViewを取得して、そのビューのメソッドを使う必要がある。
Listから、ビュー(基底)を取得しようと思った時は、以下のようにする。

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
...

var PersonsList = new List(3) {
new PersonData {FirstName = "太郎", SecondName = "田和牟礼",
Age=20, Birthday = DateTime.Today.AddYears(-20)}
,new PersonData {FirstName = "二郎", SecondName = "田和牟礼",
Age=16, Birthday = DateTime.Today.AddYears(-16)}
,new PersonData {FirstName = "三郎", SecondName = "田和牟礼",
Age=12, Birthday = DateTime.Today.AddYears(-12)}
};

//ビュー(既定)を取得する。
var listView = CollectionViewSource.GetDefaultView(PersonsList);

listViewは、ICollectionViewインターフェース実装オブジェクトとなる。
特に自分で実装しない限り、ListCollectionViewクラスとなる。
ListCollectionView クラス
以下のプロパティを持つ
CanSort:ソート可能か。値はtrue
CanGroup:グルーピング可能か。値はtrue
CanFilter:フィルタリング可能か。値はtrue
編集系
CanAddNew:新規アイテムを追加可能か。値はtrue
CanRemove:アイテムを削除可能か。値はtrue

どおりで普通にリスト型をバインドさせてもソートとかできるわけだと理解できた。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGridCellInfoから、バインドされているプロパティを取得する。

DataGridにおけるバインディングは、列単位で指定する。ということは、列がわかれば、そこで使われている(バインディングされている)プロパティがわかる(筈)という事。
ただ、DataGridCellInfoには、Itemという行にバインディングされているオブジェクトは取得できるが、そのセル(列)にバインディングされているプロパティを取得するプロパティはない。
なんで無いのか?という推測はできるけど、無いものはないので、限定的な前提で取得できるメソッドを作った。

/// <summary>
/// 指定されたDataGridColumnのプロパティパスを取得します。<br/>
/// ソートメンバーパスが設定されていなければ、バインドされているプロパティ名を
/// 取得します。<br/>
/// </summary>
/// <param name="column">DataGridColumnオブジェクト</param>
/// <returns>バインディングされているプロパティのパス</returns>
public static string GetBindingPath(this DataGridColumn column) {
var path = column.SortMemberPath;

if (!string.IsNullOrWhiteSpace(path)) {
return path;
}

if (column is DataGridBoundColumn) {
var binding = (column as DataGridBoundColumn).Binding as Binding;

if (binding != null) {

if (!StringUtil.IsEmpty(binding.XPath)) {
return binding.XPath;
} else if (binding.Path != null) {
return binding.Path.Path;
}
}
} else if (column is DataGridComboBoxColumn) {
return ((DataGridComboBoxColumn)column).SelectedValuePath;
}

column = null;
return path;
}

使い方:

using
...

if (dataGrid.CurrentCell != null) {
//カレントセルに表示されているプロパティ名を取得します。
var path = dataGrid.CurrentCell.Column.GetBindingPath();
//...
}


・SortMemberPathプロパティを優先して取得します。
・SortMemberPathプロパティが未設定の時、且つ対象の列がDataGridBoundColumnである時のみ、
 バインディングオブジェクトからパスを取得します。
SortMemberPathを取得するのは、DataGridTemplateColumn用。DataGridTemplateColumnは、DataGridBoundColumnを継承していないので、Bindingオブジェクトを使うことはできない。
DataGridComboBoxColumnもDataGridBoundColumnを継承していないので、別処理になっている。
DataGridComboBoxColumn クラス
課題:
 このメソッドは、マルチバインディングされている列には対応できない。
 マルチバインディングを使用していないというのが、「限定的な前提」。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGridCellInfoから、DataGridCellを探す。

DataGridCellInfoは、DataGridCellそのものではない(当たり前)。なので、
文字色や背景色、境界線(Border)等、Visual的な操作はできない。
DataGridCellInfoからDataGridCellを特定できないか?と調べたら、
「できることはできるけど、結構めんどくさい」
という結果になった。
手順的には…
①DataGridCellInfoのItemプロパティから、DataGridRowオブジェクトを探す。
②DataGridRowの子コントロールから、DataGridCellsPresenterオブジェクトを探す。
③DataGridCellsPresenterオブジェクトとDataGridCellInfoのColumnプロパティとで、DataGridCellオブジェクトを特定する。
箇条書きなのに、めんどくさいOrz。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace TawamureDays {

public static class DataGridExtender {

/// <summary>
/// (拡張メソッド)
/// 指定されたセル情報からDataGridCellオブジェクトを取得します。<br/>
/// </summary>
/// <param name="dataGrid">DataGridオブジェクト</param>
/// <param name="gridCellInfo">セル情報</param>
/// <returns>結果とオブジェクトのタプル(Item1:成否, Item2:DataGridCellオブジェクト)</returns>
public static Tuple<bool, DataGridCell> GetGridCellFromCellInfo(
this DataGrid dataGrid, DataGridCellInfo gridCellInfo) {

if (gridCellInfo == null || !gridCellInfo.IsValid || gridCellInfo.Item == null ||
dataGrid.Items.Count == 0) {
//パフォーマンス対策:そもそも探せる状況じゃないなら、終了
return Tuple.Create(false, null as DataGridCell);
}

//現在のセル(のItem)から現在の行(DataGridRow)を特定します。
var gridRow = (DataGridRow)dataGrid.ItemContainerGenerator.
ContainerFromItem(gridCellInfo.Item);

if (gridRow == null) {
return Tuple.Create(false, null as DataGridCell);
}

//列インデックスを取得します。
var colIndex = dataGrid.Columns.IndexOf(gridCellInfo.Column);

if (colIndex < 0) {
//列インデックスは、0以上の筈。
return Tuple.Create(false, null as DataGridCell);
}

//FirstOrDefaultで検索にヒットした最初のオブジェクトを取得します。
var cellsPresenter = gridRow.
FindVisualChildren<DataGridCellsPresenter>().FirstOrDefault();

if (cellsPresenter == null) {
return Tuple.Create(false, null as DataGridCell);
}

//DataGridCellを特定します。
var gridCell = cellsPresenter.ItemContainerGenerator.
ContainerFromIndex(colIndex) as DataGridCell;
return Tuple.Create(gridCell != null, gridCell);
}
}
}

色々な「if」を乗り越えて初めてDataGridCellオブジェクトにたどり着く。
使い方は、こんな感じ。

var dataGrid = dpObj as DataGrid;

if (dataGrid.CurrentCell != null) {
var dataGridCell = dataGrid.GetGridCellFromCellInfo(dataGrid.CurrentCell);

if (dataGridCell.Item1) {
....
}
}

これで、DataGridのCurrentCellオブジェクトから、DataGridCellオブジェクトを特定できる。
DataGrid向けの添付プロパティを実装する時に、裏側から色々操作したい時に重宝できる。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

VisualTreeを使って、目当てのコントロールを探す

VisualTreeとLogicalTreeの続き?
このVisualTreeをさかのぼって、あるいは下って、目当てのコントロールを探す。
まずは上る方

/// <summary>
/// 指定されたDependencyObjectからビジュアルツリーをさかのぼり、
/// Genericで指定された型のオブジェクトを検索します。
/// 【CAUTION!!】DataGridColumnを探す事はできません!)<br/>
/// 検索できなかった時はnullを返します。<br/>
/// </summary>
/// <remarks>
/// DataGridColumnは、VisualTree、LogicalTree両方に属さない特殊なクラスなので、
/// このメソッドを使ってDataGridやDaraGridRowオブジェクトを取得しようとしても失敗します。<br/>
/// </remarks>
/// <typeparam name="T">検索対象となるクラスの型宣言</typeparam>
/// <param name="depObj">DependencyObjectインスタンス</param>
/// <returns>検索対象オブジェクト</returns>
public static T FindAncestor<T>(this DependencyObject depObj) where T : class {
var target = depObj;

try {
do {
//ビジュアルツリー上の親を探します。
//T型のクラスにヒットするまでさかのぼり続けます。
target = System.Windows.Media.VisualTreeHelper.GetParent(target);

} while (target != null && !(target is T));

return target as T;
} finally {
target = null;
depObj = null;
}
}


逆の、下って探すタイプ

/// <summary>
/// 指定されたDependencyObjectからビジュアルツリーを下り、
/// Genericで指定された型のオブジェクトを検索します。<br/>
/// 検索できなかった時はnullを返します。<br/>
/// </summary>
/// <typeparam name="T">検索対象となるクラスの型宣言</typeparam>
/// <param name="depObj">依存関係プロパティオブジェクト</param>
/// <param name="descendant">true:子だけでなく、孫、玄孫も探しに行く</param>
/// <returns>検索対象オブジェクトが列挙されたシーケンス</returns>
public static IEnumerable<T> FindChildren<T>(
this DependencyObject depObj, bool descendant = true) where T : class {
var count = System.Windows.Media.VisualTreeHelper.GetChildrenCount(depObj);

try {
foreach (var idx in Util.RangeEnumerator(0, count - 1)) {
var child = System.Windows.Media.VisualTreeHelper.GetChild(depObj, idx);

if (child != null) {
if (child is T) {
yield return child as T;
}

if (descendant) {
count = System.Windows.Media.VisualTreeHelper.GetChildrenCount(child);
if (count > 0) {
foreach (var ch in child.FindChildren<T>(descendant)) {
yield return ch;
}
}
}
}
}
} finally {
depObj = null;
}
}

UtilクラスのRangeEnumeratorメソッドは、指定された範囲の数値をIEnumerable<int>で返すメソッド

/// <summary>
/// 指定された開始インデックス(start)から終了インデックス(end)までの
/// 数値が列挙されたシーケンス(+方向へ)を取得します<br/>
/// </summary>
/// <param name="start">開始インデックス</param>
/// <param name="end">終了インデックス</param>
/// <returns>数値シーケンス(+方向)</returns>
public static IEnumerable<int> RangeEnumerator(int start, int end) {
for (int i = start; i <= end; i ++) {
yield return i;
}
}
続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGridの現在のセル情報を取得する

DataGrid上のクリックされたセルは、「現在のセル」という扱いで、CurrentCellというプロパティに設定されている。
CurrentCellというプロパティは、DataGridCellクラスそのものではなく、DataGridCellInfoという構造体である。
DataGridCellInfo
回りくどい感じがしないでもないが、このほうが都合が良いんだろう。
というわけで、ちょっと試してみる。

<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}">
<DockPanel>
<StackPanel DockPanel.Dock="Bottom" Orientation="Vertical"
DataContext="{Binding CurrentCell, ElementName=PeoplesList}">
<TextBlock>Column:<TextBlock Text="{Binding Column.Header}"/></TextBlock>
<TextBlock>IsValid:<TextBlock Text="{Binding IsValid}"/></TextBlock>
<TextBlock>Item:<TextBlock Text="{Binding Item}"/></TextBlock>
<TextBox MinWidth="100" Text="Focusをはずす為のTextBox" IsReadOnly="True"/>
</StackPanel>
<DataGrid Name="PeoplesList"
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="ColAge2"
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>
</DockPanel>
</Window>

DataGridに名前を設定し、StackPanelのDataContextにCurrentCellプロパティをバンディングさせる。
その上で、各TextBlockにDataGridCellInfoが持つプロパティを設定している。
早速、実行。
起動直後
20120802_1

次、セルをクリックした。
20120802_2
・Columnには、クリックしたセルの列ヘッダが出ている。
・IsValidはtrue(有効)
・Itemは、その行にバインディングされているListのアイテム。
ここまではわかる。次。下のTextBoxをクリックしてフォーカス移動させてみた。
20120802_3

あれ?Falseになってるし、ColumnもItemもとれていない。
どうも、CurrentCellってのはフォーカスが当たっているときだけの情報で、フォーカスが外れると
消えるらしい。ああ、だからDataGridCellInfoなんて構造体を使ってるんだな。
でもなぁ。CurrentCellのItemを現在のアイテムで更新対象なんかにした場合、保存ボタンを押した瞬間に
その情報が消える…。CurrentCellをあてにして、VM側に(TwoWayで)バインディングしても意味ねー!というか使えねーというか…。
かなりがっくり来た。結局、セル系のイベントを取得して、連動しないようにしたんだっけ。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGridTemplateColumnの痒いところ。

型にはまらない(?)列を提供してくれるDataGridTemplateColumnだけども、こいつにもちょっとした欠点がある。
DataGridColumn系って、F2キーやダブルクリックで編集モードに入るんだけど、
DataGridTemplateColumnを使った列で、編集モードになっても、編集用に用意したTextBoxやDatePickerにフォーカスがあたらず、もう一度マウスでクリックしてやっと編集ができるという動きになってしまう。
当たり前だけど、stackoverflowには、ポストされていた。
DataGridTemplateColumn Click problem
まあ、↑にある対策でだいたい解決する。いやー助かります。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

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

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

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

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

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。