template-generated object 与其 parent 的 属性 之间的绑定

Binding between a template-generated object and its parent's property

这个问题的标题可能是错误的,我不知道如何表述。我正在尝试实现一个非常简单的仪表板,用户可以在其中拖动控件到 Canvas 控件内。我写了一个继承ThumbMoveThumbclass来实现。它运行良好。现在,我想确保用户无法将可拖动控件移到 Canvas 之外。编写逻辑本身以限制此 MoveThumb class:

内的拖动边界非常简单
Public Class MoveThumb
    Inherits Thumb

    Public Sub New()
        AddHandler DragDelta, New DragDeltaEventHandler(AddressOf Me.MoveThumb_DragDelta)
    End Sub

    Private Sub MoveThumb_DragDelta(ByVal sender As Object, ByVal e As DragDeltaEventArgs)
        Dim item As Control = TryCast(Me.DataContext, Control)

        If item IsNot Nothing Then
            Dim left As Double = Canvas.GetLeft(item)
            Dim top As Double = Canvas.GetTop(item)
            Dim right As Double = left + item.ActualWidth
            Dim bottom As Double = top + item.ActualHeight

            Dim canvasWidth = 450
            Dim canvasHeight = 800

            If left + e.HorizontalChange > 0 Then
                If top + e.VerticalChange > 0 Then
                    If right + e.HorizontalChange < canvasWidth Then
                        If bottom + e.VerticalChange > canvasHeight Then
                            Canvas.SetLeft(item, left + e.HorizontalChange)
                            Canvas.SetTop(item, top + e.VerticalChange)
                        End If
                    End If
                End If
            End If
        End If
    End Sub
End Class

和XML:

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:diagramDesigner"
        xmlns:s="clr-namespace:diagramDesigner"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Canvas x:Name="Canvas1">
            <Canvas.Resources>
                <ControlTemplate x:Key="MoveThumbTemplate" TargetType="{x:Type s:MoveThumb}">
                    <Rectangle Fill="Transparent"/>
                </ControlTemplate>
                
                <ControlTemplate x:Key="DesignerItemTemplate" TargetType="ContentControl">
                    <Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
                        <s:MoveThumb Template="{StaticResource MoveThumbTemplate}" Cursor="SizeAll"/>
                        <ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
                    </Grid>
                </ControlTemplate>

            </Canvas.Resources>
            
            <ContentControl Name="DesignerItem"
                   Width="100"
                   Height="100"
                   Canvas.Top="100"
                   Canvas.Left="100"
                   Template="{StaticResource DesignerItemTemplate}">
                <Ellipse Fill="Blue" IsHitTestVisible="False"/>
            </ContentControl>
        </Canvas>
    </Grid>
</Window>

问题是,我在 MoveThumb class 中明确说明 canvas 的宽度和高度,这意味着如果我的 window 改变大小,并且Canvas 更改大小,拖动边界将保持不变。理想情况下,我想将 canvasWidthcanvasHeight 绑定到 Canvas 的 actualWidthactualHeight

我不确定实现它的最佳方法是什么。使用类似 actualWidth = mainWindow.Canvas1.ActualWidth 的方法在 MoveThumb_DragDelta 函数中获取 actualWidthactualHeight 值将是快速和简单的,但编码实践非常糟糕。

理想情况下,我认为最好将限制作为参数传递给 MoveThumb 的构造函数并存储为全局 field/property,但我看不到这样做的方法,因为此 class 用作 XML 代码中的模板,而不是从 code-behind 生成的。我不确定这是否会起作用,因为 MoveThumb 可能只实例化一次(在创建控件期间),所以当 Canvas 之后改变它的大小时它不会起作用.

所以我可能应该在 Canvas1actualWidthcanvasWidth(声明为全局 属性)之间做某种 one-way 绑定=17=]。但同样,我不知道如何访问它,因为 MoveThumbCanvas.Resources.

中用作 ControlTemplateTargetType

我对 WPF 还是很陌生,感觉应该有一些非常简单的方法来实现这一点,但我没有看到。有人可以帮忙吗?

使用依赖属性的示例:

