要るかどうかは別にして、時計みたいなのを作った。

仕事では、ステータスバーの右の方に、時刻を表示する実装を行った。
そもそも、OSのタスクバーに既にあるのに、なぜ必要かは疑問に思うところだけども、作ってみるのも悪くないと思い、作った次第だ。Windowを作るたびに設定するのはアホらしいので、不要になったらいつでも外せるように、カスタムコントロールで作った。
追加→新しい項目→カスタムコントロールを選択して、コントロール名を入力すれば楽かな?
カスタムコントロールが項目にないときは、AssemblyInfo.csをチェックしておく
TwClock.cs
using System;
using System.Windows;
using System.Windows.Controls;

namespace TawamureDays.Controls {

/// <summary>
/// Tawamure Clock
/// </summary>
public class TwClock : Control {

/// <summary>
/// staticコンストラクタ
/// </summary>
static TwClock() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(TwClock),
new FrameworkPropertyMetadata(typeof(TwClock)));
}
}
}
Generic.xaml
<Style TargetType="{x:Type local:TwClock}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TwClock}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
これはあくまで雛形でしかないので、時刻を表示するための実装を行なっていく。

○依存関係プロパティを追加する。
 Intervalプロパティ...時刻表示を更新する時間間隔(S)。型はintやdecimalではなく、TimeSpanにした。
namespace TawamureDays.Controls {

/// <summary>
/// Tawamure Clock
/// </summary>
public class TwClock : Control {

/// <summary>
/// タイマー間隔を取得|設定します。
/// </summary>
public TimeSpan Interval {
get { return (TimeSpan)GetValue(IntervalProperty); }
set { SetValue(IntervalProperty, value); }
}

/// <summary>タイマー間隔</summary>
public static readonly DependencyProperty IntervalProperty =
DependencyProperty.Register("Interval", typeof(TimeSpan),
typeof(TwClock),
new UIPropertyMetadata(
new TimeSpan(0, 0, 1),
OnIntervalPropertyChanged));

/// <summary>
/// Intervalプロパティ値変更イベントハンドラ
/// </summary>
/// <param name="dpObj">イベント発生元</param>
/// <param name="e">イベント引数</param>
private static void OnIntervalPropertyChanged(
DependencyObject dpObj, DependencyPropertyChangedEventArgs e) {

var clock = dpObj as TwClock;

if (clock.timer_ != null && e.NewValue != null) {
if (clock.timer_.IsEnabled) {
//一旦止めて、設定し、再始動
clock.timer_.Stop();
clock.timer_.IsEnabled = false;
clock.timer_.Interval = (TimeSpan)e.NewValue;
clock.timer_.IsEnabled = true;
clock.timer_.Start();
} else {
clock.timer_.Interval = (TimeSpan)e.NewValue;
}
}

return;
}
}
}

○タイマーを設定する。
 時刻の更新には、タイマーを使用する。タイマーにも色々あるんだけども、今回はDispatcherを使うDispatcherタイマーを使ってみる。
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;

namespace TawamureDays.Controls {

/// <summary>
/// Tawamure Clock
/// </summary>
public class TwClock : Control {

/// <summary>
/// コンストラクタ
/// </summary>
public TwClock() {
timer_ = new DispatcherTimer(DispatcherPriority.Background);
timer_.Tick += new EventHandler(Timer_Tick);
timer_.IsEnabled = false;

//終了処理用(リーク対策)
this.Loaded += new RoutedEventHandler(TwClock_Loaded);
this.Unloaded += new RoutedEventHandler(TwClock_Unloaded);
return;
}

/// <summary>
/// タイマーイベントハンドラ
/// </summary>
/// <param name="sender">イベント発生元</param>
/// <param name="e">イベント引数</param>
private void Timer_Tick(object sender, EventArgs e) {
//DataContextを更新します。
this.DataContext = DateTime.Now;
return;
}

/// <summary>
/// loadedイベントハンドラ
/// </summary>
/// <param name="sender">イベント発生元</param>
/// <param name="e">イベント引数</param>
private void TwClock_Loaded(object sender, RoutedEventArgs e) {
this.DataContext = DateTime.Now;
timer_.IsEnabled = true;
timer_.Interval = this.Interval;
timer_.Start();
return;
}

/// <summary>
/// Unloadedイベントハンドラ
/// </summary>
/// <param name="sender">イベント発生元</param>
/// <param name="e">イベント引数</param>
private void TwClock_Unloaded(object sender, RoutedEventArgs e) {
timer_.Stop();
timer_.IsEnabled = false;
return;
}
}
}
DataContextにDateTimeを設定してあるので、ControlTemplateもそのつもりで記述する。
○テンプレート(XAML)編集
<Style TargetType="{x:Type local:TwClock}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TwClock}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Month, StringFormat={}{0:D2}}"/>
<TextBlock Text="/"/>
<TextBlock Text="{Binding Day, StringFormat={}{0:D2}}"/>
<TextBlock Text=" "/>
<TextBlock Text="{Binding Hour, StringFormat={}{0:D2}}"/>
<TextBlock Text=":"/>
<TextBlock Text="{Binding Minute, StringFormat={}{0:D2}}"/>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
「月/日 時:分」となるようにStackPanelで横並びになるようにしてある。
○試しに貼り付けてみる。
<Window x:Class="TawamureDays.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tw="http://schemas.tawamuredays.jp/wpf/gui"
Title="MainWindow" Height="350" Width="525">
<tw:TawamureContents>
<tw:TwClock Interval="0:0:20"/>
</tw:TawamureContents>
</Window>

実行結果
20120927_1
この実装の場合、日付の書式は固定となっている。なので、秒まで表示しようと思うなら、ControlTemplateを編集する必要がある。あるいは、もう1つControlTemplateを作るのも手かもしれない。x:Keyをつける必要はあるけど。
仕事では、可変にできるようにFormatを指定する為のプロパティを設け、DataContextをDateTimeではなく、Stringにした。上のような細かいレイアウトってのはできなくなるんだけど、仕事ではstringで十分だった。続きを読む
スポンサーサイト
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGrid用のViewModelを作ったよ。(弐)

長くなったので、分けた。の続き。

○DataGridのItemに対するViewModel
 DataGridの各行(DataGridRow)のDataContextとなる。ついでにセル(DataGridCell)のDataContextでもある。 INotifyPropertyChangedインターフェースの実装は必須となる。これがないと、パフォーマンスに影響するという話。
 可能であれば、IDisposableインターフェースも実装しておいた方が、終了処理が楽。

 ◇プロパティ(基本)
  ・ViewIndex
   ビュー上のインデックス。ItemsSourceに設定された時に設定したい感じ。削除されたら、-1になる。
   ただ、フィルタ等と連動させると重くなりそうだったので、取得したい時にリフレッシュさせる感じにした。
  ・IsCurrenRow
   現在行かどうか。つい最近実装。CurrentItemプロパティ系のメソッド内で設定される。
   DataTrigger等で使うことを目論んでいる。
  ・IsDeleted
   削除されているかどうか。無効かどうかとか。マスタテーブルのデータを表示する時に、無効データも表示するケースがあるので、これをもたせている。
 ◇プロパティ(編集)
  ・IsReadOnly
   アイテム(行)そのものが読取専用かどうか。
  ・IsUpdated
   更新されたかどうか。自身のPropertyChangedイベントをフックして、編集可能項目なら、trueに設定する。
  ※IEditableObjectインターフェース(System.ComponentModel)を実装すると、「行単位」の編集を実装できたりするけど、あまり使わなかった。
 ◇プロパティ(選択)
  ・IsSelected
   選択されているかどうか。
  ・CanUserSelect
   ユーザが選択できるかどうか。
  ※これらは、DataGridが持つ選択機能とは連動させていない。

  これらのプロパティを、DataGridのアイテムとなるクラス全てに実装するのか?という話になった時、
  ・サーバ-クライアント通信時にデータ量が増える。
   上記プロパティは、ぶっちゃけて言えば、クライアントにあれば十分。
  ・インターフェースにしたところで、結局個別に実装することには変わりない。
   ※インターフェースを用意しなくて良い、という訳ではない。
  ・WebサービスやWCFサービス等のプロクシ(Proxy)となるクラスでは、インターフェースを実装できない。
   WebサービスはINotifyPropertyChangedも実装できない。WCFサービスは設定次第で可能。
  等々、めんどくさい難しい問題が上がってきた。ココらへんの話もおいおいメモしていく。

仕事では更に拡張された機能を持つViewModelを使用している。データ仮想化等も行なっている。
それでも、列数が増えたりしたら、やはり動きが"もっさり"するので、改良の余地はまだまだあったりする。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGrid用のViewModelを作ったよ。(壱)

