WPF習作メモ2

こういうのを作りたいとします。Borderの中にあるチェックボックスをチェックすると、ボーダーの色と太さが変わるというもの。コードビハインド使えば何とでもなるので、XAMLのみ縛りで。
blog20191219-DataBindingToChild

こういうXAMLを書けば普通に動きます。今回の件でStackPanelは必要ないのですが、CheckBoxだけBorderで囲むなんて実用的じゃないですのでオマケ。

<Border>
    <Border.Style>
        <Style TargetType="{x:Type Border}">
            <Setter Property="BorderBrush" Value="Gray"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsChecked,ElementName=checkbox1}" Value="true">
                    <Setter Property="BorderBrush" Value="RoyalBlue"/>
                    <Setter Property="BorderThickness" Value="3"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>                
    </Border.Style>
    <StackPanel>
        <CheckBox Name="checkbox1" IsChecked="False" Content="Check for blue border"/>
        <TextBlock Text="Test"/>
    </StackPanel>
</Border>

ただこれの難点は内部のチェックボックスに固有の名前を与えているのでこういうのを作るたびにElementNameを変えたStyleを書く必要があり、メンテナンス性が悪いことです。Styleをリソースで定義して使いまわしたいわけです。子要素から親要素へのBindingはFindAncestorを使えばいいですが逆はどうするかというと、BorderのプロパティChildが内部のStackPanelを指しそれのプロパティChildren[0]がStackPanelの第1子要素CheckBoxを指してくれますので、Bindingパス Child.Children[0].IsCheckedで得られます。

ここからが本題。下記XAMLはうまく動きません。
<Window.Resources>
    <Style x:Key="BorderStyle1" TargetType="{x:Type Border}">
        <Setter Property="BorderBrush" Value="Gray"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Style.Triggers>
            <DataTrigger Binding="{Binding Child.Children[0].IsChecked}" Value="true">
                <Setter Property="BorderBrush" Value="RoyalBlue"/>
                <Setter Property="BorderThickness" Value="3"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
<Border Style="{StaticResource BorderStyle1}">
    <StackPanel>
        <CheckBox Name="checkbox1" IsChecked="False" Content="Check for blue border"/>
        <TextBlock Text="Test"/>
    </StackPanel>
</Border>

理由はXAMLは上から処理されていくから。BorderのStyle={StaticResource...}の部分でBindingパスを解決しようとするのですが、XAML内では後に書いてある子要素のStackPanelやCheckBoxはまだ処理されておらずオブジェクトとして存在してません。つまりBorderのChildはまだnullなのです。ElementNameの場合はXAML上で後ろの物でもちゃんと探してくれるのですが、こちらはダメのようです。

ならばStyleの設定をXAML上で後に書けばいいのです。というわけで、以下のXAMLは動きます。

<Window.Resources>
    <Style x:Key="BorderStyle1" TargetType="{x:Type Border}">
        <Setter Property="BorderBrush" Value="Gray"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Style.Triggers>
            <DataTrigger Binding="{Binding Child.Children[0].IsChecked,RelativeSource={RelativeSource Self}}" Value="true">
                <Setter Property="BorderBrush" Value="RoyalBlue"/>
                <Setter Property="BorderThickness" Value="3"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
<Border>
    <StackPanel>
        <CheckBox Name="checkbox1" IsChecked="False" Content="Check for blue border"/>
        <TextBlock Text="Test"/>
    </StackPanel>
    <Border.Style>
        <Style TargetType="{x:Type Border}" BasedOn="{StaticResource BorderStyle1}"/>
    </Border.Style>
</Border>
注意すべき点があります。
  • Border.Style内のStyleにTargetTypeをちゃんと指定すること。そうでないとこのStyleはUIElementの物として扱われ、その継承クラスであるBorder用に作られたBorderStyle1をBasedOnとして設定できません。
  • リソースStyleでのBindingにRelativeSource Self設定が無いと動きません。これの理由は不明。

— posted by mu at 01:21 am   commentComment [0]  pingTrackBack [0]

T: Y: ALL: Online:
ThemeSwitch
  • Basic
Created in 0.0095 sec.
prev
2019.12
next
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31