dhq_boiler
@dhq_boiler

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

[WPF] DataTriggerが機能しない

お世話になっております。

解決したいこと

C# + WPFでベクターグラフィックスドローイングツールを開発しています。

2021-07-23.png

※下にソースコードへの案内を記載しております。よろしければそちらを参照ください。

発生している問題・エラー

現在、レイヤー機能を実装しているところなのですが、レイヤーの表示・非表示を切り替える目のアイコンのボタンを作ろうとしているところです。ここで、想定外の問題が発生しています。

Layers.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:control="clr-namespace:boilersGraphics.Controls"
                    xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
                    xmlns:helper="clr-namespace:boilersGraphics.Helpers"
                    xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase">
    <Style TargetType="{x:Type control:Layers}">

        <Style.Resources>
            <Style x:Key="ToggleButtonStyle" TargetType="ToggleButton">
                <Setter Property="SnapsToDevicePixels" Value="true" />
                <Setter Property="OverridesDefaultStyle" Value="true" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ToggleButton">
                            <Border x:Name="Border" Background="{StaticResource NormalBrush}">
                                <ContentPresenter />
                            </Border>
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsMouseOver" Value="true">
                                    <Setter TargetName="Border" Property="Background" Value="{StaticResource DarkBrush}" />
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>

            <Style TargetType="Expander">
                <Setter Property="SnapsToDevicePixels" Value="true" />
                <Setter Property="OverridesDefaultStyle" Value="true" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="Expander">
                            <DockPanel>
                                <ToggleButton
                                    HorizontalContentAlignment="Left"
                                    VerticalContentAlignment="Center"
                                    DockPanel.Dock="Top"
                                    IsChecked="{Binding Path=IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
                                    Style="{StaticResource ToggleButtonStyle}">
                                    <ToggleButton.Content>
                                        <Grid Margin="4">
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition Width="20" />
                                                <ColumnDefinition Width="*" />
                                            </Grid.ColumnDefinitions>
                                            <Path
                                                Name="Arrow"
                                                Grid.Column="0"
                                                HorizontalAlignment="Center"
                                                VerticalAlignment="Center"
                                                Data="M 0 0 L 0 8 L 5 4 Z"
                                                Fill="{TemplateBinding Foreground}"
                                                RenderTransformOrigin="0.5,0.5"
                                                SnapsToDevicePixels="True"
                                                Stroke="{TemplateBinding Foreground}"
                                                StrokeThickness="0.5">
                                                <Path.RenderTransform>
                                                    <RotateTransform Angle="0" />
                                                </Path.RenderTransform>
                                            </Path>
                                            <ContentPresenter
                                                Name="HeaderContent"
                                                Grid.Column="1"
                                                ContentSource="Header" />
                                        </Grid>
                                    </ToggleButton.Content>
                                </ToggleButton>
                                <Border Name="Content">
                                    <Border.LayoutTransform>
                                        <ScaleTransform ScaleY="0" />
                                    </Border.LayoutTransform>
                                    <ContentPresenter Content="{TemplateBinding Content}" />
                                </Border>
                            </DockPanel>
                            <ControlTemplate.Triggers>
                                <Trigger Property="Expander.IsExpanded" Value="True">
                                    <Trigger.EnterActions>
                                        <BeginStoryboard>
                                            <Storyboard>
                                                <DoubleAnimation
                                                    Storyboard.TargetName="Content"
                                                    Storyboard.TargetProperty="LayoutTransform.ScaleY"
                                                    To="1"
                                                    Duration="0:0:0.3" />
                                                <DoubleAnimation
                                                    Storyboard.TargetName="Content"
                                                    Storyboard.TargetProperty="Opacity"
                                                    To="1"
                                                    Duration="0:0:0.3" />
                                                <DoubleAnimation
                                                    DecelerationRatio="1"
                                                    Storyboard.TargetName="Arrow"
                                                    Storyboard.TargetProperty="(FrameworkElement.RenderTransform).(RotateTransform.Angle)"
                                                    To="90"
                                                    Duration="0:0:0.2" />
                                            </Storyboard>
                                        </BeginStoryboard>
                                    </Trigger.EnterActions>
                                    <Trigger.ExitActions>
                                        <BeginStoryboard>
                                            <Storyboard>
                                                <DoubleAnimation
                                                    Storyboard.TargetName="Content"
                                                    Storyboard.TargetProperty="LayoutTransform.ScaleY"
                                                    To="0"
                                                    Duration="0:0:0.3" />
                                                <DoubleAnimation
                                                    Storyboard.TargetName="Content"
                                                    Storyboard.TargetProperty="Opacity"
                                                    To="0"
                                                    Duration="0:0:0.3" />
                                                <DoubleAnimation
                                                    AccelerationRatio="1"
                                                    Storyboard.TargetName="Arrow"
                                                    Storyboard.TargetProperty="(FrameworkElement.RenderTransform).(RotateTransform.Angle)"
                                                    Duration="0:0:0.2" />
                                            </Storyboard>
                                        </BeginStoryboard>
                                    </Trigger.ExitActions>
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>

        </Style.Resources>

        <Setter Property="SnapsToDevicePixels" Value="true" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type control:Layers}">
                    <Border
                        Background="#EEE"
                        BorderBrush="DimGray"
                        BorderThickness="1"
                        CornerRadius="1">
                        <Expander Background="Transparent" IsExpanded="True">
                            <DockPanel>
                                <ItemsControl ItemsSource="{Binding Layers}">
                                    <ItemsControl.ItemTemplate>
                                        <DataTemplate>
                                            <Grid>
                                                <Grid.ColumnDefinitions>
                                                    <ColumnDefinition Width="50" />
                                                    <ColumnDefinition Width="200" />
                                                </Grid.ColumnDefinitions>
                                                <Button Grid.Column="0"
                                                        Command="{Binding SwitchVisibilityCommand}">
                                                    <Button.ContentTemplate>
                                                        <DataTemplate>
                                                            <Image Name="Eye" Source="{StaticResource Icon_Eye}" />
                                                            <DataTemplate.Triggers>
                                                                <DataTrigger Binding="{Binding IsVisible.Value}"
                                                                             Value="True">
                                                                    <Setter TargetName="Eye" Property="Source" Value="{StaticResource Icon_Eye}" />
                                                                </DataTrigger>
                                                                <DataTrigger Binding="{Binding IsVisible.Value}"
                                                                             Value="False">
                                                                    <Setter TargetName="Eye" Property="Source" Value="{StaticResource Icon_EyeSlash}" />
                                                                </DataTrigger>
                                                            </DataTemplate.Triggers>
                                                        </DataTemplate>
                                                    </Button.ContentTemplate>
                                                </Button>
                                            </Grid>
                                        </DataTemplate>
                                    </ItemsControl.ItemTemplate>
                                </ItemsControl>
                            </DockPanel>
                            <Expander.Header>
                                <Grid>
                                    <Label Content="レイヤー" />
                                </Grid>
                            </Expander.Header>
                        </Expander>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>