仕事では、DataGrid用のViewModelとして、
・DataGridそのものに対するViewModel
・DataGridの各アイテム(行)に対するViewModel
を作った。イベントと連動させるコマンドや、その他のお決まりなプロパティ、メソッドを逐一Window用のVMに実装するのもどうかと思ったし、再利用という点から見れば、あまりよろしくなかったからだ。

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

リソースを共有したくないんだ。

App.xamlやGeneric.xamlで宣言したImage等のインスタンスは、リソースとして扱われ、基本的に使いまわされる(共有される)。要するに、1つのインスタンスでやり繰りされようとする。それでは都合の悪いケースもあったりする。
例えば、ボタンを文字ではなく、画像で表示したい場合。
・App.xaml
<Application x:Class="TawamureDays.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Image x:Key="Search_Jpg" Source="search_btn.png"/>
</Application.Resources>
</Application>

プロジェクト直下に配置した画像(search_btn.png)をボタンに設定する。
<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>
<Button Width="48">
<StaticResource ResourceKey="Search_Jpg"/>
</Button>
<Button Width="48">
<StaticResource ResourceKey="Search_Jpg"/>
</Button>
</StackPanel>
</Grid>
</Window>
デザイナ上で見る分にはOKなんだけど、実行時にはエラーとなる。

指定された要素は、既に別の要素の論理子です。まず接続を切断してください。


コントロールもオブジェクトも、複数のコントロールの子にはなれない感じ。
こういうときは、リソースを共有しないという設定にする。
<Application x:Class="TawamureDays.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Image x:Key="Search_Jpg" x:Shared="False" Source="search_btn.png"/>
</Application.Resources>
</Application>

こうすると、実行できる。
20120922_1

次に、この画像を使ったボタンをスタイル化しようとした。
<Style TargetType="Button" x:Key="SearchBtnStyle">
<Setter Property="Content">
<Setter.Value>
<StaticResource ResourceKey="Search_Jpg"/>
</Setter.Value>
</Setter>
</Style>

1画面内で1つのボタンに適用した時は、問題なく使えるけど、上記のように、複数のボタンのスタイルに適用すると駄目だった。同じエラーを言われる。Styleではなく、ControlTemplateを使って、一から構築すれば可能なのかもしれないけど、仕事ではそこまではできなかった。
後、このx:Sharedって、コードヒントに出てこないんだよなぁ。なんでだろ。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

システムリソースというものもある。

WPFには、システムリソースなるものがある。システム(OS)が持つ設定を利用するためのクラス達の事。

SystemColors...色各種
SystemFonts...フォント各種
SystemParameters...幅|高さやアニメーション
SystemCommands...コマンド各種(ただし、.NET4.5から)

○SystemColors
使い方がやや特殊。
<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>
<Button Background="SystemColors.WindowBrush">駄目っぽい</Button>
<Button Background="{StaticResource SystemColors.WindowBrush}">SystemColors.WindowBrushはキーじゃない。</Button>
<Button Background="{StaticResource SystemColors.WindowBrushKey}">キーだけど駄目っぽい。</Button>
<Button Background="{StaticResource {x:Static SystemColors.WindowBrush}}">実行時に落ちる</Button>
<Button Background="{StaticResource {x:Static SystemColors.WindowBrushKey}}">実行時に落ちる</Button>
<Button Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">これが本命</Button>
</StackPanel>
</Grid>
</Window>
本命というか、それしか動かないというか。
文字から色を連想できないものもあるので、パレットを見るのが便利かな。
WPF Color Palette

○SystemFonts
方法 : SystemFonts を使用する
フォントファミリ情報、フォントサイズなんかも含まれる。
SystemColorsよりは使いやすいかな。

○SystemParameters
方法 : SystemParameters を使用する
SystemFontsと同じ使い方。

.NET4でSystemCommands使えたらなぁ。でも、リンク先をよく見たら、XPで動作しない感じやな…。
こりゃしばらく使えないな。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

例えば、こういう方法もある。

フールプルーフという意味でこういう方法もある。
<Window x:Class="TawamureDays.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tw="http://schemas.tawamuredays.jp/wpf/gui"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:TawamureDays"
Title="MainWindow" Height="350" Width="525">
<tw:TawamureContents>
<StackPanel>
<TextBox DataContext="{Binding NumericBoxVM}"
tw:FrameworkElementBehavior.UpdatePropertySourceWhenEnterPressed="TextBox.Text">
<TextBox.Text>
<Binding Path="InputValue"
Converter="{StaticResource NullableDecimal2StringConverter}"
ValidatesOnExceptions="False">
<Binding.ValidationRules>
<local:DecimalValidationRule IsNullOK="False"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<i:Interaction.Behaviors>
<tw:WatermarkBevavior WartermarkOpacity="0.4"
Watermark="数値を入力してね"/>
</i:Interaction.Behaviors>
</TextBox>
<ComboBox MinWidth="100">
<i:Interaction.Behaviors>
<tw:WatermarkBevavior WartermarkOpacity="0.4"
Watermark="▼ボタンを押して選択してね"/>
</i:Interaction.Behaviors>
</ComboBox>
<PasswordBox MinWidth="100">
<i:Interaction.Behaviors>
<tw:WatermarkBevavior WartermarkOpacity="0.4"
Watermark="パスワードをどうぞ!"/>
</i:Interaction.Behaviors>
</PasswordBox>
<ListBox MinHeight="100">
<i:Interaction.Behaviors>
<tw:WatermarkBevavior WartermarkOpacity="0.4">
<tw:WatermarkBevavior.Watermark>
<TextBlock>
ここには、XXXXのリストが表示されますよ。<LineBreak/>
検索ボタンを押してね。
</TextBlock>
</tw:WatermarkBevavior.Watermark>
</tw:WatermarkBevavior>
</i:Interaction.Behaviors>
</ListBox>
</StackPanel>
<tw:TawamureContents.Footer>
<Button Content="終われ!" Command="{Binding CloseCommand}"/>
</tw:TawamureContents.Footer>
</tw:TawamureContents>
</Window>

いわゆるウォーターマークってやつかな。フォーカスが当たったり、入力があったりすると消える。
20120920_3
フォーカスがあたると、消える。
20120920_4
ちょっと薄い。WartermarkOpacityプロパティで透明度を制御できるようにしている。ユーザに正しいアクションを促す意味では良いかも。
Extended WPF Toolkitには、WatermarkTextBoxがあるけど、あれは、1個のコントロールなので、それにしか使えない。なので、メジャーな入力/選択系コントロールに使えるように、自作した。
コードを全部載せられないので、箇条書きで。
・Blend系のBehaviorクラスを継承して実装。
・表示用のアドーナークラスも実装。
・Loadedイベントでアドーナーを対象コントロールに追加する。
・アタッチされたコントロールによって、フックするイベントを切り替える。
例: TextBox→TextChangedイベント、ComboBox→SelectionChanged
・アドーナーで対象コントロールの上にコントロールをかぶせている感じにする。
あくまで促すだけで、チェックするわけではないから、こっちは気休めなんだけどね。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

数値入力オンリーにしなくたって方法はある。かな。

数値入力オンリーのテキストボックスだって作れるんだぞ。で書いたように、数値入力だけ受け付けるテキストボックスを作るまでの情熱や時間がないときは、ValidationRuleクラスを使う手もある。
ValidationRuleは、入力された値に対して独自のチェックを付けられるクラス。依存関係プロパティの値決定における順序で言えば、一番最後になるかな。数値チェックだけでなく、桁数や範囲も指定でき、且つエラー内容を自分好みのメッセージにもできる。数値型プロパティとTextBox.Textプロパティとのバインドで使ったウィンドウをベースに、ValidationRuleを設定してみる。

○カスタムなマイValidationRuleクラスを作る。
namespace TawamureDays {

/// <summary>
/// 数値入力を検証する為のクラス
/// </summary>
public class DecimalValidationRule : ValidationRule {

/// <summary>
/// 空入力を認めますか?
/// </summary>
public bool IsNullOK { get; set; }

/// <summary>
/// 値の検証チェックを行います。<br/>
/// falseをセットして返すと、バインディングエラーと同じ感じになります。<br/>
/// </summary>
/// <param name="value">チェックするバインディング ターゲットの値。</param>
/// <param name="cultureInfo">この規則で使用するカルチャ。</param>
/// <returns>ValidationResultオブジェクト</returns>
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) {
if (value == null || value.ToString() == string.Empty) {
if (IsNullOK) {
return ValidationResult.ValidResult;
} else {
return new ValidationResult(false, "必ず入力してね");
}
}

var decVal = decimal.Zero;

if (!decimal.TryParse(value.ToString(), out decVal)) {
return new ValidationResult(false, "数値を入力してね!");
}

return ValidationResult.ValidResult;
}
}
}
IsNullOK:空入力を認めるかどうかのプロパティ。
Validateメソッド:引数の値(value)を検証して、結果(ValidationResult)を返す。OKなら一々インスタンスを作らずに、ValidationResult.ValidResultを返すと節約になる。

