antsk blog

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
  1. --/--/--(--) --:--:--|
  2. スポンサー広告

WPFコントロールのテンプレートをいじりたい

WPFの標準コントロールテンプレートはまだこなれてないというか、
ControlTemplateをいじって問題を解決しなきゃいけない場面がある。

ListView のカラムヘッダの幅を固定する
とか、
Horizontal stretch on TreeViewItems
とか。


……で、上記のページでは解決法が示されてるんだけど、
これだとTemplateを丸ごと置き換えなきゃいけない。

そうすると、イチからアプリケーションテーマを作りこんでるならともかく、
標準テーマでやってる場合はシステムのほかの部分のコントロールから
見た目が浮いてしまう。

テンプレートは標準のままで、問題になってる一部分を修正して使いたい。
……というわけで、だいぶ限定的だけど、そんな感じのものを作った。

TemplateOperation.cs

これで提供される TemplateOperation.AddStyle 添付プロパティを使って
<GridViewColumnHeader>
<TemplateOperation.AddStyle>
<ResourceDictionary>
<Style x:Key="PART_HeaderGripper">
<Setter Property="Control.Visibility"
Value="Collapsed" />
</Style>
</ResourceDictionary>
</TemplateOperation.AddStyle>
</GridViewColumnHeader>

と書くと、テンプレート内の "PART_HeaderGripper"要素を
非表示にするのでユーザーは列サイズを変更できなくなる。

……ま、テンプレートの要素名がいつも同じとは限らないわけだが
少なくとも現在PresentationFrameworkにくっついてる標準Templateでは
どのテーマも同じ名前を各要素につけてるから大丈夫なはず。
※ AeroとClassicしか試してない

たぶんMSDNのサンプルテーマは標準テーマと同じ内容のはずで、
これを基に作った多くのアプリケーションテーマでもそれなりに利用できる、といいなあ。


以下利用サンプルzip.(拡張子変えてアップしてる)
XAMLUtil.mp3
スポンサーサイト

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2009/09/12(土) 00:41:14|
  2. WPF
  3. | トラックバック:0
  4. | コメント:0

WPFパネルの使い方まとめ

しばらくWPFを触ってて、当初はWinFormとかなり異なる作り方に難儀したけど
だいぶ理解してきたのでレイアウト(というか、パネルの使い方)の基本(と、自分が勝手に思っていること)をメモ。

基本



レイアウトはGridが基本。
Gridにできないことなど、あんまりない。
いろいろと試行錯誤した結果、少なくともWinFormのダイアログと同じような画面を作る場合は
以下の単純なルールが良さそう。


ようするに、


というのを再帰的に行っていけば、WPFがうまい具合に要素を配置してくれる。

パターン



2分割



縦か横に2分割する場合、上(左)部のサイズを固定にするか、下(右)部のサイズを固定にするかでちょっと違う。

上または左が固定



例:
fixtop.jpgfixleft.jpg

テンプレート

上が固定の場合:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<SubPanel Grid.Row="0">
上のほうのコントロール
</SubPanel>
<SubPanel Grid.Row="1">
下のほうのコントロール
</SubPanel>
</Grid>


左が固定の場合:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<SubPanel Grid.Column="0">
左側のコントロール
</SubPanel>
<SubPanel Grid.Column="1">
右側のコントロール
</SubPanel>
</Grid>


一般的な「上から下」、「左から右」へのコントロール配置。

このパターンでは常に DockPanel をかわりに使うことができる。

上が固定の場合:
<DockPanel>
<SubPanel DockPanel.Dock="Top">

</SubPanel>
<SubPanel>

</SubPanel>
</DockPanel>


左が固定の場合:
<DockPanel>
<SubPanel DockPanel.Dock="Left">

</SubPanel>
<SubPanel>

</SubPanel>
</DockPanel>


DockPanelの方が記述量が減るのでオススメ。

下または右が固定



例:
fixbuttom.jpgfixright.jpg

下が固定の場合:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<SubPanel Grid.Row="0">
上のほうのコントロール
</SubPanel>
<SubPanel Grid.Row="1">
下のほうのコントロール
</SubPanel>
</Grid>


右が固定の場合:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<SubPanel Grid.Column="0">
左側のコントロール
</SubPanel>
<SubPanel Grid.Column="1">
右側のコントロール
</SubPanel>
</Grid>


「OK」「キャンセル」などのボタンは、
たいていダイアログウインドウの下 or 右に配置するのでこのパターンになる。

このパターンではDockPanelを使えない場合が多いことに注意。

DockPanelは「最後の要素」を残りの領域全体に割り当てるので、どうしても
<DockPanel>
<SubPanel DockPanel.Dock="Buttom">
下のほうのコントロール
</SubPanel>
<SubPanel>
上のほうのコントロール
</SubPanel>
</DockPanel>