Layer.cs
using boilersGraphics.ViewModels;
using Prism.Mvvm;
using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Linq;
using System.Reactive.Disposables;
using System.Text;
using System.Threading.Tasks;

namespace boilersGraphics.Models
{
    public class Layer : BindableBase
    {
        private CompositeDisposable _disposable = new CompositeDisposable();
        public static ObservableCollection<Layer> SelectedLayers { get; } = new ObservableCollection<Layer>();

        public ReactivePropertySlim<bool> IsVisible { get; } = new ReactivePropertySlim<bool>();

        public ReactivePropertySlim<Bitmap> Appearance { get; } = new ReactivePropertySlim<Bitmap>();

        public ReactivePropertySlim<string> Name { get; } = new ReactivePropertySlim<string>();

        public ReactiveCommand SwitchVisibilityCommand { get; } = new ReactiveCommand();

        public ObservableCollection<SelectableDesignerItemViewModelBase> Items { get; } = new ObservableCollection<SelectableDesignerItemViewModelBase>();

        public IObservable<PropertyPack<SelectableDesignerItemViewModelBase, bool>> Observable
        {
            get { return Items.ObserveElementProperty(x => x.IsSelected); }
        }

        public Layer()
        {
            SwitchVisibilityCommand.Subscribe(_ =>
            {
                IsVisible.Value = !IsVisible.Value;
            })
            .AddTo(_disposable);
        }
    }
}

目のアイコンのボタンを押すと、IsVisible.Valueプロパティのboolが反転し、それに伴って目のアイコンが切り替わる(目に赤の斜線が入る)はずなのですが、DataTriggerが正しく機能していないのか、アイコンが切り替わりません。Visual Studioでデバッグ中に表示される「XAMLバインドエラー」ウィンドウにも何も表示されていないので、Bindingでプロパティ名を間違っているとか、.Valueを付け忘れているとかではないようです。
また、この目のボタンにはSwitchVisibilityCommandをBindingしていますが、このCommandをSubscribe()している行にブレークポイントを設置して、ボタンを押すと、ブレークポイントにヒットします。

eye_dont_switch.gif

この問題の原因がわかる方いらっしゃいますでしょうか。教えていただけると幸いです。

ソースコード

boiler's Graphics
https://github.com/dhq-boiler/boiler-s-Graphics

gitリポジトリ
https://github.com/dhq-boiler/boiler-s-Graphics.git