○実際に使う。
このValidationRuleを使う。ただし、Binding周りの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:tw="http://schemas.tawamuredays.jp/wpf/gui"
xmlns:local="clr-namespace:TawamureDays"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<tw:NullableDecimal2StringConverter x:Key="NullableDecimal2StringConverter"/>
</Window.Resources>
<tw:TawamureContents>
<StackPanel>
<TextBox DataContext="{Binding NumericBoxVM}"
tw:FrameworkElementBehavior.UpdatePropertySourceWhenEnterPressed="TextBox.Text">
<TextBox.Text>
<Binding Path="InputValue"
Converter="{StaticResource NullableDecimal2StringConverter}">
<Binding.ValidationRules>
<local:DecimalValidationRule IsNullOK="False"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
<tw:TawamureContents.Footer>
<Button Content="終われ!" Command="{Binding CloseCommand}"/>
</tw:TawamureContents.Footer>
</tw:TawamureContents>
</Window>
IsNullOK="False"にしているので、空入力は認められずエラーになる。
でも、このままでは、カスタムなエラーの意味があまりない。だって、どこにも表示されないし。

○エラーを表示する
仕事では、エラーをToolTipに載せて表示した。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:tw="http://schemas.tawamuredays.jp/wpf/gui"
xmlns:local="clr-namespace:TawamureDays"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="InputErrorStyle" TargetType="TextBox"
BasedOn="{StaticResource {x:Type TextBox}}">
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Validation.HasError"
Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding PlacementTarget,
RelativeSource={RelativeSource Self}}">
<StackPanel>
<TextBlock Text="{Binding (Validation.Errors)[0].ErrorContent}"/>
</StackPanel>
</ToolTip>
</Setter.Value>
</Setter>
</MultiTrigger>
</Style.Triggers>
</Style>
<tw:NullableDecimal2StringConverter x:Key="NullableDecimal2StringConverter"/>
</Window.Resources>
<tw:TawamureContents>
<StackPanel>
<TextBox DataContext="{Binding NumericBoxVM}"
tw:FrameworkElementBehavior.UpdatePropertySourceWhenEnterPressed="TextBox.Text"
Style="{StaticResource InputErrorStyle}">
<TextBox.Text>
<Binding Path="InputValue"
Converter="{StaticResource NullableDecimal2StringConverter}">
<Binding.ValidationRules>
<local:DecimalValidationRule IsNullOK="False"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
<tw:TawamureContents.Footer>
<Button Content="終われ!" Command="{Binding CloseCommand}"/>
</tw:TawamureContents.Footer>
</tw:TawamureContents>
</Window>

数値以外を入力してみると、
20120920_1

何かを入力した後で空にしてみると…
20120920_2
メリット:
・エラーを表示するスペースを考慮しなくていい(レイアウト崩れの心配が不要となる)。
デメリット:
・カーソルを持って行かないと内容がわからない。ユーザが一々持って行ってくれるかどうか。

ErrorTemplateもあるんだけど、動作したら載せようっと。続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

ObservableCollection<T>クラスをちょっと拡張する。

 DataGridのItemsSourceプロパティとよくバインディングされるのが、ObservableCollection<T>クラスだ。このクラスは、アイテムの増減をイベントで通知してくれる機能(INotifyCollectionChangedインターフェイスを実装している)があるので、追加されたアイテムに対する処理、削除されたアイテムに対する処理をイベントハンドラ(メソッド)内でできる。
using System.Collections.ObjectModel;
using System.Collections.Specialized;

/// <summary></summary>
private ObservableCollection<int> list;

//イベントへハンドラメソッドを登録します。
list.CollectionChanged +=
new NotifyCollectionChangedEventHandler(list_CollectionChanged);

/// <summary>
/// list コレクション アイテム数変更イベントハンドラ<br/>
/// アイテム数の増減に伴ってイベントが発生します。<br/>
/// </summary>
/// <param name="sender">イベント発生元</param>
/// <param name="e">イベント引数</param>
private void list_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
//ここに処理を実装します。
if (e.OldItems != null) {
}

if (e.NewItems != null) {
}
}

イベント引数(NotifyCollectionChangedEventArgs)には、OldItemsとNewItemsがある。
OldItems:削除されたアイテムリスト
NewItems:追加されたアイテムリスト
となる。便利なクラスなんだけども、ちょっと困るのがClearメソッド。イベントは発生するけど、クリアされた時のアイテムがOldItemsに設定されていない。nullで飛んでくる。Clearされる時のアイテム達に対して、終了処理(Disposeやイベントハンドラの削除)を行いたい時にできない。これはちょっとまずい。処理できないと、メモリリークの原因にもなる。
そこで、その対策として、ObservableCollection<>クラスを拡張する。これって、オリジナルじゃなくて、どこかの記事を見て実装したんだけど、リンクが見つからない…Orz。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace TawamureDays.Container {

/// <summary>
/// ObservableCollection拡張クラス
/// </summary>
/// <remarks>
/// System.Collections.ObjectModel.ObservableCollection<T>クラスをちょっとだけ
/// 拡張するクラスです。<br/>
/// 元記事のリンクをロスト中…。コメントに残すべきだな。Orz<br/>
/// </remarks>
/// <typeparam name="T">コレクション要素の型宣言</typeparam>
public class ObservableCollectionEx<T> : ObservableCollection<T> {

#region コンストラクタ

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

/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="newlist">コピー元のリストオブジェクト</param>
public ObservableCollectionEx(List<T> newlist) : base(newlist) {
}

/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="newCollection">コピー元のコレクション</param>
public ObservableCollectionEx(IEnumerable<T> newCollection) : base(newCollection) {
}

#endregion

#region ObservableCollection<T> メンバ

/// <summary>
/// コレクションから全ての項目を削除します。
/// </summary>
protected override void ClearItems() {
//クリアされるリストを確保しましょう。
var clearedList = new List<T>(this);
try {
//リストをクリアしましょう。
base.ClearItems();

//事後にしよう。
try {
//イベント励起
OnCleaning(new CleaningItemsEventArgs<T>(clearedList));
} catch (Exception) {
;//無視か、警告レベルのログを残す
}
} finally {
//後始末
clearedList.Clear();
clearedList = null;
}
return;
}

#endregion

#region イベント

/// <summary>アイテムクリアイベント</summary>
public event EventHandler<CleaningItemsEventArgs<T>> CleaningItems;

#endregion

#region 内部メソッド

/// <summary>
/// アイテムクリア前にイベントを発生させます。<br/>
/// </summary>
/// <param name="e">イベント引数</param>
protected virtual void OnCleaning(CleaningItemsEventArgs<T> e) {
if (CleaningItems != null) {
CleaningItems(this, e);
}
}

#endregion
}

/// <summary>
/// アイテムクリアイベント用引数クラス
/// </summary>
/// <remarks>
/// ObservableCollectionEx<T>クラスのClearItemsメソッド実行時に発生する
/// CleaningItemsイベント用の引数となるクラスです。<br/>
/// </remarks>
public sealed class CleaningItemsEventArgs<T> : System.EventArgs {

#region コンストラクタ

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

}

/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="newClearedList">クリア対象だったリスト</param>
public CleaningItemsEventArgs(IList<T> newClearedList) : this() {
this.ClearedList = new ReadOnlyCollection<T>(newClearedList);
}

#endregion

#region プロパティ

/// <summary>
/// クリアされたリストを取得します。
/// </summary>
public ReadOnlyCollection<T> ClearedList {
get; internal set;
}

#endregion
}
}

・内部メソッド(ClearItems)をオーバーライドして、クリアされるリストを確保し、追加実装したイベント(CleaningItems)を発生させる。
・イベント引数に、そのクリアされたリストを設定する。
・イベント引数のリスト(ClearedList)は、ReadOnlyCollection<T>、読み取り専用としている(追加,削除不可)。
・イベント通知が終わったら、リストを削除する。
この追加したイベントに登録したハンドラ(メソッド)は、画面終了時に確実に削除しておく必要がある。
でないと、メモリリークの元となる。CollectionChangedとCleaningItemsという2つのイベントに対して処理を行う事になるが、もうこれは仕方ないかなと。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

リソースディクショナリを分割する際の注意点

