template-generated object 与其 parent 的 属性 之间的绑定
Binding between a template-generated object and its parent's property
这个问题的标题可能是错误的,我不知道如何表述。我正在尝试实现一个非常简单的仪表板,用户可以在其中拖动控件到 Canvas
控件内。我写了一个继承Thumb
的MoveThumb
class来实现。它运行良好。现在,我想确保用户无法将可拖动控件移到 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 更改大小,拖动边界将保持不变。理想情况下,我想将 canvasWidth
和 canvasHeight
绑定到 Canvas 的 actualWidth
和 actualHeight
。
我不确定实现它的最佳方法是什么。使用类似 actualWidth = mainWindow.Canvas1.ActualWidth
的方法在 MoveThumb_DragDelta
函数中获取 actualWidth
和 actualHeight
值将是快速和简单的,但编码实践非常糟糕。
理想情况下,我认为最好将限制作为参数传递给 MoveThumb
的构造函数并存储为全局 field/property,但我看不到这样做的方法,因为此 class 用作 XML 代码中的模板,而不是从 code-behind 生成的。我不确定这是否会起作用,因为 MoveThumb
可能只实例化一次(在创建控件期间),所以当 Canvas
之后改变它的大小时它不会起作用.
所以我可能应该在 Canvas1
的 actualWidth
和 canvasWidth
(声明为全局 属性)之间做某种 one-way 绑定=17=]。但同样,我不知道如何访问它,因为 MoveThumb
在 Canvas.Resources
.
中用作 ControlTemplate
的 TargetType
我对 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>
这个问题的标题可能是错误的,我不知道如何表述。我正在尝试实现一个非常简单的仪表板,用户可以在其中拖动控件到 Canvas
控件内。我写了一个继承Thumb
的MoveThumb
class来实现。它运行良好。现在,我想确保用户无法将可拖动控件移到 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 更改大小,拖动边界将保持不变。理想情况下,我想将 canvasWidth
和 canvasHeight
绑定到 Canvas 的 actualWidth
和 actualHeight
。
我不确定实现它的最佳方法是什么。使用类似 actualWidth = mainWindow.Canvas1.ActualWidth
的方法在 MoveThumb_DragDelta
函数中获取 actualWidth
和 actualHeight
值将是快速和简单的,但编码实践非常糟糕。
理想情况下,我认为最好将限制作为参数传递给 MoveThumb
的构造函数并存储为全局 field/property,但我看不到这样做的方法,因为此 class 用作 XML 代码中的模板,而不是从 code-behind 生成的。我不确定这是否会起作用,因为 MoveThumb
可能只实例化一次(在创建控件期间),所以当 Canvas
之后改变它的大小时它不会起作用.
所以我可能应该在 Canvas1
的 actualWidth
和 canvasWidth
(声明为全局 属性)之间做某种 one-way 绑定=17=]。但同样,我不知道如何访问它,因为 MoveThumb
在 Canvas.Resources
.
ControlTemplate
的 TargetType
我对 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>