I have a ToggleButton
that I have set the template to a new ControlTemplate
I have a behavior that I have added to this ToggleButton
.
<ControlTemplate TargetType="ToggleButton" x:Key="IconToggleButton">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="15 0 0 15"
Height="{TemplateBinding Height}"
MinHeight="{TemplateBinding MinHeight}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Width="{TemplateBinding Width}">
<Grid>
<TextBlock
FontFamily="{TemplateBinding FontFamily}"
Foreground="{StaticResource White}"
HorizontalAlignment="Center"
Text="{TemplateBinding Content}"
VerticalAlignment="Center" />
</Grid>
</Border>
</ControlTemplate>
<ToggleButton
Background="{StaticResource LightGray}"
BorderBrush="{StaticResource LightGray}"
BorderThickness="1"
Content="{x:Static constants:FontAwesomeIcons.Bars}"
FontFamily="{StaticResource FontAwesomeSolid}"
FontSize="19"
HorizontalAlignment="Center"
MinHeight="35"
MouseLeftButtonDown="UIElement_OnMouseDown"
Template="{StaticResource IconToggleButton}"
VerticalAlignment="Top"
Width="38"
x:Name="ToggleButton">
<b:Interaction.Behaviors>
<b:MouseDragElementBehavior ConstrainToParentBounds="True" />
</b:Interaction.Behaviors>
</ToggleButton>
private void UIElement_OnMouseDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine(sender.GetType().Name);
e.Handled = false;
}
My issue is that because the ToggleButton
is handling the event The behavior actually never fires.
So, on to my question: How can I get the behavior to catch this MouseLeftButtonDown
Event.
The MouseDragElementBehavior
does handle the bubbling version of the mouse input events. And those events are marked as handled by the Button
as those events are replaced with the Button.Click
event. This means the UIElement.MouseLeftButtonDown
and the UIElement.MouseDown
events are swallowed by the Button. Only the tunneling versions of those events are allowed to traverse.
To enable drag with the help of the MouseDragElementBehavior
for a Button
, or any control that marks the bubbling mouse events as handled, you must control this behavior manually.
To achieve this, register a handler for the tunneling UIElement.PreviewMouseLeftButtonDown
event on the draggable element:
<ToggleButton PreviewMouseLeftButtonDown="OnDraggableElementLeftButtonDown">
<b:Interaction.Behaviors>
<b:MouseDragElementBehavior ConstrainToParentBounds="True" />
</b:Interaction.Behaviors>
</ToggleButton>
private Point DragStartPosition { get; set; }
private void OnDraggableElementLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var draggableElement = sender as UIElement;
this.DragStartPosition = e.GetPosition(this);
draggableElement.PreviewMouseMove += OnDraggingElementMouseMove;
draggableElement.PreviewMouseLeftButtonUp += OnDraggedElementLeftButtonUp;
}
protected void OnDraggingElementMouseMove(object sender, MouseEventArgs e)
{
var draggingElement = sender as UIElement;
Microsoft.Xaml.Behaviors.Layout.MouseDragElementBehavior dragMoveBehavior = Microsoft.Xaml.Behaviors.Interaction.GetBehaviors(draggingElement)
.OfType<Microsoft.Xaml.Behaviors.Layout.MouseDragElementBehavior>()
.First();
Point initialDraggableElementPosition = draggingElement.TranslatePoint(new Point(0, 0), this);
if (double.IsNaN(dragMoveBehavior.X))
{
dragMoveBehavior.X = initialDraggableElementPosition.X;
}
if (double.IsNaN(dragMoveBehavior.Y))
{
dragMoveBehavior.Y = initialDraggableElementPosition.Y;
}
Point mousePosition = e.GetPosition(this);
dragMoveBehavior.X += mousePosition.X - this.DragStartPosition.X;
dragMoveBehavior.Y += mousePosition.Y - this.DragStartPosition.Y;
this.DragStartPosition = new Point(mousePosition.X, mousePosition.Y);
}
private void OnDraggedElementLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var draggedElement = sender as UIElement;
draggedElement.PreviewMouseMove -= OnDraggingElementMouseMove;
draggedElement.PreviewMouseLeftButtonUp -= OnDraggedElementLeftButtonUp;
}
To add convenience, you probably would like to wrap the MouseDragElementBehavior
into a custom Behavior
or attached behavior. You then use this behavior to enable element drag.
This way you no longer have to care about whether the element marks bubbling mouse events as handled.
This custom behavior will internally delegate the drag operation to the wrapped MouseDragElementBehavior
using the above code:
<ToggleButton local:MyMouseDragElementBehavior.IsEnabled="True"
local:MyMouseDragElementBehavior.ConstrainToParentBounds="True" />
public class MyMouseDragElementBehavior : DependencyObject
{
public static bool GetIsEnabled(DependencyObject attachingElement) => (bool)attachingElement.GetValue(IsEnabledProperty);
public static void SetIsEnabled(DependencyObject attachingElement, bool value) => attachingElement.SetValue(IsEnabledProperty, value);
public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached(
"IsEnabled",
typeof(bool),
typeof(MyMouseDragElementBehavior),
new PropertyMetadata(default(bool), OnIsEnabledChanged));
public static bool GetConstrainToParentBounds(DependencyObject attachingElement) => (bool)attachingElement.GetValue(ConstrainToParentBoundsProperty);
public static void SetConstrainToParentBounds(DependencyObject attachingElement, bool value) => attachingElement.SetValue(ConstrainToParentBoundsProperty, value);
public static readonly DependencyProperty ConstrainToParentBoundsProperty = DependencyProperty.RegisterAttached(
"ConstrainToParentBounds",
typeof(bool),
typeof(MyMouseDragElementBehavior),
new PropertyMetadata(default(bool), OnConstrainToParentBoundsChanged));
private static Dictionary<DependencyObject, MouseDragElementBehavior> BehaviorTable { get; } = new Dictionary<DependencyObject, MouseDragElementBehavior>();
private static Dictionary<DependencyObject, Window> RootVisualTable { get; } = new Dictionary<DependencyObject, Window>();
private static Point DragStartPosition { get; set; }
private static void OnIsEnabledChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (attachingElement is not UIElement attachingUiElement)
{
return;
}
if ((bool)e.NewValue)
{
var isConstrainToParentBoundsEnabled = GetConstrainToParentBounds(attachingUiElement);
var dragBehavior = new MouseDragElementBehavior() { ConstrainToParentBounds = isConstrainToParentBoundsEnabled };
BehaviorTable.Add(attachingUiElement, dragBehavior);
dragBehavior.Attach(attachingUiElement);
Window rootVisualOfAttachingElement = Window.GetWindow(attachingElement);
RootVisualTable.Add(attachingUiElement, rootVisualOfAttachingElement);
attachingUiElement.PreviewMouseLeftButtonDown += OnDraggableElementLeftButtonDown;
}
else
{
if (BehaviorTable.TryGetValue(attachingUiElement, out MouseDragElementBehavior dragBehavior))
{
dragBehavior.Detach();
BehaviorTable.Remove(attachingUiElement);
}
RootVisualTable.Remove(attachingUiElement);
}
}
private static void OnConstrainToParentBoundsChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (!BehaviorTable.TryGetValue(attachingElement, out MouseDragElementBehavior dragMoveBehavior))
{
return;
}
dragMoveBehavior.ConstrainToParentBounds = (bool)e.NewValue;
}
private static void OnDraggableElementLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var draggableElement = sender as UIElement;
if (!RootVisualTable.TryGetValue(draggableElement, out Window rootVisualOfDraggableElement))
{
return;
}
DragStartPosition = e.GetPosition(rootVisualOfDraggableElement);
draggableElement.PreviewMouseMove += OnDraggingElementMouseMove;
draggableElement.PreviewMouseLeftButtonUp += OnDraggingElementLeftButtonUp;
}
private static void OnDraggingElementLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var draggedElement = sender as UIElement;
draggedElement.PreviewMouseMove -= OnDraggingElementMouseMove;
draggedElement.PreviewMouseLeftButtonUp -= OnDraggingElementLeftButtonUp;
}
private static void OnDraggingElementMouseMove(object sender, MouseEventArgs e)
{
var draggingElement = sender as UIElement;
if (!BehaviorTable.TryGetValue(draggingElement, out MouseDragElementBehavior dragMoveBehavior)
|| !RootVisualTable.TryGetValue(draggingElement, out Window rootVisualOfDraggingElement)
{
return;
}
Point initialDraggableElementPosition = draggingElement.TranslatePoint(new Point(0, 0), rootVisualOfDraggingElement);
if (double.IsNaN(dragMoveBehavior.X))
{
dragMoveBehavior.X = initialDraggableElementPosition.X;
}
if (double.IsNaN(dragMoveBehavior.Y))
{
dragMoveBehavior.Y = initialDraggableElementPosition.Y;
}
Point mousePosition = e.GetPosition(rootVisualOfDraggingElement);
dragMoveBehavior.X += mousePosition.X - DragStartPosition.X;
dragMoveBehavior.Y += mousePosition.Y - DragStartPosition.Y;
DragStartPosition = new Point(mousePosition.X, mousePosition.Y);
}
}