WPFのスタイルやカスタムコントロールのテンプレート等は、リソースディクショナリファイル(Generic.xaml)に記述するんだけど、コントロールを作る度にファイルがどんどん大きくなっていく。探すのが大変。ワケワカメになってくる。
そうなる前に、リソースディクショナリの分割を検討する。仕事では、カスタムコントロール毎にxamlを作り、標準コントロールに対するスタイル設定等は、Generic.xamlに記述するようにしている。
分割する際は、MergedDictionariesを使う。

<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- External Resource Dictionary -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/XXXXX;component/Themes/YYYYYY.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
XXXXXは、アセンブリ名、YYYYYYはリソースディクショナリ名となる。
WPF4にも起こりうる問題かどうかは定かではないのだけれど、最適化処理によるバグが存在するらしい。
Adding a Merged Dictionary to a Merged Dictionary
MSへのリンクは既にない感じなので、どうなっているかが確認できない…。
解決策(回避策)も載っているので、まあ、その通りにしておこうとなった次第。
新たにカスタムコントロールライブラリ作るときに忘れないようにするためのメモ。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

数値型プロパティとTextBox.Textプロパティとのバインド

数値のみ入力を許可する用なTextBoxの場合、TextBoxのTextプロパティと数値型プロパティのバインドを行う。そうしないと、BindingのStringFormatが有効にならない。でも、そうした場合、入力が空になった時に問題が発生した。
例えば、TextBoxVMを改造して数値ボックス用のViewModelを作ったとする。
using System;

namespace TawamureDays.ViewModels {

/// <summary>
/// 数値用TextBox用ViewModelクラス
/// </summary>
public class NumericBoxVM : UIElementVM {

#region コンストラクタ

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

/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="inputValue">初期値</param>
public NumericBoxVM(decimal? inputValue) : this() {
inputValue_ = inputValue;
}

#endregion

#region プロパティ

/// <summary>入力値</summary>
private decimal? inputValue_;

/// <summary>
/// 入力値を取得|設定します。
/// </summary>
public decimal? InputValue {
get {return inputValue_;}
set {
if (inputValue_ != value) {
inputValue_ = value;
this.NotifyPropertyChanged("InputValue");
}
}
}

#endregion
}
}
InputValueのプロパティ型がdecimal?なのは、空文字列をnulとして受け取りたい為。
これを使ってみる。
○ViewModel側
using System.Windows;
using System.Windows.Input;

namespace TawamureDays {

/// <summary>
/// ViewModelクラス
/// </summary>
public class TawamureVM : BaseWindowVM {

/// <summary>
/// 数値入力用VMを取得します。
/// </summary>
public NumericBoxVM NumericBoxVM {
get; private set;
}

/// <summary>
/// コンストラクタ
/// </summary>
public TawamureVM() {
closeCommand_ = new RelayCommand(ExecuteCloseCommand);
NumericBoxVM = new NumericBoxVM();
return;
}
}
}

○View側
<Window x:Class="TawamureDays.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tw="http://schemas.tawamuredays.jp/wpf/gui"
Title="MainWindow" Height="350" Width="525">
<tw:TawamureContents>
<StackPanel>
<TextBox DataContext="{Binding NumericBoxVM}"
Text="{Binding InputValue}"
/>
</StackPanel>
<tw:TawamureContents.Footer>
<Button Content="終われ!" Command="{Binding CloseCommand}"/>
</tw:TawamureContents.Footer>
</tw:TawamureContents>
</Window>
そうした場合、起動は特に問題ない。問題は入力の時に出てくる。
数値を入力する分には問題ない。問題は、数値を入力した後で、文字列をクリアした時に発生する。
数値を入力する。
20120915_1
その後、空にすると…
20120915_2
バインディングエラーを示す赤色になる。裏で発生しているエラーが下のようになる。

System.Windows.Data Error: 7 : ConvertBack cannot convert value '' (type 'String'). BindingExpression:Path=InputValue; DataItem='NumericBoxVM' (HashCode=13635742); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String') FormatException:'System.FormatException: 入力文字列の形式が正しくありません。
場所 System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
場所 System.Number.ParseDecimal(String value, NumberStyles options, NumberFormatInfo numfmt)
場所 System.Convert.ToDecimal(String value, IFormatProvider provider)
場所 System.String.System.IConvertible.ToDecimal(IFormatProvider provider)
場所 System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
場所 MS.Internal.Data.SystemConvertConverter.ConvertBack(Object o, Type type, Object parameter, CultureInfo culture)
場所 System.Windows.Data.BindingExpression.ConvertBackHelper(IValueConverter converter, Object value, Type sourceType, Object parameter, CultureInfo culture)'

どうも、空文字列をdecimal?のnullに変換できないらしい。まあ、decimal?系のnullは、クラス等のnullとは違うようなので、当たり前といえば当たり前の話だった。
じゃあ、どうやれば、変換できるのか?といえば、これはもうConverterを使うしかない。

using System;

namespace TawamureDays.Converters {

/// <summary>
/// 数値型(decimal?)を文字列に変えるコンバータクラス
/// </summary>
[System.Windows.Data.ValueConversion(typeof(decimal?), typeof(string))]
public sealed class NullableDecimal2StringConverter : System.Windows.Data.IValueConverter {

#region IValueConverter メンバー

/// <summary>
/// 値を変換します。
/// </summary>
/// <param name="value">バインディング ソースによって生成された値。</param>
/// <param name="targetType">バインディング ターゲット プロパティの型。</param>
/// <param name="parameter">使用するコンバーター パラメーター</param>
/// <param name="culture">コンバーターで使用するカルチャ。</param>
/// <returns>変換された値</returns>
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
if (value == null) {
return string.Empty;
}

decimal decVal;

if (!decimal.TryParse(value.ToString(), out decVal)) {
return value;
}

var format = parameter == null ? string.Empty : parameter.ToString();
return decVal.ToString(format);
}

/// <summary>
/// 値を変換します。
/// </summary>
/// <param name="value">バインディング ターゲットによって生成される値。</param>
/// <param name="targetType">変換後の型。</param>
/// <param name="parameter">使用するコンバーター パラメーター</param>
/// <param name="culture">コンバーターで使用するカルチャ。</param>
/// <returns>変換された値</returns>
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
if (value == null) {
return (decimal?)null;
}

decimal decVal = decimal.Zero;

if (decimal.TryParse(value.ToString(), out decVal)) {
return decVal;
}

return (decimal?)null;
}

#endregion
}
}
ソース→ターゲットは、decimal?型をstring型に、ターゲット→ソースは、string型をdecimalにパースしている。decimal型に変換できない場合にdecimal?のnullを返している。
これで、空文字列でもバインドエラーが言われなくなった。ただ、このコンバータを噛ましてしまうと、StringFormatが効かなくなる。まあ、これはどうしようもないので、ConverterParameterに書式を設定するとみなして、変換するしかない。何もかもが上手くいくものではないか。
続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

数値入力オンリーのテキストボックスだって作れるんだぞ。

WPFの強みは、拡張性の高さだと思う。後はパフォーマンスだけだ!と言いたい。
仕事では、大抵必要になる「数値のみ入力できるTextBox」だって、その気になれば作れるのだ。というか、実際に作って仕事で使っていたりする。まあ、作らなくても、Extended WPF ToolkitにあるMaskedTextBoxでも良いんだけどね。まあ、その時のメモって事で。
数値入力コントロールとなるのに必要な要件としては

・文字については、半角数値のみ入力を受け付ける。
・負の値を示す「-」は、必ず先頭に持ってくる。
・小数と整数の区切りとなるピリオド「.」は1つだけ受け付ける。
・←や→、コピーや貼り付け等のコマンドは受け付ける必要がある。
・ただし、貼り付けられた(Ctrl+V)ら、その内容をチェックする必要がある。

