The title of this question might be wrong, I am not sure how to phrase it. I am trying to implement a very simple dashboard in which users can drag controls around inside a Canvas
control. I wrote a MoveThumb
class that inherits Thumb
to achieve this. It is working well enough. Now, I want to make sure that the user cannot move the draggable control outside the Canvas
. It is simple enough to write the logic itself to limit the drag boundaries inside this 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
And the 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>
Problem is, I am explicitly stating the width and height of the canvas inside that MoveThumb
class, which means that if my window changes size, and the Canvas changes size, the drag boundaries will remain the same. Ideally, I want to bind canvasWidth
and canvasHeight
to the actualWidth
and actualHeight
of the Canvas.
I am not sure what is the best way to achieve it. Acquiring the actualWidth
and actualHeight
values inside the MoveThumb_DragDelta
function with something like actualWidth = mainWindow.Canvas1.ActualWidth
would be quick and simple, but very bad coding practice.
Ideally, I imagine it would be best to pass the limits as arguments to the constructor of MoveThumb
and stored as global field/property, but I don't see a way to do it, since this class is used as a template in the XML code, and not generated from code-behind. I am not exactly sure if that would work at all, because the MoveThumb
might be instantized only once (during the creation of the control), so it won't work when the Canvas
changes it's size afterwards.
So I probably should do some kind of one-way binding between the actualWidth
of Canvas1
and canvasWidth
(declared as global property) of MoveThumb
. But again, I have no idea how to access it, since MoveThumb
is used as a TargetType
of ControlTemplate
inside Canvas.Resources
.
I am still pretty new to WPF, and it feels like there should be some very simple way to achieve this, but I'm not seeing it. Can anyone help?
CodePudding user response:
Example using Dependency Properties:
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));
}
In the Project add the theme "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>