TileBrush を基底クラスとする DrawingBrush や ImageBrush などを定義する際、ソースのどの範囲を塗りつぶし先でどのように出力するか、といった指定をするプロパティた群について、使い方がよくわかっていませんでした。ちょっと整理してみたので書いてみます。
Viewbox プロパティと ViewboxUnits プロパティは、塗りつぶしに使用するソースのクリッピング領域を表します。
| Viewbox | ViewboxUnits | 塗りつぶしに使用する範囲 |
|---|---|---|
| 0,0,1,1 | RelativeToBoundingBox | ブラシの内容として指定された要素すべてを内包する矩形範囲 |
| Absolute | 要素左上から、1デバイス非依存ピクセルの幅・高さを持つ矩形範囲 |
Viewport プロパティと ViewportUnits プロパティは、塗りつぶし先での、ブラシの1タイルが占める領域を表します。
| Viewport | ViewportUnits | Stretch="Fill" の場合の塗りつぶし方法 |
|---|---|---|
| 0,0,1,1 | RelativeToBoundingBox | Viewbox の内容を出力領域の幅・高さに引き伸ばして出力する |
| Absolute | Viewbox の内容を1デバイス非依存ピクセルの幅・高さとして出力する |
<DrawingBrush x:Key="PunchedMetalBrush" TileMode="Tile" Viewport="0,0,20,20" Stretch="None" Viewbox="0,0,20,20" ViewportUnits="Absolute" ViewboxUnits="Absolute">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing>
<GeometryDrawing.Brush>
<SolidColorBrush Color="Gray"/>
</GeometryDrawing.Brush>
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,20,20"/>
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing>
<GeometryDrawing.Brush>
<SolidColorBrush Color="White"/>
</GeometryDrawing.Brush>
<GeometryDrawing.Geometry>
<GeometryGroup>
<EllipseGeometry Center="5,6" RadiusX="3" RadiusY="3"/>
<EllipseGeometry Center="15,16" RadiusX="3" RadiusY="3"/>
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing>
<GeometryDrawing.Brush>
<SolidColorBrush Color="Black"/>
</GeometryDrawing.Brush>
<GeometryDrawing.Geometry>
<GeometryGroup>
<EllipseGeometry Center="5,5" RadiusX="3" RadiusY="3"/>
<EllipseGeometry Center="15,15" RadiusX="3" RadiusY="3"/>
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
WPF の ListView では、カラムヘッダの幅を変更を不可にする手段が用意されていないようです。(Windows 標準の ListView にもなかった気もしますが・・・)
GridViewColumn の HeaderContainerStyle プロパティで、GridViewColumnHeader のスタイルを指定することができるので、Expression Blend で展開した GridViewColumnHeader の VisualTree をいじくることに。
結局、幅変更用の Thumb を無効にすることで目的を達成することができました。
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ListViewColumnTest.Window2"
Title="Window2">
<Window.Resources>
<!-- ブラシリソース -->
<SolidColorBrush x:Key="GridViewColumnHeaderBackground" Color="#FFECE9D8"/>
<LinearGradientBrush x:Key="GridViewColumnHeaderHighlightBackground" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFECE9D8" Offset="0"/>
<GradientStop Color="#FFCBC7B8" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="GridViewColumnHeaderDarkBackground" Color="#FFCBC7B8"/>
<SolidColorBrush x:Key="GridViewColumnHeaderGripperBackground" Color="#FFC7C5B2"/>
<!-- カラムヘッダサイズ変更用の Thumb -->
<Style x:Key="GridViewColumnHeaderGripper" TargetType="{x:Type Thumb}">
<Setter Property="Canvas.Right" Value="-9"/>
<Setter Property="Width" Value="18"/>
<Setter Property="Height" Value="{Binding Path=ActualHeight, RelativeSource={RelativeSource TemplatedParent}}"/>
<Setter Property="Padding" Value="0,3,0,4"/>
<Setter Property="Background" Value="{StaticResource GridViewColumnHeaderGripperBackground}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border Background="Transparent" Padding="{TemplateBinding Padding}">
<DockPanel HorizontalAlignment="Center">
<Rectangle Fill="{TemplateBinding Background}" Width="1"/>
<Rectangle Fill="White" Width="1"/>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ブラシリソース -->
<SolidColorBrush x:Key="TabItemHotBorderBrush" Color="#FFE68B2C"/>
<SolidColorBrush x:Key="TabItemHotBorderBackround" Color="#FFFFC73C"/>
<SolidColorBrush x:Key="GridViewColumnHeaderHoverBackground" Color="#FFFAF8F3"/>
<SolidColorBrush x:Key="GridViewColumnHeaderPressBorder" Color="#FFA5A597"/>
<SolidColorBrush x:Key="GridViewColumnHeaderPressBackground" Color="#FFDEDFD8"/>
<!-- カラムヘッダのテンプレート -->
<Style x:Key="FixedGridViewColumnHeaderStyle" TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Background" Value="{StaticResource GridViewColumnHeaderBackground}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="2,0,2,0"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GridViewColumnHeader}">
<Grid SnapsToDevicePixels="true" Background="{TemplateBinding Background}">
<Border x:Name="HighlightBorder" VerticalAlignment="Bottom" Height="3" Background="{StaticResource GridViewColumnHeaderHighlightBackground}" BorderBrush="{StaticResource GridViewColumnHeaderDarkBackground}" BorderThickness="0,0,0,1"/>
<Border Margin="1,0,1,0" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="0,0,0,1" x:Name="HeaderContent" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True"/>
</Border>
<Canvas>
<!-- カラムヘッダ幅変更用の Thumb の IsEnabled を False に -->
<Thumb x:Name="PART_HeaderGripper" Style="{StaticResource GridViewColumnHeaderGripper}" IsEnabled="False"/>
</Canvas>
<Border x:Name="HeaderPressBorder" BorderThickness="1"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="BorderBrush" TargetName="HighlightBorder" Value="{StaticResource TabItemHotBorderBrush}"/>
<Setter Property="Background" TargetName="HighlightBorder" Value="{StaticResource TabItemHotBorderBackround}"/>
<Setter Property="CornerRadius" TargetName="HighlightBorder" Value="0,0,3,3"/>
<Setter Property="BorderThickness" TargetName="HighlightBorder" Value="1,0,1,1"/>
<Setter Property="Background" TargetName="PART_HeaderGripper" Value="Transparent"/>
<Setter Property="Background" Value="{StaticResource GridViewColumnHeaderHoverBackground}"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Visibility" TargetName="HighlightBorder" Value="Hidden"/>
<Setter Property="Visibility" TargetName="PART_HeaderGripper" Value="Hidden"/>
<Setter Property="BorderBrush" TargetName="HeaderPressBorder" Value="{StaticResource GridViewColumnHeaderPressBorder}"/>
<Setter Property="Margin" TargetName="HeaderPressBorder" Value="1,0,0,0"/>
<Setter Property="Margin" TargetName="HeaderContent" Value="1,1,0,0"/>
<Setter Property="Background" Value="{StaticResource GridViewColumnHeaderPressBackground}"/>
<Setter Property="BorderBrush" Value="{StaticResource GridViewColumnHeaderPressBackground}"/>
</Trigger>
<Trigger Property="Height" Value="Auto">
<Setter Property="MinHeight" Value="20"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Role" Value="Floating">
<Setter Property="Opacity" Value="0.7"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GridViewColumnHeader}">
<Canvas x:Name="PART_FloatingHeaderCanvas"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Role" Value="Padding">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GridViewColumnHeader}">
<Grid SnapsToDevicePixels="true" Background="{TemplateBinding Background}">
<Border VerticalAlignment="Bottom" Height="3" Background="{StaticResource GridViewColumnHeaderHighlightBackground}" BorderBrush="{StaticResource GridViewColumnHeaderDarkBackground}" BorderThickness="0,0,0,1"/>
<Border Margin="1,0,1,0" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Height" Value="Auto">
<Setter Property="MinHeight" Value="20"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
<!-- データソース -->
<XmlDataProvider x:Key="MyDataSource" Source="http://frog.raindrop.jp/index.xml" XPath="/rss/channel">
<XmlDataProvider.XmlNamespaceManager>
<XmlNamespaceMappingCollection>
<XmlNamespaceMapping Prefix="dc" Uri="http://purl.org/dc/elements/1.1/"/>
<XmlNamespaceMapping Prefix="sy" Uri="http://purl.org/rss/1.0/modules/syndication/"/>
<XmlNamespaceMapping Prefix="admin" Uri="http://webns.net/mvcb/"/>
<XmlNamespaceMapping Prefix="rdf" Uri="http://www.w3.org/1999/02/22-rdf-syntax-ns#"/>
</XmlNamespaceMappingCollection>
</XmlDataProvider.XmlNamespaceManager>
</XmlDataProvider>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<ListView ItemsSource="{Binding Source={StaticResource MyDataSource}, XPath=item}">
<ListView.View>
<GridView>
<GridViewColumn Header="タイトル" Width="300" DisplayMemberBinding="{Binding XPath=title}" HeaderContainerStyle="{DynamicResource FixedGridViewColumnHeaderStyle}"/>
<GridViewColumn Header="カテゴリ" Width="250" DisplayMemberBinding="{Binding XPath=dc:subject}" HeaderContainerStyle="{DynamicResource FixedGridViewColumnHeaderStyle}"/>
<GridViewColumn Header="更新日時" DisplayMemberBinding="{Binding XPath=dc:date}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
ずっと DrawingBrush の定義方法がわからなかったのですが、やっと調べてブラシリソースを作りました。以下はその覚書です。
<!-- 透明と黒のシマシマ。OpacityMask に重宝 --> <DrawingBrush x:Key="StripeBrush" TileMode="Tile" Stretch="None" ViewportUnits="Absolute" Viewport="0,0,3,1"> <DrawingBrush.Drawing> <GeometryDrawing> <!-- 線分座標を定義 --> <GeometryDrawing.Geometry> <LineGeometry StartPoint="0.75,0" EndPoint="0.75,1"/> </GeometryDrawing.Geometry> <!-- 線分の線を定義 --> <GeometryDrawing.Pen> <Pen Thickness="1.5" Brush="Black"/> </GeometryDrawing.Pen> </GeometryDrawing> </DrawingBrush.Drawing> </DrawingBrush> <!-- 赤地に白の水玉ブラシ。 --> <DrawingBrush x:Key="DotBrush" TileMode="FlipXY" Stretch="None" ViewportUnits="Absolute" Viewport="0,0,20,20"> <DrawingBrush.Drawing> <!-- GeometryDrawing 要素が複数存在する場合は DrawingGroup 要素でグループ化する --> <DrawingGroup> <!-- 背景の矩形 --> <GeometryDrawing> <!-- 矩形の座標を定義 --> <GeometryDrawing.Geometry> <RectangleGeometry Rect="0,0,20,20"/> </GeometryDrawing.Geometry> <!-- 矩形の塗りを定義 --> <GeometryDrawing.Brush> <SolidColorBrush Color="Red"/> </GeometryDrawing.Brush> </GeometryDrawing> <!-- 水玉 --> <GeometryDrawing> <!-- 座標定義が複数存在する場合は GeometryGroup でグループ化する --> <GeometryDrawing.Geometry> <GeometryGroup> <EllipseGeometry Center="0,0" RadiusX="10" RadiusY="10"/> <EllipseGeometry Center="20,20" RadiusX="10" RadiusY="10"/> </GeometryGroup> </GeometryDrawing.Geometry> <!-- 水玉の塗りを定義 --> <GeometryDrawing.Brush> <SolidColorBrush Color="White"/> </GeometryDrawing.Brush> </GeometryDrawing> </DrawingGroup> </DrawingBrush.Drawing> </DrawingBrush>
<Window x:Class="CheckListView.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="パン">
<Window.Resources>
<XmlDataProvider x:Key="MyListSource" XPath="/Breads">
<x:XData>
<Breads xmlns="">
<Bread>ツォップフ</Bread>
<Bread>クロワッサン</Bread>
<Bread>ベーグル</Bread>
<Bread>フガス</Bread>
<Bread>バケット</Bread>
<Bread>リュスティック</Bread>
<Bread>ロッゲンフォルコーンブロート</Bread>
<Bread>チャパタ</Bread>
</Breads>
</x:XData>
</XmlDataProvider>
</Window.Resources>
<ListView x:Name="CheckBoxList" ItemsSource="{Binding Source={StaticResource MyListSource}, XPath=Bread}">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.HeaderTemplate>
<DataTemplate>
<CheckBox x:Name="CheckAll" Checked="CheckAll_Checked" Unchecked="CheckAll_Checked"/>
</DataTemplate>
</GridViewColumn.HeaderTemplate>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="CheckSelect"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="パンの種類" DisplayMemberBinding="{Binding XPath=.}"/>
</GridView>
</ListView.View>
</ListView>
</Window>
ヘッダのチェックボックス(CheckAll)の Checked と Unchecked のイベントハンドラ CheckAll_Checked の実装は、MSDN ライブラリの 方法 : DataTemplate によって生成された要素を検索する を参考にしました。
private void CheckAll_Checked (object sender, RoutedEventArgs e)
{
var checkAll = sender as CheckBox;
if (checkAll != null)
{
// ListView の各行を走査
foreach (var item in CheckBoxList.Items)
{
// 行より ListViewItem を取得
ListViewItem listViewItem = CheckBoxList.ItemContainerGenerator.ContainerFromItem (item) as ListViewItem;
if (listViewItem != null)
{
// ListViewItem の VisualTree より、ContentPresenter を検索する
ContentPresenter presenter = FindVisualChild<ContentPresenter> (listViewItem);
// ContentPresenter の DataTemplate より、名前でコントロールを検索する
CheckBox checkSelect = presenter.ContentTemplate.FindName ("CheckSelect", presenter) as CheckBox;
if (checkSelect != null)
{
// コントロールが見つかれば、チェック状態を追従
checkSelect.IsChecked = checkAll.IsChecked;
}
}
}
}
}
FindVisualTree の実装は上記リンクで紹介されているものと同じです。子要素を再帰検索して、最初に見つかった TChild 型の要素を返します。
private static TChild FindVisualChild<TChild> (DependencyObject parent)
where TChild : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount (parent); i++)
{
DependencyObject child = VisualTreeHelper.GetChild (parent, i);
if (child != null && child is TChild)
{
return (TChild) child;
}
else
{
TChild subItem = FindVisualChild<TChild> (child);
if (subItem != null)
{
return subItem;
}
}
}
return null;
}
結構レスポンスが悪いので、もう少し効率のよい方法がないか考えます・・・。
XmlDataProvider 要素内に x:XData 要素を定義して、その中に直接記述した XML の内容をリストボックスにデータバインドで表示しようとしていたのですが、なぜかデータが表示されず悩んでいました。
原因はなんだったかというと、ルート要素に名前空間の宣言が必要だったのです。下記のように xmlns="" を追加すると表示されるようになりました。
<UserControl.Resources>
<XmlDataProvider x:Key="MyDataSource" XPath="/Breads">
<x:XData>
<Breads xmlns="">
<Bread>ベーグル</Bread>
<Bread>フガス</Bread>
<Bread>バケット</Bread>
<Bread>バタール</Bread>
<Bread>フィセル</Bread>
<Bread>リュスティック</Bread>
<Bread>カイザーゼンメル</Bread>
<Bread>ロッゲンフォルコーンブロート</Bread>
<Bread>プレッツェル</Bread>
<Bread>チャパタ</Bread>
</Breads>
</x:XData>
</XmlDataProvider>
</UserControl.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource MyDataSource}, XPath=Bread}"/>
public static readonly DependencyPropertyKey PropertyNamePropertyKey =
DependencyProperty.RegisterReadOnly ("PropertyName", typeof (PropertyType), typeof (OwnerClass),
new FrameworkPropertyMetadata (DefaultValue));
public static readonly DependencyProperty PropertyNameProperty = PropertyNamePropertyKey.DependencyProperty;
public PropertyType PropertyName
{
get { return (PropertyType) GetValue (PropertyNameProperty); }
}
内部的に値を設定する際には、下記のようにする。
SetValue (PropertyNamePropertyKey, NewValue);
添付プロパティというのは Grid.Row みたいなやつで、要するに親要素の持つプロパティで、子要素が値を持つものです。コードから Grid.Row を設定する場合、
Grid.SetRow (button);
のように、親要素の静的メソッドを呼び出して設定します。添付プロパティの定義方法はこんな感じらしいです。
// 依存関係プロパティの定義 public static readonly DependencyProperty PropertyNameProperty = DependencyProperty.RegisterAttached ("PropertyName", typeof (PropertyType), typeof (OwnerClass), new FrameworkPropertyMetadata (DefaultValue, FrameworkPropertyMetadataOptions.AffectsRender)); // set アクセサ (静的メソッドとして定義) public static void SetPropertyName (UIElement element, PropertyType value) { element.SetValue (PropertyNameProperty, value); } // get アクセサ (静的メソッドとして定義) public static PropertyType GetPropertyName (UIElement element) { return (PropertyType) element.GetValue (PropertyNameProperty); }