かなと。仕事では、TextBoxを継承したクラスではなく、添付プロパティとして実装した。
こうすることで、そのプロパティとDataGridのDataGridTextBoxColumnを拡張して、数値入力用の列クラスを実装できる。
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace TawamureDays.Extensions {

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

/// <summary>数値のみの入力とするかどうか</summary>
public static readonly DependencyProperty AllowInputOnlyNumericProperty =
DependencyProperty.RegisterAttached("AllowInputOnlyNumeric", typeof(bool),
typeof(TextBoxBehavior),
new UIPropertyMetadata(false,
OnAllowInputOnlyNumericPropertyChanged));

/// <summary>
/// AllowInputOnlyNumericプロパティ値変更イベントハンドラ
/// </summary>
/// <param name="dpObj">イベント発生元</param>
/// <param name="e">イベント引数</param>
private static void OnAllowInputOnlyNumericPropertyChanged(
DependencyObject dpObj, DependencyPropertyChangedEventArgs e) {

var textBox = dpObj as TextBox;

if (textBox == null) {
return;
}

textBox.PreviewKeyDown -= new KeyEventHandler(TextBox_PreviewKeyDown);

if ((bool)e.NewValue) {
textBox.PreviewKeyDown += new KeyEventHandler(TextBox_PreviewKeyDown);
}
}

/// <summary>
/// キーダウン直前イベントハンドラ
/// </summary>
/// <param name="sender">イベント発生元</param>
/// <param name="e">イベント引数</param>
private static void TextBox_PreviewKeyDown(object sender, KeyEventArgs e) {
}
}
}
TextBox_PreviewKeyDownメソッドで、不要な入力をキャンセルする。キャンセルする方法は、e.Cancel=trueとするだけ。
入力されたキーの判定は、e.Keyプロパティで可能となる。テンキーの数値とキーボードの上に横に並ぶキーとは値が違うので要注意である。
後は、貼り付けや切り取り等のコマンドだ。これらは、キー入力の判定ではなく、ビルトインコマンド(ApplicationCommandやEditingCommands)として存在するものなので、それらから、CommandBindingを作成し、TextBoxのCommandBindingsプロパティに登録して、結果となる文字列が数値として適正かどうかを判定する。
これらに登録したCommandBindingは後(画面終了時)に必ず削除する必要がある。前にも書いたんだけど、CommandBindingって、そのインスタンスじゃないと削除できない。なので、削除するときまで確保しておく必要がある(Clearメソッドでも良いんだけどね)。
実際に、入力を数値だけに絞れるようになったら、その次は、入力できる範囲等を決める必要が出た。

整数部は何桁まで入力できるか?
小数部は何桁まで入力できるか?
マイナス値を許容しているか?
最大値は?最小値は?

これらを設定するのも、添付プロパティで可能ではあるけど、たくさんあってややこしい。
こういうときは、Behaviorクラスを使うのもいいかと思う。続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

.NET Framework4.5とVS2012がリリース

マイクロソフト、「Visual Studio 2012」を発表――「Windows 8」に対応
同時に.NET Framework4.5もリリースされたようだ。このバージョンは、.NET4のアセンブリを上書きしてしまうので、仕事では気軽にインストールできない。
でも、家のPCならできるかなぁ。
VS Express 2012もダウンロードできるようなので、試してみようかな。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: C# 5.0 | コメント: 0 | トラックバック: 0

WindowVMの次は、TextBox用VMだろうと。

Window用ViewModelクラスの基礎はほぼ作れたので、次は、各コントロール用VMを作っていく事にした。
一番連携したいプロパティ。
・Textプロパティ
・IsEnabledプロパティ
・IsReadOnlyプロパティ
・IsFocucedプロパティ
この4つは是非ほしいところ。でも、TextプロパティとIsFocucedプロパティ以外は、TextBoxではなく、UIElementクラスのプロパティなんだよな。IsFocucedプロパティとの連動は、添付プロパティでなんとかなるだろ。というわけで、TextBoxよりまず先にUIElement用のVMを作ることにした。
using System;

namespace TawamureDays.ViewModels {

/// <summary>
/// UIElement用ViewModelクラス
/// </summary>
public class UIElementVM : BaseViewModel {

#region プロパティ

/// <summary>有効かどうか</summary>
private bool isEnabled_;

/// <summary>
/// 有効かどうかを取得|設定します。
/// </summary>
public bool IsEnabled {
get {return isEnabled_;}
set {
if (isEnabled_ != value) {
isEnabled_ = value;
this.NotifyPropertyChanged("IsEnabled");
}
}
}

/// <summary>読み取り専用かどうか</summary>
private bool isReadOnly_;

/// <summary>
/// 読み取り専用かどうかを取得|設定します。
/// </summary>
public bool IsReadOnly {
get {return isReadOnly_;}
set {
if (isReadOnly_ != value) {
isReadOnly_ = value;
this.NotifyPropertyChanged("IsReadOnly");
}
}
}

/// <summary>フォーカスを当てるかどうか</summary>
private bool isFocused_;

/// <summary>
/// フォーカスを当てるかどうかを取得|設定します。
/// </summary>
public bool IsFocused {
get {return isFocused_;}
set {
if (isFocused_ != value) {
isFocused_ = value;
this.NotifyPropertyChanged("IsFocused");
}
}
}

#endregion
}
}
これを更に継承して、TextBox用のViewModelを作る。
using System;

namespace TawamureDays.ViewModels {

/// <summary>
/// TextBox用ViewModelクラス
/// </summary>
public class TextBoxVM : UIElementVM {

#region コンストラクタ

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

/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="inputValue">初期値</param>
public TextBoxVM(string inputValue) : this() {
inputValue_ = inputValue;
}

#endregion

#region プロパティ

/// <summary>入力値</summary>
private string inputValue_;

/// <summary>
/// 入力値を取得|設定します。
/// </summary>
public string InputValue {
get {return inputValue_;}
set {
if (inputValue_ != value) {
inputValue_ = value;
this.NotifyPropertyChanged("InputValue");
}
}
}

#endregion
}
}


使い方としては、とりあえずこんなもの。
○ViewModel側
using System.Windows;
using System.Windows.Input;

namespace TawamureDays {

/// <summary>
/// ViewModelクラス
/// </summary>
public class TawamureVM : BaseWindowVM {

#region コンストラクタ

/// <summary>
/// コンストラクタ
/// </summary>
public TawamureVM() {
closeCommand_ = new RelayCommand(ExecuteCloseCommand);
TextBoxVM = new TextBoxVM();
return;
}

#endregion

#region プロパティ

/// <summary>終了用コマンド</summary>
private RelayCommand closeCommand_;

/// <summary>
/// 終了用コマンドを取得します。
/// </summary>
public ICommand CloseCommand {
get {return closeCommand_;}
}

/// <summary>
/// テキストボックス用VMを取得します。
/// </summary>
public TextBoxVM TextBoxVM {
get; private set;
}

#endregion
}
}