と、下や右のコントロールを先に書かなければいけない。

タブオーダーは明示的にTabIndexを指定しなければXAMLの記述順になるから、
これだとTabキーで移動したときの動きがわけわからなくなる。

結局、ステータスバーやコンボボックスのドロップダウンボタンのように、
下や右のコントロールがフォーカスを受け取らない場合にのみDockPanelが使える。

3分割



3分割では、中央を * 幅にする。

menuandstatus.jpg
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<SubPanel Grid.Row="0">
上のほうのコントロール
</SubPanel>
<SubPanel Grid.Row="1">
中央のコントロール
</SubPanel>
<SubPanel Grid.Row="2">
下のほうのコントロール
</SubPanel>
</Grid>


やはりこれも、下や右のコントロールがフォーカスを受け取らなければDockPanelを使うことができる。
<DockPanel>
<SubPanel DockPanel.Dock="Top">
上のほうのコントロール
</SubPanel>
<SubPanel DockPanel.Dock="Buttom">
下のほうのコントロール
</SubPanel>
<SubPanel>
中央のコントロール
</SubPanel>
</DockPanel>


ただ、このパターンは


という形で使うことがほとんどなので、
普通にDockPanelでいい場合のほうが多いかも。

いっぱい



単純に縦または横に並べるだけなら、幅Autoで必要な行数・列数だけ作る。

itemlist.jpg
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<SubPanel Grid.Row="0" >

</SubPanel>
<SubPanel Grid.Row="1" >

</SubPanel>
<SubPanel Grid.Row="2" >

</SubPanel>
<SubPanel Grid.Row="3">

</SubPanel>
....
</Grid>


このような場合は、StackPanelを代わりに使えばインデックスを指定しなくていいのでラクチン。
<StackPanel>
<SubPanel>

</SubPanel>
<SubPanel>

</SubPanel>
<SubPanel>

</SubPanel>
<SubPanel>

</SubPanel>
....
</StackPanel>


縦横揃え



左側にタイトルラベル、右側に入力コントロール、という形で縦に並んでいるパターン。

align.jpg
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<SubPanel Grid.Row="0" Grid.Column="0">
タイトル1
</SubPanel>
<SubPanel Grid.Row="0" Grid.Column="1">
入力1
</SubPanel>
<SubPanel Grid.Row="1" Grid.Column="0">
タイトル2
</SubPanel>
<SubPanel Grid.Row="1" Grid.Column="1">
入力2
</SubPanel>
....
</Grid>


恐るべき面倒くささを誇る。
何とかこれをうまい具合にRow/Column指定せずに済ませられればいいんだけど……。

位置あわせ



さて、上記のパネル指定だけだと、コントロールは隙間なくぴっちり配置されてしまう。

例:単にこれだけだと、
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0"
Orientation="Horizontal">
<Button Content="Button1" />
<Button Content="Button2" />
<Button Content="Button3" />
</StackPanel>
<DockPanel Grid.Row="1">
<TextBlock DockPanel.Dock="Top"
Text="一覧" />
<ListBox>
<ListBoxItem Content="Item1" />
<ListBoxItem Content="Item2" />
<ListBoxItem Content="Item3" />
<ListBoxItem Content="Item4" />
<ListBoxItem Content="Item5" />
<ListBoxItem Content="Item6" />
</ListBox>
</DockPanel>
</Grid>


こんな感じ。
nopadding.jpg

このためPaddingやMarginを指定して微調整する必要がある。

このとき、個々のButtonなどには直接レイアウト情報を設定せずに
パネルのMarginやResourceのスタイル指定で調整すると
複数のコントロールをきれいにあわせることができる。

コントロールレベルの要素にはバインディングなんかのデータ情報だけ書いて
表示とデータの分離を進めておくと何かと良いかも。

例:こんな感じにすると
<Grid Margin="6" > <!-- Margin指定で全体を内側に寄せる -->
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0"
Orientation="Horizontal">
<StackPanel.Resources>
<!-- ButtonのStyle指定で隙間と大きさを調整 -->
<Style TargetType="Button">
<Setter Property="Margin"
Value="3" />
<Setter Property="Padding"
Value="5 0" />
</Style>
</StackPanel.Resources>
<Button Content="Button1" />
<Button Content="Button2" />
<Button Content="Button3" />
</StackPanel>
<DockPanel Grid.Row="1"
Margin="0 3" ><!-- Margin指定でちょっとボタンと離す -->
<TextBlock DockPanel.Dock="Top"
Text="一覧" />
<ListBox>
<ListBoxItem Content="Item1" />
<ListBoxItem Content="Item2" />
<ListBoxItem Content="Item3" />
<ListBoxItem Content="Item4" />
<ListBoxItem Content="Item5" />
<ListBoxItem Content="Item6" />
</ListBox>
</DockPanel>
</Grid>


