WPF - How to set a dependency property of a control from a nested templated control's trigger

CB 9 1 Reputation point
2021-03-11T16:24:24.937+00:00

I have a subclassed ContentControl (MySpecialContentControl) and added to it a DP called "mySpecialDP". In this control's Style template, there are a few other controls in the templated "ResizeDecorator" (also subclassed controls), like so:

    <ControlTemplate x:Key="ResizeDecoratorTemplate" TargetType="{x:Type Control}">
        <Grid>
            <controls:ResizeThumb Height="1" Margin="0,-1,0,0"/>
            <controls:ResizeThumb Width="1" Margin="-1,0,0,0"/>
        </Grid>

        <ControlTemplate.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="mySpecialDP" Value="X"/>
            </Trigger>
        </ControlTemplate.Triggers>

    </ControlTemplate>

    <Style x:Key="ControlStyle" TargetType="{x:Type controls:MySpecialContentControl}">
        <Setter Property="mySpecialDP" Value="Y"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type controls:MySpecialContentControl}">
                    <Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
                        <Control x:Name="ResizeDecorator" Template="{StaticResource ResizeDecoratorTemplate}" Visibility="Collapsed"/>
                        <ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="Selector.IsSelected" Value="True">
                            <Setter TargetName="ResizeDecorator" Property="Visibility" Value="Visible"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