○View側(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:tw="http://schemas.tawamuredays.jp/wpf/gui"
Title="MainWindow" Height="350" Width="525">
<tw:TawamureContents>
<StackPanel>
<TextBox DataContext="{Binding TextBoxVM}"
Text="{Binding InputValue}"
IsEnabled="{Binding IsEnabled}"
IsReadOnly="{Binding IsReadOnly}"
tw:FrameworkElementBehavior.IsFocused="{Binding IsFocuced}"
/>
</StackPanel>
<tw:TawamureContents.Footer>
<Button Content="終われ!" Command="{Binding CloseCommand}"/>
</tw:TawamureContents.Footer>
</tw:TawamureContents>
</Window>

・TextBoxのTextプロパティはデフォルトでMode=TwoWay。明示的な設定は不要
・IsEnabledやIsReadOnlyはOneWayでも良いかもしれない。
まあ、これでViewModel側からの連携が可能となる。続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

メモリリークやパフォーマンスに関する事をメモっとく。

WPFでは、とかくパフォーマンスを気にする必要がある。当然、メモリリークもだ。WPFだから大丈夫なんてのは、通用しない。WPFだからこそ起こりうる事と思いながら作っている。Windows.Formsとは違い、コントロールがIDisposaleを実装していないから終了処理をしなくて良いわけじゃないし。
深刻なメモリリークに悩まされるWPF(2009年10月20日)
A hotfix is available that resolves some memory leak issues for WPF in the .NET Framework 4
Avoiding a WPF memory leak with DataBinding (Black Magic)(2008年5月7日)
視覚的なエフェクトに対する汎用性は、Windows.Formsの比ではないし。
パフォーマンスを調査する上で使えそうな(使った)ツールや参照した記事をメモしておく。

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

??演算子ってのがあったんだ。

C#に数ある演算子で、??演算子ってのがあるのを知った。?:と似ている感じがする。
?:演算子ではこんな感じ。
return x == null ? y : x;

↑を??演算子で表現するとこうなる。
return x ?? y


ただ、Nullable<>系(int?とかdecimal?とか)だと、GetValueOrDefault()があったりするんで、使い道としては、

○string:
string x;
...
//nullなら、string.Emptyを返す
return x ?? string.Empty;


○配列:
string[] array;
...
return array ?? new string[0];

くらいかなぁ。
でも、知らない人がみたら、それこそ「??」な気分になりそうなので、使うなら説明コメント付きになるかな。
「知らない方が駄目」みたいななものでもないし。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: C# | コメント: 0 | トラックバック: 0

ViewModel側から画面終了を通知(要求)してみた。

画面(View)側から終了する事は、簡単にできる。×ボタンを押すだけだし。
その終了をVM側に通知するのは、OnClosingなんかをバインディングさせれば、可能となる。
では、VM側から終了を通知したい時はどうしよう?
組み込み(ビルトインコマンド)を使おうか?
ApplicationCommands.Close プロパティ
使い方
[C#][WPF]コマンドですよ その8 「用意されてるコマンド」
ん?でもこれ、Closeを呼び出すのにメソッド登録してるなぁ…。
MVVMにしようと思ったら、どうするんだろ?
どうやってDialogResultプロパティに結果を設定するんだろ?と。
・結果(true or false)を決めるのは、ViewModel側
・画面を実際に閉じるのは、View側
結果として、仕事では、カーソス変更同様、コマンドで実装することにした。
なお、DialogResultは、モーダル起動でのみ設定可能。モーダレス(モードレス)で設定すると、例外が発生する。続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

マウスカーソルの形状を変える。

検索してる最中や保存している最中等々、「処理中ですよ」という状態を示す為に、カーソル形状を変えることがある。
よくある形状は砂時計かな。
普通に変更するのなら、
Mouse.Override = Cousors.Wait;//砂時計に

とする。戻すときは、
Mouse.Override = null;//元に戻す

となる。ところで、MVVMパターンでこのカーソルをどう変えよう?と悩んだ事がある。

マウスカーソルって「View」だよな?じゃあ、Viewで変えるべきだよな?

という変なこだわりの元、ViewModel側で上の処理を行いたくなかったので、Commandとして実装した。
簡単に書くと以下のとおり。

1.View側に依存関係プロパティを用意する。
2.View側でコマンドのインスタンスを用意する。
3.データバインディングを設定する。
4.ViewModel側でプロパティを用意する。
5.DataContextが設定されたら、2で作ったインスタンスを1のプロパティに設定する。続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGridのパフォーマンスが気になった時は

DataGridのパフォーマンスが気になった時は、UI仮想化とデータ仮想化について調べて見ること。

パフォーマンスの最適化 : コントロール
WPF4では、データ仮想化が施されたコントロールはないと断言されている。

WPFでデータ仮想化をする方法。
IEnumerableではなく、IListをItemsSourceとバインドしよう!という事かな?

WPF: Data Virtualization
英語だけど、ソースコードがある。

UI Virtualization vs. Data Virtualization (Part 1)
UI Virtualization vs. Data Virtualization (Part 2)
仕事では、この記事に書かれている内容を元にDataGridにデータ仮想化を施してある。
非常にありがたかった記事(-人-)。ただし、すべて英語。
この記事を読んだ時、「グルーピングもばっちりだYO!」な実装だと解釈してしまったけど、実装してみると、実際は「UI仮想化が有効でナンボ」なようなので、グルーピングにはあまり効果がなかった。

このデータ仮想化は、速度というより、メモリリソースの専有を抑制してパフォーマンスに貢献しようという感じなので、劇的な速度向上にはならないと思う。そこを履き違えなければ、試す価値はあるかも。
TableViewが正式リリースしてくれないものだろうか…。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGridのアレはやばいメモ

VirtualizingStackPanel.VirtualizingMode="Recycling"
Ui仮想化のモードをリサイクル方式にするという設定。
何を「リサイクル」するのかと言えば、日本語で「アイテムコンテナ」、英語では「ItemContainers」。DataGridRowクラスの1階層下のオブジェクト?かなぁ。
検証してた記事を発見。
【WPF】VirtualizingStackPanel Recyclingはコンテナがリサイクルされる。
おお、なるほど。

でもねぇ。この再利用というのが、仕事ではクセモノでした。行番号の表示とかをした時、行ヘッダに表示しているんだけど、何も設定しなければ、番号の桁数によって幅が自動的に幅が変わる動作になる。どうもこれが悪さをしているようで、特に変な実装をしたわけでもないのに、レイアウトが崩れまくる。壊れたのか?と思うくらいに。そして、再検索か、ソートを実行しない限り治らない。スクロールしても一緒。どうにも上手く直らなかったので、デフォルトで"Standard"で実装するようにしている。

○ScrollViewer.CanContentScroll="True"
WPF メモ
論理的(True)と物理的(False)の違いらしい。
Trueにした時、なぜか一部の列ソートが異常に重くなるという現象があった。原因不明…。

○EnableColumnVirtualization="True"
列方向への仮想化を有効にするかどうか。
ある程度の列数以上になると重くなるという性質が見られるので、是非Trueにしたいところなんだけど、不意に落ちる。
なぜか落ちる。そして、スタックトレースを見てもわからないという詰みな状態。
CellTemplateや、ColumnHeaderTemplateにちょい複雑なテンプレートを仕掛けたりすると、より顕著になる。

○グルーピング全般
DataGridにはグルーピングという機能があり、そのグループ単位で小計を表示したりできる。当然、それ用のテンプレートを実装する必要があるけど。
ただし、グルーピングするだけで、UI仮想化全部無効という超イヤな仕様がWPF4ではあるので、大量データを検索する時には全く使えない。
自分でDataGridRowを改造したほうがマシ。
なお、WPF4.5では、幾分の改修が期待できる。期待するしかない。というか、WPF4では諦めろって事か?

○IsReadOnly="True"
すべての列に一々設定するのがめんど臭いからと、DataGridのIsReadOnlyをTrueにしてみた事がある。
たしかに、すべての列で、IsReadOnly=Trueな状態になったけど、セル(内の文字列)のコピーまで封印されるとは思わなかった。
なんにもできない。ある意味徹底したReadOnlyだった。続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

マスターコンテンツとViewModelクラスの連携

マスターコンテンツを実装したので、前に作ったWindow用VMと連携するために、マスターコンテンツ(TawamureContents)側にも働いてもらう事にした。
対Window用ViewModelをとりあえず作るか。
対Window用ViewModelをとりあえず作るか。(続き)
MVVMで、メッセージボックスを表示する(壱)。
MVVMで、メッセージボックスを表示する(弐)
MVVMで、メッセージボックスを表示する(参)
で実装したBaseWindowVMクラスのメッセージコマンド系プロパティとバインディングさせる。
まずは起動、終了系のコマンドと連携させるために、マスターコンテンツのコンストラクタに実装を加える。
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
...
/// <summary>
/// コンストラクタ
/// </summary>
public TawamureContents() {
BindingOperations.SetBinding(
this,
FrameworkElementBehavior.OnLoadedCommandProperty,
new Binding("LoadedCommand"){Mode = BindingMode.OneWay});
BindingOperations.SetBinding(
this,
FrameworkElementBehavior.UnloadedCommandProperty,
new Binding("UnloadedCommand"){Mode = BindingMode.OneWay});
BindingOperations.SetBinding(
this,
WindowBehavior.ClosingCommandProperty,
new Binding("ClosingCommand"){Mode = BindingMode.OneWay});
return;
}
これは、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:tw="http://schemas.tawamuredays.jp/wpf/gui"
Title="MainWindow" Height="350" Width="525"
tw:FrameworkElementBehavior.OnLoadedCommand="{Binding LoadedCommand}"
tw:FrameworkElementBehavior.UnloadedCommand="{Binding UnloadedCommand}"
tw:WindowBehavior.ClosingCommand="{Binding ClosingCommand}
">
<tw:TawamureContents>

</tw:TawamureContents>
</Window>
XAMLに書くもが良いのかもしれないけど、そうすると、すべての画面において記述が必要となる。BaseWindowVMクラスを基底にすれば、それだけでプロパティ名は固定されるので、一々書くのは手間となる。なので、コードによるバインディングを仕事では行なっている。実装するのは、共通系の開発者だけになり、各画面の開発担当者が個別に実装する必要がない。それだけで、多少効率はあがる(説明する手間, 理解する手間、実際に実装する手間が減る)。
更にメッセージボックス系の添付プロパティをつける。これも同様にコード上でバインディングさせる。ただし、添付プロパティの対象をWindowクラスにしているので、ワンクッション置くことになる。
/// <summary>
/// コンストラクタ
/// </summary>
public TawamureContents() {
(中略)
this.Loaded += new RoutedEventHandler(TawamureContents_Loaded);
return;
}

/// <summary>
/// Loadedイベントハンドラ
/// </summary>
/// <param name="sender">イベント発生元</param>
/// <param name="e">イベント引数</param>
private void TawamureContents_Loaded(object sender, RoutedEventArgs e) {
var window = Window.GetWindow(this);

if (window != null) {
WindowBehavior.SetUseMessageCommand(window, true);
}
return;
}

次に、SetUseMessageCommandメソッドの中身をちょっと改修する。
このメソッドは、DataContextChangedイベントが発生したら、処理をするという実装になっているけど、
既にDataContextが設定されていた時の事が考慮に入っていなかった。
プロパティ値変更イベント用メソッドを以下のようにした。
/// <summary>
/// UseMessageCommandプロパティ値変更イベントハンドラ
/// </summary>
/// <param name="dpObj">変更発生元</param>
/// <param name="e">イベント引数</param>
private static void OnUseMessageCommandPropertyChanged(
DependencyObject dpObj, DependencyPropertyChangedEventArgs e) {

var window = dpObj as Window;

if (window != null && (bool)e.NewValue) {
window.DataContextChanged +=
new DependencyPropertyChangedEventHandler(Window_DataContextChanged);
window.Closed += new System.EventHandler(Window_Closed);
}


//ここを追加
if (window.DataContext != null) {
Window_DataContextChanged(window,
new DependencyPropertyChangedEventArgs(
Window.DataContextProperty,
null, window.DataContext));
}

return;
}
これで設定済でも動くようになる。
ViewModelクラス実装時に、BaseWindowVMを継承させ、DataContextに設定すれば良いだけとなる。
○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:tw="http://schemas.tawamuredays.jp/wpf/gui"
Title="MainWindow" Height="350" Width="525"">
<tw:TawamureContents>

</tw:TawamureContents>
</Window>

○コードビハインド
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window {

public MainWindow() {
InitializeComponent();
this.DataContext = new TawamureVM();
}
}

○DataContext(ViewModel)側の実装
/// <summary>
/// ViewModelクラス
/// </summary>
public class TawamureVM : BaseWindowVM {

/// <summary>
/// Loaded時に呼び出されるコマンドの実行メソッド。<br/>
/// 初期化処理等、Loaded時に必要な実装をここで行います。<br/>
/// </summary>
/// <param name="parameter">パラメータ</param>
protected override void OnLoaded(object parameter) {
base.OnLoaded(parameter);

this.ShowInfoMessage("INFO MESSAGE!");
this.ShowWarnMessage("WARNING MESSAGE!");
this.ShowErrorMessage("ERROR MESSAGE!");

return;
}
}

ここまでで、ViewModel側で、
○Load時の実装、Closing時の実装を行う。
○メッセージボックスを使う。
が可能になった。次は。
○VM側から画面終了を要求する
○マウスカーソルの形を変える要求をVM側から行う
を実装しようかな。続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGridTemplateColumnに関するメモ。

DataGridTemplateColumnの痒いところ。で書いたDataGridTemplateColumnに関するメモ。
・DataGridTemplateColumn列でソートする
 →SortMemberPathプロパティを使う。
・DataGridTemplateColumn列のセルから文字列をコピーする。
 →ClipBoradContentBindingプロパティを使う。
 ※コピーにも貼り付けにも使える(貼り付けは要実装)。しかし、貼り付けは性能がやや悪い。もっさりする。

・通常と編集モード
 通常モードのTemplateは、CellTemplate
 BindingはOneWayでOK
 編集モードのTemplateは、CellEditingTemplate
 BindingはTwoWay推奨
 いずれもDataTemplateで、リソースで定義して、使いまわすことも可能。
 Bindingのソースは、ItemsSourceのアイテムになっているインスタンス。
 使いまわすとは言ったものの、バインディングで指定するプロパティ名はベタ打ちの為、複数列での使い回しには、非常に頭が痛いところ。手はあるにはあるけど、まだ試せてはいない。

なお、DataGridTemplateColumnに限った話ではないけど、BindingでRelativeSource Ancestor系は効果がない。
RelativeSourceは、基本的にVisualTreeを遡る物なので、VisualTree上に存在しないDataGridColumnには適用できない。
適用できないが、独自のMarkupExtensionを実装することで、限定的ながら使うことが可能とわかり、仕事ではそれを使っている。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

クラスライブラリに追加したリソースファイルが動かない

備忘録的なメモ。
クラスライブラリ(*.dll)に追加したリソースファイル(Generic.xaml)を外から持ってくると、動いていないっぽい時がある。テンプレートが適用されないとか。
なんでだろー?と思ってたんだけど、さっき見つけた。
AssemblyInfo.csの中に以下の記述がないと駄目っぽい。

using System.Windows;
...
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //テーマ固有のリソース ディクショナリが置かれている場所
//(リソースがページ、
//またはアプリケーション リソース ディクショナリに見つからない場合に使用されます)
ResourceDictionaryLocation.SourceAssembly //汎用リソース ディクショナリが置かれている場所
//(リソースがページ、
//アプリケーション、またはいずれのテーマ固有のリソース ディクショナリにも見つからない場合に使用されます)
)]

