< July 2008 | August 2008 | September 2008 >

August 21, 2008

メモ: OpacityMask

MSDN ライブラリ > .NET 開発 > .NET Framework 3.5 > .NET Framework > Windows Presentation Foundation > グラフィックスとマルチメディア > グラフィックス > ブラシ > 不透明マスクの概要

ところで、OpacityMask って引き算はできないの? 不透明に透明を重ねても不透明のままだよね…。
→GeometryGroup::FillRule 参照!

TileBrush の Viewbox, Viewport について

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>

August 20, 2008

ListView のカラムヘッダの幅を固定する

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>

August 19, 2008

DrawingBrush

ずっと 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>

August 11, 2008

DataTemplate で生成されたコントロールにアクセスする

リストビューの各行にチェックボックスを配置して、さらにカラムヘッダにも全選択/全選択解除用のチェックボックスを配置しました。ヘッダのチェックボックスのチェック状態を変更すると、すべての行のチェック状態が連動するようなイメージです。
<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;
}
結構レスポンスが悪いので、もう少し効率のよい方法がないか考えます・・・。

August 8, 2008

XAML ファイル内にデータソースを埋め込む

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

August 7, 2008

読み取り専用の依存関係プロパティ

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

参照:MSDN ライブラリ 添付プロパティの概要