I'm trying to find a way to set the "mySpecialDP" property from the IsMouseOver trigger of the templated control (ResizeDecoratorTemplate) - line 9 above. Can the ResizeDecoratorTemplate know about his "templating parent" (controls:MySpecialContentControl) ?

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,694 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
778 questions
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. CB 9 1 Reputation point
    2021-03-11T16:45:11.093+00:00

    MSDN is not letting me update the question -- So adding the use case like this.

    The reason for doing this is that the content control is part of a Canvas, and I need to know at the time the MouseLeftButtonDown or MouseMove are being called if the user clicked somewhere inside the Canvas, or particularly on the ResizeThumb control. My idea was to use an enum to distinguish.

    <ControlTemplate x:Key="ResizeDecoratorTemplate" TargetType="{x:Type Control}">
            <Grid>
                <controls:ResizeThumb Height="1" Margin="0,-1,0,0"/>
                <controls:ResizeThumb Width="1" Margin="-1,0,0,0"/>
            </Grid>
    
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="controls:MySpecialContentControl.BoxDragMode" Value="RESIZE"/>
                </Trigger>
            </ControlTemplate.Triggers>
    
        </ControlTemplate>
    
    
        <Canvas Margin="0,0,-4,-4" Visibility="Collapsed" 
                PreviewMouseLeftButtonDown="cnvs_PreviewMouseLeftButtonDown"
                PreviewMouseMove="cnvs_PreviewMouseMove">
            <controls:MySpecialContentControl
                    BoxDragMode="NONE"
                    Width="130"
                    Height="130"
                    Canvas.Top="125"
                    Canvas.Left="330"
                    Selector.IsSelected="True"
                    Style="{StaticResource ControlStyle}"
                    SizeChanged="_SizeChanged">
                <Ellipse Fill="#3321A7FB" Stretch="Fill" IsHitTestVisible="False"/>
            </controls:MySpecialContentControl>
        </Canvas> 
    

  2. CB 9 1 Reputation point
    2021-03-13T02:01:53.477+00:00

    Apologies for the confusing post .. This thing is a transparent bounding box that sits on top of an image, and has 4 corners where the user can resize the box (like the crop ones in image apps).

    The ellipse is not in play here - even though it is inside the Canvas and is the content of the ST_BoundingBoxContentControl, I need to know when the user is resizing (IE clicked the ResizeThumb control), as opposed to just move the whole box around clicking anywhere inside the box (which I use cnvs_BoundingBox_PreviewMouseMove and cnvs_BoundingBox_PreviewMouseLeftButtonDown). Since the thumbs are part of ST_BoundingBoxContentControl which is under the Canvas, anywhere I click (including the resize thumb) will trigger the cnvs_BoundingBox_PreviewMouseLeftButtonDown event. So I was hoping to write something like this in the cnvs_BoundingBox_PreviewMouseLeftButtonDown handler:

    if (cBox.BoxDragMode != Controls.enmBoxDragMode.NONE) return;
    

    and change the BoxDragMode in the IsMouseOver of the ResizeThumb.

    Yes, BoxDragMode is an enum.

    Here is the XAML:

            <Style x:Key="ResizeThumbStyle1" TargetType="{x:Type controls:ResizeThumb}">
                <Setter Property="Stylus.IsPressAndHoldEnabled" Value="False"/>
                <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type controls:ResizeThumb}">
                            <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
                                <Grid>
                                    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="0,0,1,1" Background="{TemplateBinding Background}"/>
                                    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="0,0,1,1" Background="{TemplateBinding Background}" Margin="1"/>
                                    <Border Background="{TemplateBinding Background}" Margin="2"/>
                                </Grid>
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
    
            <ControlTemplate x:Key="ResizeDecoratorTemplate" TargetType="{x:Type Control}">
                <Grid>
                    <controls:ResizeThumb Height="1" Cursor="SizeNS" Margin="0,-1,0,0"
                           VerticalAlignment="Top" HorizontalAlignment="Stretch" Background="#FF21A7FB" Style="{DynamicResource ResizeThumbStyle1}"/>
                    <controls:ResizeThumb Width="1" Cursor="SizeWE" Margin="-1,0,0,0"
                           VerticalAlignment="Stretch" HorizontalAlignment="Left" Style="{DynamicResource ResizeThumbStyle1}" Background="#FF21A7FB"/>
                    <controls:ResizeThumb Width="1" Cursor="SizeWE" Margin="0,0,-1,0"
                           VerticalAlignment="Stretch" HorizontalAlignment="Right" Style="{DynamicResource ResizeThumbStyle1}" Background="#FF21A7FB"/>
                    <controls:ResizeThumb Height="1" Cursor="SizeNS" Margin="0,0,0,-1"
                           VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Style="{DynamicResource ResizeThumbStyle1}" Background="#FF21A7FB"/>
                    <controls:ResizeThumb Width="8" Height="8" Cursor="{Binding Tag, Converter={StaticResource CursorConverter}, RelativeSource={RelativeSource Self}}" Margin="-6 -6 0 0"
                           VerticalAlignment="Top" HorizontalAlignment="Left" Background="White" BorderBrush="#FF21A7FB" Style="{DynamicResource ResizeThumbStyle2}" Tag="NWSE"/>
                    <controls:ResizeThumb Width="8" Height="8" Cursor="{Binding Tag, Converter={StaticResource CursorConverter}, RelativeSource={RelativeSource Self}}" Margin="0 -6 -6 0"
                           VerticalAlignment="Top" HorizontalAlignment="Right" Style="{DynamicResource ResizeThumbStyle2}" BorderBrush="#FF21A7FB" Background="White" Tag="NESW"/>
                    <controls:ResizeThumb Width="8" Height="8" Cursor="{Binding Tag, Converter={StaticResource CursorConverter}, RelativeSource={RelativeSource Self}}" Margin="-6 0 0 -6"
                           VerticalAlignment="Bottom" HorizontalAlignment="Left" Style="{DynamicResource ResizeThumbStyle2}" BorderBrush="#FF21A7FB" Background="White" Tag="NESW"/>
                    <controls:ResizeThumb Width="8" Height="8" Cursor="{Binding Tag, Converter={StaticResource CursorConverter}, RelativeSource={RelativeSource Self}}" Margin="0 0 -6 -6"
                           VerticalAlignment="Bottom" HorizontalAlignment="Right" Style="{DynamicResource ResizeThumbStyle2}" BorderBrush="#FF21A7FB" Background="White" Tag="NWSE"/>
                </Grid>
    
                <ControlTemplate.Triggers>
                     <Trigger Property="IsMouseOver" Value="True">
                        <!--This is what I need-->
                        <!--<Setter Property="controls:ST_BoundingBoxContentControl.BoxDragMode" Value="RESIZE"/>-->
                    </Trigger>
                </ControlTemplate.Triggers>           
    
            </ControlTemplate>
    
            <Style x:Key="DesignerItemStyle" TargetType="{x:Type controls:ST_BoundingBoxContentControl}">
                <Setter Property="MinHeight" Value="5"/>
                <Setter Property="MinWidth" Value="5"/>
                <Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
                <Setter Property="SnapsToDevicePixels" Value="true"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type controls:ST_BoundingBoxContentControl}">
                            <Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
                                <controls:MoveThumb Template="{StaticResource MoveThumbTemplate}" Cursor="SizeAll"/>
                                <Control x:Name="ResizeDecorator" Template="{StaticResource ResizeDecoratorTemplate}" Visibility="Collapsed"/>
                                <ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
                            </Grid>
                            <ControlTemplate.Triggers>
                                <Trigger Property="Selector.IsSelected" Value="True">
                                    <Setter TargetName="ResizeDecorator" Property="Visibility" Value="Visible"/>
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
    
            <!--The grid that has the image and the bounding box on top of it-->
            <Grid x:Name="gr_ImageGrid" Width="507" Height="366" VerticalAlignment="Center" HorizontalAlignment="Center">
    
                <Image x:Name="img_Image" Width="507" Height="366" Stretch="Uniform"
                       MouseMove="img_Image_MouseMove"
                       MouseLeave="img_Image_MouseLeave"
                       MouseWheel="img_Image_MouseWheel"
                       MouseLeftButtonDown="img_Image_MouseLeftButtonDown"
                       MouseLeftButtonUp="img_Image_MouseLeftButtonUp"/>
    
                <Canvas x:Name="cnvs_CBoundingBox" Margin="0,0,-4,-4" Visibility="Collapsed" 
                        PreviewMouseLeftButtonDown="cnvs_BoundingBox_PreviewMouseLeftButtonDown"
                        PreviewMouseMove="cnvs_BoundingBox_PreviewMouseMove"
                        PreviewMouseUp="cnvs_BoundingBox_PreviewMouseUp">
                    <controls:ST_BoundingBoxContentControl 
                            x:Name="cBox"
                            BoxDragMode="NONE"
                            Width="130"
                            Height="130"
                            Canvas.Top="125"
                            Canvas.Left="330"
                            Selector.IsSelected="True"
                            Style="{StaticResource DesignerItemStyle}"
                            SizeChanged="cBox_SizeChanged">
                        <Ellipse Fill="#3321A7FB" Stretch="Fill" IsHitTestVisible="False"/>
                    </controls:ST_BoundingBoxContentControl>
                </Canvas>
            </Grid>
    

    And the C# code:

        public enum enmBoxDragMode
        {
            NONE, 
            MOVE,
            RESIZE,
            ROTATE
        }
    
        public class ST_BoundingBoxContentControl : ContentControl
        {
            public static readonly DependencyProperty BoxDragModeProperty =
                DependencyProperty.Register(
                    "BoxDragMode",
                    typeof(enmBoxDragMode),
                    typeof(ST_BoundingBoxContentControl),
                    new PropertyMetadata(enmBoxDragMode.NONE));
    
            public enmBoxDragMode BoxDragMode
            {
                get => (enmBoxDragMode)GetValue(BoxDragModeProperty);
                set => SetValue(BoxDragModeProperty, value);
            }
        }
    
        public class ResizeThumb : Thumb
        {
            public ResizeThumb()
            {
                DragDelta += new DragDeltaEventHandler(this.ResizeThumb_DragDelta);
            }
    
            private void ResizeThumb_DragDelta(object sender, DragDeltaEventArgs e)
            {
                Control designerItem = this.DataContext as Control;
    
                if (designerItem != null)
                {
                    double deltaVertical, deltaHorizontal;
    
                    switch (VerticalAlignment)
                    {
                        case VerticalAlignment.Bottom:
                            deltaVertical = Math.Min(-e.VerticalChange, designerItem.ActualHeight - designerItem.MinHeight);
                            designerItem.Height -= deltaVertical;
                            break;
                        case VerticalAlignment.Top:
                            deltaVertical = Math.Min(e.VerticalChange, designerItem.ActualHeight - designerItem.MinHeight);
                            Canvas.SetTop(designerItem, Canvas.GetTop(designerItem) + deltaVertical);
                            designerItem.Height -= deltaVertical;
                            break;
                        default:
                            break;
                    }
    
                    switch (HorizontalAlignment)
                    {
                        case HorizontalAlignment.Left:
                            deltaHorizontal = Math.Min(e.HorizontalChange, designerItem.ActualWidth - designerItem.MinWidth);
                            Canvas.SetLeft(designerItem, Canvas.GetLeft(designerItem) + deltaHorizontal);
                            designerItem.Width -= deltaHorizontal;
                            break;
                        case HorizontalAlignment.Right:
                            deltaHorizontal = Math.Min(-e.HorizontalChange, designerItem.ActualWidth - designerItem.MinWidth);
                            designerItem.Width -= deltaHorizontal;
                            break;
                        default:
                            break;
                    }
                }
    
                e.Handled = true;
            }
        }
    

    Since I have 4 of these canvases on my page, I was hoping to set the BoxDragMode in the Template (line 43 above) so that each box will have its own.

    Thanks for looking.