これがあるとないとでは、プロジェクトへ追加する際に選択できる項目数も違う。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

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

マスターページならぬマスターウィンドウが欲しい。
マスターページならぬマスターウィンドウが欲しい。(弐)
の続き。今回は長い。まずはカスタムコントロールを作る。

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

namespace TawamureDays {

/// <summary>
/// マスターページならぬ、マスターコンテンツ用コントロール。
/// </summary>
public class TawamureContents : Control {

#region コンストラクタ

/// <summary>
/// staticコンストラクタ
/// </summary>
static TawamureContents() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(TawamureContents),
new FrameworkPropertyMetadata(typeof(TawamureContents)));
}

#endregion
}
}
今回作りたいのは、Contentを1つ持つマスターウィンドウならぬ、マスターコンテンツなので、ContentControlに切り替える。

/// <summary>
/// マスターページならぬ、マスターコンテンツ用コントロール。
/// </summary>
[System.Windows.Markup.ContentProperty("Content")]
public class TawamureContents : ContentControl {

#region コンストラクタ

/// <summary>
/// staticコンストラクタ
/// </summary>
static TawamureContents() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(TawamureContents),
new FrameworkPropertyMetadata(typeof(TawamureContents)));
}

#endregion
}

これに、各々のパーツ様にプロパティを実装していく。具体的には、

ヘッダーが欲しい→ヘッダー用の(依存関係)プロパティを作る
フッターーが欲しい→フッター用の(依存関係)プロパティを作る
ステータスバーが欲しい→ControlTemplateに組み込む。
ステータスメッセージを出したい→ステータスメッセージ用の(依存関係)プロパティを作る

な感じ。

#region 依存関係プロパティ

/// <summary>
/// ヘッダを取得|設定します。
/// </summary>
public object Header {
get {return (object)GetValue(HeaderProperty);}
set {SetValue(HeaderProperty, value);}
}

/// <summary>ヘッダ</summary>
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(object),
typeof(TawamureContents), new UIPropertyMetadata(null));


/// <summary>
/// フッタを取得|設定します。
/// </summary>
public object Footer {
get {return (object)GetValue(FooterProperty);}
set {SetValue(FooterProperty, value);}
}

/// <summary>フッタ</summary>
public static readonly DependencyProperty FooterProperty =
DependencyProperty.Register("Footer", typeof(object),
typeof(TawamureContents), new UIPropertyMetadata(null));


/// <summary>
/// ステータスメッセージを取得|設定します。
/// </summary>
public object StatusMessage {
get { return (object)GetValue(StatusMessageProperty); }
set { SetValue(StatusMessageProperty, value); }
}

/// <summary>ステータスメッセージ</summary>
public static readonly DependencyProperty StatusMessageProperty =
DependencyProperty.Register("StatusMessage", typeof(object),
typeof(TawamureContents), new UIPropertyMetadata(null));

#endregion
型をオブジェクトにしているのは、基本なんでも設定できるようにするため。
ここまでやったら、このクラス用のスタイルを定義する。続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

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

マスターページならぬマスターウィンドウが欲しい。の続き。
マスタページは、ページか何かを継承して使っていたけど、Windowを継承すると、色々めんどそうな感じがする。
WPFには、Windowの他にもNavigationWindowやPageなんてクラスもあるし。
Windows.Formsなら、そうせざるを得ないのかもしれないけど、WPFなので、ControlTemplateを使う事にした。
ControlTemplateは、コントロール有りきのテンプレートで、DataContext(VM)側をあまり意識しない。
「こういうプロパティを持っているだろう」という前提でバインディングを記述していく。
仕事で使った時の作り方は、

1.ContentControlを継承したクラスを作る。
2.XAML内で1で作ったクラスをTargetTypeにしたコントロールテンプレートを定義する。
3.1向けのViewModelクラス(基底)を実装する。

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

画像を貼り付けてみようとしたら、勘違いに気づいた。

背景色にColor以外を設定してみた。の流れで、背景に画像を貼り付けてみようと思った。

1.プロジェクト直下に画像ファイルを配置し、プロジェクトに含める。
 ビルドアクションは「Resource」で。

2.App.xamlにリソースとして登録する。
<Application x:Class="TawamureDays.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<BitmapImage x:Key="WallPaper" UriSource="wallpaper.png"/>
</Application.Resources>
</Application>