こうなる
padding.jpg

備考



ちなみに自分はBlendとか持ってないので
デザインツールを使用してXAMLを書くことはまったく考慮してない。

VSのWPFデザイナ?
あれは単にXAML編集結果をリアルタイムで表示してくれるビューです。

マウスでぺたぺたコントロールを貼り付けてUIを作る時代は終わった……。

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2009/09/10(木) 00:57:51|
  2. WPF
  3. | トラックバック:0
  4. | コメント:0

MVVMでViewModelからダイアログを扱いたい・続き

昨日の続き。

ViewModelRelation.Register(viewModel, view);
……ってのはviewにWPFオブジェクトを想定してるんだから
添付プロパティとして表現すれば簡単に使えそうだなー、と思って実装。

MVVM_Util.mp3
※ 例によって拡張子を変えてアップしてる

クラス名を ViewModelManagerということにして、
ViewModel添付プロパティを設定するとRegister()メソッドを呼ぶようにしてみた。

こんな感じで使う。

<Window ViewModelManager.ViewModel="{Binding RelativeSource={RelativeSource Self}, Path=DataContext}" .../>


……これ使うときってほとんど絶対にこの形だなー、と思ったので
省略用にViewModelPathもついでに追加。

<Window ViewModelManager.ViewModelPath="DataContext" .../>

で上と同じ。

これでDataContextに設定したViewModelからViewであるWindowが得られるようになる。

さて、実際に使ってみてた感じ……予想外に便利。
というか、ちと危険な便利さだなぁ。

ViewModelからViewが取れるようになったから

<Window
ViewModelManager.ViewModelPath="DataContext" >
<MyUserControl
DataContext="{Binding Path=Property1} "
ViewModelManager.ViewModelPath="DataContext"
/>
</Window>

みたいなパターンで、

本来WindowのViewModelにアクセスできなかった
MyUserControlのViewModelが
 自分のView(MyUserControl)を取得
  → Window.GetWindowで親を取得
   → WindowのViewModelを取得
て形で簡単に取り出せてしまう。

これではWindowのViewModelはほとんどグローバル変数だ……
ってstaticに保存してるんだから当然なんだけどさ。

まあ、もともと親子関係にあるオブジェクトで子から親を参照できるようになっただけだから
「そういうことはあんまりしないでね」とか言っとくだけでいいかなぁ。

とはいえ、これでとりあえず
Windowの基本機能を呼び出したり
ダイアログを表示したりするラッパーを作れば
ある程度テスト可能な状態を維持しつつ
VM側からViewを操作できそうかな?

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2009/09/05(土) 00:17:25|
  2. WPF
  3. | トラックバック:0
  4. | コメント:0

MVVMでViewModelからダイアログを扱いたい

MVVMでViewModelはテストしたい。MessageBoxの扱いをどうする?

で言われてるように、MVVMで作ってると、
ViewModelからメッセージを表示したり
ViewのDialogResultを設定して閉じたりしたくなるんだけど
実際なんか良い方法がわからなくて悩んでる。


Viewを直接操作したくはないから
イベントを飛ばしてViewで処理したりとか、
……めんどくさい……。

結局「ViewModelオブジェクトからViewを取り出せるようにする」
でやってみようか、と思って作ってみた。

MVVM_Util.mp3
※ zipがアップロードできないので拡張子を変えてアップしてる。

使用法としては
1.Viewで ViewModelRelation.Register(viewModel, view); する。
2.ViewModelで ViewModelRelation.GetView(viewModel); するとViewが返ってくる
3.上記を利用して、ViewModelを引数に受けるインターフェースメソッドを実装するときに内部でViewを取り出す

これで、ViewModelが直接使用するViewModelMessageBoxにはViewを渡さなくてよくなるので
ViewModel→Viewの依存が減る……はず。

Registerでstaticに登録することになるけどWeakEventManagerでそのうち回収されるので問題なし。

……Registerメソッドじゃなくて添付プロパティにすればよかったな。
まあいいや。明日にしよう。

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2009/09/03(木) 23:52:42|
  2. WPF
  3. | トラックバック:0
  4. | コメント:0

プロフィール

antsk

Author:antsk
主にC#のプログラマ。

最新記事

最新コメント

最新トラックバック

月別アーカイブ

カテゴリ

未分類 (0)
.net (1)
WPF (8)

検索フォーム

RSSリンクの表示

リンク

このブログをリンクに追加する

ブロとも申請フォーム

この人とブロともになる

QRコード

QRコード

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