ブランチ:feature/Layer

コミット:c45434c

自分で試したこと

上記のページでDataTriggerをデバッグする方法があったので、実装して動作確認してみました。
しかし、TriggerTraceStoryboardにキャスト(as)するところで、nullに変換されて、その後のif文のnullチェックで弾かれてしまうようです。

TriggerTracing.cs

        /// <summary>
        /// A custom tracelistener.
        /// </summary>
        private class TriggerTraceListener : TraceListener
        {
            public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args)
            {
                base.TraceEvent(eventCache, source, eventType, id, format, args);

                if (format.StartsWith("Storyboard has begun;"))
                {
                    TriggerTraceStoryboard storyboard = args[1] as TriggerTraceStoryboard; //ここで必ずnullが返る
                    if (storyboard != null)
                    {
                        // add a breakpoint here to see when your trigger has been
                        // entered or exited

                        // the element being acted upon
                        object targetElement = args[5];

                        // the namescope of the element being acted upon
                        INameScope namescope = (INameScope)args[7];

                        TriggerBase triggerBase = storyboard.TriggerBase;
                        string triggerName = GetTriggerName(storyboard.TriggerBase);

                        Debug.WriteLine(string.Format("Element: {0}, {1}: {2}: {3}",
                            targetElement,
                            triggerBase.GetType().Name,
                            triggerName,
                            storyboard.StoryboardType));
                    }
                }
            }

            public override void Write(string message)
            {
            }

            public override void WriteLine(string message)
            {
            }
        }

Visual StudioのバージョンはMicrosoft Visual Studio Community 2019 Version 16.10.4です。

何か私の見落とし、致命的な勘違いなど気づいたところがあれば、回答していただけると助かります。よろしくお願いいたします。

0

2Answer

Layers.xamlを下記のように変更したところ、出力ウィンドウ(デバッグ)にWarningが出てました。

Layers.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:control="clr-namespace:boilersGraphics.Controls"
                    xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
                    xmlns:helper="clr-namespace:boilersGraphics.Helpers"
                    xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase">
:

        <Setter Property="SnapsToDevicePixels" Value="true" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type control:Layers}">
                    <Border
                        Background="#EEE"
                        BorderBrush="DimGray"
                        BorderThickness="1"
                        CornerRadius="1">
                        <Expander Background="Transparent" IsExpanded="True">
                            <DockPanel>
                                <ItemsControl ItemsSource="{Binding Layers}">
                                    <ItemsControl.ItemTemplate>
                                        <DataTemplate>
                                            <Grid>
                                                <Grid.ColumnDefinitions>
                                                    <ColumnDefinition Width="50" />
                                                    <ColumnDefinition Width="200" />
                                                </Grid.ColumnDefinitions>
                                                <Button Grid.Column="0"
                                                        Command="{Binding SwitchVisibilityCommand}">
                                                    <Button.ContentTemplate>
                                                        <DataTemplate>
                                                            <Image Name="Eye" Source="{StaticResource Icon_Eye}" />
                                                            <DataTemplate.Triggers>
                                                                <DataTrigger Binding="{Binding IsVisible.Value, diag:PresentationTraceSources.TraceLevel=High}" <!--ここ-->
                                                                             Value="True">
                                                                    <Setter TargetName="Eye" Property="Source" Value="{StaticResource Icon_Eye}" />
                                                                </DataTrigger>
                                                                <DataTrigger Binding="{Binding IsVisible.Value, diag:PresentationTraceSources.TraceLevel=High}" <!--ここ-->
                                                                             Value="False">
                                                                    <Setter TargetName="Eye" Property="Source" Value="{StaticResource Icon_EyeSlash}" />
                                                                </DataTrigger>
                                                            </DataTemplate.Triggers>
                                                        </DataTemplate>
                                                    </Button.ContentTemplate>
                                                </Button>
                                            </Grid>
                                        </DataTemplate>
                                    </ItemsControl.ItemTemplate>
                                </ItemsControl>
                            </DockPanel>
                            <Expander.Header>
                                <Grid>
                                    <Label Content="レイヤー" />
                                </Grid>
                            </Expander.Header>
                        </Expander>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>