3.Backgroundプロパティに設定する。
<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="418" Width="402">
<Grid Name="MainGrid">
<Grid.Background>
<ImageBrush ImageSource="{StaticResource WallPaper}"/>
</Grid.Background>
</Grid>
</Window>

リソースとして登録した情報を使う場合は、StaticResourceかDynamicResourceで利用できる。
実行↓
20120904_1

異なる型同士でバインディングさせたい時もある。の最後の方にも書いたんだけど、FreezableなリソースはFreezeしておいた方が良いってんで、
XAML上でフリーズさせる為の表記をここに書こうと思ったんだけど…。
上で設定したリソースさん達をコードビハインドで取得して、Freezeされてない事をまず確認して~とか考えてコードを記述した。
        /// <summary>
/// Loadedイベントハンドラ
/// </summary>
/// <param name="sender">イベント発生元</param>
/// <param name="e">イベント引数</param>
void MainWindow_Loaded(object sender, RoutedEventArgs e) {
var bkBrush = MainGrid.Background as ImageBrush;

if (bkBrush != null) {
var image = bkBrush.ImageSource;
var canFreeze = image.CanFreeze;
var frozen = image.IsFrozen;
}

var bitmap = Application.Current.FindResource("WallPaper") as BitmapImage;

if (bitmap != null) {
var canFreeze = bitmap.CanFreeze;
var frozen = bitmap.IsFrozen;
}

return;
}

そうしたら、なんと、既にFreezeされてる(IsFrozen = true)んだな、これがOrz。
なんてこったい。じゃあ、嬉しげに設定したやつ意味ないのかΣ(T◇T)って事に気づいた。
まあ、その仕様の方が、いちいち設定しなくてもパフォーマンスには良いので、設定を消せばいいだけなんだけど…。
ということは、自分でnewでもしない限りは、Freezeしてくれるのかもしれない。
さきに調べておけばなぁとちょっとへこんでしまった。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

変更通知機能、その弐

変更通知機能で書いたけど、自前の依存関係プロパティや、添付プロパティに関する変更検知は、比較的簡単に実装できる。
では、既存の依存関係プロパティの変更検知はどうするんだろ?と思った。
それを目的に調査はしていないけど、開発の過程で分かった事をメモしておく。
続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

背景色にColor以外を設定してみた。

異なる型同士でバインディングさせたい時もある。で、Color以外にも設定できると書いたので、実際に設定してみた。
<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>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<DockPanel>
<DockPanel.Background>
<LinearGradientBrush>
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="Black" Offset="0.5"/>
<GradientStop Color="Blue" Offset="1"/>
</LinearGradientBrush>
</DockPanel.Background>
</DockPanel>
<DockPanel Grid.Row="1">
<DockPanel.Background>
<RadialGradientBrush>
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="Black" Offset="0.5"/>
<GradientStop Color="Blue" Offset="1"/>
</RadialGradientBrush>
</DockPanel.Background>
</DockPanel>
<DockPanel Grid.Row="2">
<DockPanel.Background>
<LinearGradientBrush>
<GradientStop Color="White" Offset="0.1"/>
<GradientStop Color="Black" Offset="0.25"/>
<GradientStop Color="Green" Offset="0.5"/>
<GradientStop Color="Red" Offset="0.75"/>
<GradientStop Color="Blue" Offset="0.9"/>
</LinearGradientBrush>
</DockPanel.Background>
</DockPanel>
</Grid>
</Window>
実行してみる。続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

異なる型同士でバインディングさせたい時もある。

UI系のコントロールが持つBackgroundプロパティは、背景色を示すプロパティではあるが、その型は、Color構造体ではなく、Brushというクラスである。
これは、単純なColor構造体では、グラデーションのかかった背景色なんかが実装できない為だろう。
XAML上では、Colorの値を指定すれば、裏でTypeConverterが働いて、Brushクラスのインスタンスに変換してくれる。しかし、DataContext側にColor型のプロパティを持たせて、それとデータバインディングをしようとしても、変換はしてくれない。こんなときに使うのが、コンバータクラス(Converter)だ。
コンバータ(クラス)は、System.Windows.Data.IValueConverterインターフェイスを実装するクラスの事。ある型から別の型に変換するためのクラス。今回だけでは説明しないけど、よく実装されるのが、

bool→Visibility
decimal?←→string

等々。今回は、Color←→Brushとするコンバータを実装する。
using System;
using System.Windows.Data;
using System.Windows.Media;

namespace TawamureDays.Converters {

/// <summary>
/// Color構造体から、Brushクラスインスタンスへ変換するコンバータ。
/// </summary>
[ValueConversion(typeof(Color), typeof(SolidColorBrush))]
public sealed class ColorToSolidBrushConverter : IValueConverter {

#region IValueConverter メンバ

/// <summary>
/// 値を変換します。
/// </summary>
/// <param name="value">バインディング ソースによって生成された値。</param>
/// <param name="targetType">バインディング ターゲット プロパティの型。</param>
/// <param name="parameter">使用するコンバーター パラメーター</param>
/// <param name="culture">コンバーターで使用するカルチャ。</param>
/// <returns>変換された値</returns>
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture) {

if (value == null) {
return new SolidColorBrush(Colors.Transparent);
}

try {
var color = (Color)value;
return new SolidColorBrush(color);

} catch {
return new SolidColorBrush(Colors.Transparent);
}
}

/// <summary>
/// 値を変換します。(未実装)
/// </summary>
/// <param name="value">バインディング ターゲットによって生成される値。</param>
/// <param name="targetType">変換後の型。</param>
/// <param name="parameter">使用するコンバーター パラメーター</param>
/// <param name="culture">コンバーターで使用するカルチャ。</param>
/// <returns>変換された値</returns>
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture) {
//何もしない
var brush = value as SolidColorBrush;

if (brush != null) {
return brush.Color;
}

return Colors.Transparent;
}

#endregion
}
}

ValueConversion属性は、設定しなくても動作する。
Convertメソッド:ソース→ターゲット
ConvertBackメソッド:ソース←ターゲット
でデータバインディングから呼び出される。
実装例:
<Window x:Class="TawamureDays.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tw="http://schemas.tawamuredays.jp/wpf/gui"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<tw:ColorToSolidBrushConverter x:Key="ColorToSolidBrushConverter"/>
</Window.Resources>
<Grid Background="{Binding BackColor, Converter={StaticResource ColorToSolidBrushConverter}}">
</Grid>
</Window>

ColorToSolidBrushConverterクラスインスタンスをWindowのリソースとして登録している。キー名は、クラス名を使っている。別になんでもいいけど、そのほうがわかりやすい。コードビハインドは↓のようになる。

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

#region コンストラクタ

/// <summary>
/// コンストラクタ
/// </summary>
public MainWindow() {
InitializeComponent();
this.DataContext = this;
this.BackColor = Colors.Tomato;

return;
}

#endregion

#region プロパティ

/// <summary>
/// 背景色を取得|設定します。
/// </summary>
public Color BackColor {
get; set;
}

#endregion
}

MVVMパターンであれば、VMクラスのプロパティとして、BackColorを実装することになる。
実行すると↓のような感じになる。
20120903_1

ん。たしかにトマト色だな。
ConvertBackメソッドも実装しているので、ターゲット側で変更された場合も、その値がソース側に送られる事になる。
ただし、バインディングのモードが、「TwoWay」や「OneWayToSource」の場合に限るけど。
ConvertBackメソッドを実装できたのは、今回の型が可逆的な関係にあるからで、常にそうとは限らない。
例えば、リスト→リストのアイテム数のような変換では、リストのアイテム数(int)からその具体的な内容はわからない。
こういうときは、ConvertBackメソッドは実装できないので、

/// <summary>
/// 値を変換します。(未実装)
/// </summary>
/// <param name="value">バインディング ターゲットによって生成される値。</param>
/// <param name="targetType">変換後の型。</param>
/// <param name="parameter">使用するコンバーター パラメーター</param>
/// <param name="culture">コンバーターで使用するカルチャ。</param>
/// <returns>変換された値</returns>
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture) {
//何もしない
return Binding.DoNothing;
}
や、
/// <summary>
/// 値を変換します。(未実装)
/// </summary>
/// <param name="value">バインディング ターゲットによって生成される値。</param>
/// <param name="targetType">変換後の型。</param>
/// <param name="parameter">使用するコンバーター パラメーター</param>
/// <param name="culture">コンバーターで使用するカルチャ。</param>
/// <returns>変換された値</returns>
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
のようにする。前者は何も発生しない。後者は例外が発生する。特にエラーでなくても良いときは前者かな。
Converterは便利で、仕事でも色々作った。続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

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

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

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

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