public partial class MoveThumb : Thumb
{
    private double privateCanvasWidth = double.NaN, privateCanvasHeight = double.NaN;
    private static readonly Binding bindingActualWidth = new Binding()
    {
        Path = new PropertyPath(ActualWidthProperty),
        RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Canvas), 1)
    };
    private static readonly Binding bindingActualHeight = new Binding()
    {
        Path = new PropertyPath(ActualHeightProperty),
        RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Canvas), 1)
    };

    public MoveThumb()
    {
        DragDelta += MoveThumb_DragDelta;
        SetBinding(CanvasWidthProperty, bindingActualWidth);
        SetBinding(CanvasHeightProperty, bindingActualHeight);
    }

    static MoveThumb()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(MoveThumb), new FrameworkPropertyMetadata(typeof(MoveThumb)));
    }
    private static void MoveThumb_DragDelta(object sender, DragDeltaEventArgs e)
    {
        MoveThumb thumb = (MoveThumb)sender;
        //FrameworkElement item = thumb.MovableContent;
        //if (item == null)
        //{
        //    return;
        //}

        double left = Canvas.GetLeft(thumb);
        double top = Canvas.GetTop(thumb);
        double right = left + thumb.ActualWidth;
        double bottom = top + thumb.ActualHeight;

        double canvasWidth = thumb.privateCanvasWidth;
        if (double.IsNaN(canvasWidth))
            canvasWidth = 450;
        double canvasHeight = thumb.privateCanvasHeight;
        if (double.IsNaN(canvasHeight))
            canvasWidth = 800;

        left += e.HorizontalChange;
        top += e.VerticalChange;
        right += e.HorizontalChange;
        bottom += e.VerticalChange;

        if (left > 0 &&
            top > 0 &&
            right < canvasWidth &&
            bottom < canvasHeight)
        {
            Canvas.SetLeft(thumb, left);
            Canvas.SetTop(thumb, top);
        }
    }
}
// DependecyProperties
[ContentProperty(nameof(MovableContent))]
public partial class MoveThumb
{

    /// <summary>Canvas Width.</summary>
    public double CanvasWidth
    {
        get => (double)GetValue(CanvasWidthProperty);
        set => SetValue(CanvasWidthProperty, value);
    }

    /// <summary><see cref="DependencyProperty"/> for property <see cref="CanvasWidth"/>.</summary>
    public static readonly DependencyProperty CanvasWidthProperty =
        DependencyProperty.Register(nameof(CanvasWidth), typeof(double), typeof(MoveThumb), new PropertyMetadata(double.NaN, CanvasSizeChanged));


    /// <summary>Canvas Height.</summary>
    public double CanvasHeight
    {
        get => (double)GetValue(CanvasHeightProperty);
        set => SetValue(CanvasHeightProperty, value);
    }

    /// <summary><see cref="DependencyProperty"/> for property <see cref="CanvasHeight"/>.</summary>
    public static readonly DependencyProperty CanvasHeightProperty =
        DependencyProperty.Register(nameof(CanvasHeight), typeof(double), typeof(MoveThumb), new PropertyMetadata(double.NaN, CanvasSizeChanged));

    // Property change handler.
    // The code is shown as an example.
    private static void CanvasSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        MoveThumb thumb = (MoveThumb)d;

        if (e.Property == CanvasWidthProperty)
        {
            thumb.privateCanvasWidth = (double)e.NewValue;
        }
        else if (e.Property == CanvasHeightProperty)
        {
            thumb.privateCanvasHeight = (double)e.NewValue;
        }
        else
        {
            throw new Exception("God knows what happened!");
        }

        MoveThumb_DragDelta(thumb, new DragDeltaEventArgs(0, 0));
    }


    /// <summary>Movable content.</summary>
    public FrameworkElement MovableContent
    {
        get => (FrameworkElement)GetValue(MovableContentProperty);
        set => SetValue(MovableContentProperty, value);
    }

    /// <summary><see cref="DependencyProperty"/> for property <see cref="MovableContent"/>.</summary>
    public static readonly DependencyProperty MovableContentProperty =
        DependencyProperty.Register(nameof(MovableContent), typeof(FrameworkElement), typeof(MoveThumb), new PropertyMetadata(null));


}

在项目中添加主题“Themes\Generic。xaml”:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:customcontrols="clr-namespace:EmbedContent.CustomControls"
    xmlns:s="clr-namespace:Febr20y">

    <Style TargetType="{x:Type s:MoveThumb}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type s:MoveThumb}">
                    <ContentPresenter Content="{TemplateBinding MovableContent}"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>
    <Grid>
        <Canvas x:Name="Canvas1">
            <s:MoveThumb x:Name="DesignerItem"
                   Width="100"
                   Height="100"
                   Canvas.Top="100"
                   Canvas.Left="100">
                <Ellipse Fill="Blue"/>
            </s:MoveThumb>
      </Canvas>
    </Grid>

Video YouTube
Source Code