System.Windows.Data Warning: 56 : Created BindingExpression (hash=43901854) for Binding (hash=45587811)
System.Windows.Data Warning: 58 :   Path: 'IsVisible.Value'
System.Windows.Data Warning: 60 : BindingExpression (hash=43901854): Default mode resolved to OneWay
System.Windows.Data Warning: 61 : BindingExpression (hash=43901854): Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 62 : BindingExpression (hash=43901854): Attach to System.Windows.Controls.ContentPresenter.NoTarget (hash=59572368)
System.Windows.Data Warning: 67 : BindingExpression (hash=43901854): Resolving source 
System.Windows.Data Warning: 70 : BindingExpression (hash=43901854): Found data context element: ContentPresenter (hash=59572368) (OK)
System.Windows.Data Warning: 78 : BindingExpression (hash=43901854): Activate with root item <null>
System.Windows.Data Warning: 106 : BindingExpression (hash=43901854):   Item at level 0 is null - no accessor
System.Windows.Data Warning: 103 : BindingExpression (hash=43901854): Replace item at level 1 with {NullDataItem}
System.Windows.Data Warning: 80 : BindingExpression (hash=43901854): TransferValue - got raw value {DependencyProperty.UnsetValue}
System.Windows.Data Warning: 88 : BindingExpression (hash=43901854): TransferValue - using fallback/default value <null>
System.Windows.Data Warning: 89 : BindingExpression (hash=43901854): TransferValue - using final value <null>
System.Windows.Data Warning: 56 : Created BindingExpression (hash=66389266) for Binding (hash=49957660)
System.Windows.Data Warning: 58 :   Path: 'IsVisible.Value'
System.Windows.Data Warning: 60 : BindingExpression (hash=66389266): Default mode resolved to OneWay
System.Windows.Data Warning: 61 : BindingExpression (hash=66389266): Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 62 : BindingExpression (hash=66389266): Attach to System.Windows.Controls.ContentPresenter.NoTarget (hash=59572368)
System.Windows.Data Warning: 67 : BindingExpression (hash=66389266): Resolving source 
System.Windows.Data Warning: 70 : BindingExpression (hash=66389266): Found data context element: ContentPresenter (hash=59572368) (OK)
System.Windows.Data Warning: 78 : BindingExpression (hash=66389266): Activate with root item <null>
System.Windows.Data Warning: 106 : BindingExpression (hash=66389266):   Item at level 0 is null - no accessor
System.Windows.Data Warning: 103 : BindingExpression (hash=66389266): Replace item at level 1 with {NullDataItem}
System.Windows.Data Warning: 80 : BindingExpression (hash=66389266): TransferValue - got raw value {DependencyProperty.UnsetValue}
System.Windows.Data Warning: 88 : BindingExpression (hash=66389266): TransferValue - using fallback/default value <null>
System.Windows.Data Warning: 89 : BindingExpression (hash=66389266): TransferValue - using final value <null>
0Like

自己解決しました。

Layers.xamlを以下のように変更したところ、正しく動作するようになりました。
Button.TemplateにControlTemplateを設定してやれば、そのControlTemplateのTriggersでDataTriggerを定義できました。DataContextもLayerインスタンスが正しく伝播しました。
Button.ContentTemplateにDataTemplateを設定するやり方は間違いだったようです。

Layers.xaml
<Setter Property="SnapsToDevicePixels" Value="true" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type control:Layers}">
                    <Border
                        Background="#EEE"
                        BorderBrush="DimGray"
                        BorderThickness="1"
                        CornerRadius="1">
                        <Expander Background="Transparent" IsExpanded="True">
                            <DockPanel>
                                <ItemsControl ItemsSource="{Binding Layers}">
                                    <ItemsControl.ItemTemplate>
                                        <DataTemplate>
                                            <Grid>
                                                <Grid.ColumnDefinitions>
                                                    <ColumnDefinition Width="50" />
                                                    <ColumnDefinition Width="200" />
                                                </Grid.ColumnDefinitions>
                                                <Button Grid.Column="0"
                                                        Command="{Binding SwitchVisibilityCommand}">
                                                    <Button.Template>
                                                        <ControlTemplate TargetType="{x:Type Button}">
                                                            <Image Name="Eye" Source="{StaticResource Icon_Eye}" />
                                                            <ControlTemplate.Triggers>
                                                                <DataTrigger Binding="{Binding IsVisible.Value}" Value="True">
                                                                    <Setter TargetName="Eye" Property="Source" Value="{StaticResource Icon_Eye}" />
                                                                </DataTrigger>
                                                                <DataTrigger Binding="{Binding IsVisible.Value}" Value="False">
                                                                    <Setter TargetName="Eye" Property="Source" Value="{StaticResource Icon_EyeSlash}" />
                                                                </DataTrigger>
                                                            </ControlTemplate.Triggers>
                                                        </ControlTemplate>
                                                    </Button.Template>
                                                </Button>
                                            </Grid>
                                        </DataTemplate>
                                    </ItemsControl.ItemTemplate>
                                </ItemsControl>
                            </DockPanel>
                            <Expander.Header>
                                <Grid>
                                    <Label Content="レイヤー" />
                                </Grid>
                            </Expander.Header>
                        </Expander>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>

eye_can_be_switched.gif

0Like

Your answer might help someone💌