Introduction
I was asked to put up a dialog while the application was busy that could be dragged, allowing the user to move it out of the way so that he could see what was underneath.
Background
I had tried several things I found online but did not really like any of them, and some actually had issues.
The Code
The DraggablePopup
derives from the Popup
class. The overridden OnInitialized
method checks that the Popup
has content. This is because the Popup
cannot be clicked on. The content, or Child
, should contain a FrameworkElement
that encompases any part of the Popup
that the designer wants to allow the user to click and drag with. The OnInitialized
method, after checking that there is content, then attaches handlers for the MouseLeftButtonDown
, MouseLeftButtonUp
and MouseMove
events:
public class DraggablePopup : Popup
{
Point _initialMousePosition;
bool _isDragging ;
protected override void OnInitialized(EventArgs e)
{
var contents = Child as FrameworkElement;
Debug.Assert(contents != null, "DraggablePopup either has no content if content that " +
"does not derive from FrameworkElement. Must be fixed for dragging to work.");
contents.MouseLeftButtonDown += Child_MouseLeftButtonDown;
contents.MouseLeftButtonUp += Child_MouseLeftButtonUp;
contents.MouseMove += Child_MouseMove;
}
private void Child_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var element = sender as FrameworkElement;
_initialMousePosition = e.GetPosition(null);
element.CaptureMouse();
_isDragging = true;
e.Handled = true;
}
private void Child_MouseMove(object sender, MouseEventArgs e)
{
if (_isDragging)
{
var currentPoint = e.GetPosition(null);
HorizontalOffset = HorizontalOffset + (currentPoint.X - _initialMousePosition.X);
VerticalOffset = VerticalOffset + (currentPoint.Y - _initialMousePosition.Y);
}
}
private void Child_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (_isDragging)
{
var element = sender as FrameworkElement;
element.ReleaseMouseCapture();
_isDragging = false;
e.Handled = true;
}
}
}
There are two private
fields: one to indicate that a drag operation is in progress, and the other to contain the initial location that dragging started. The MouseLeftButtonDown
and MouseLeftButtonUp
event
handlers basically start and end the process by setting the bool
IsDragging
field, and the MouseLeftButtonDown
also sets the initial location of the click. Then the MouseMove
adjusts the location of the Popup
.
Using the Code
I have used a Border
as the content in the sample. There is a Button
within the Border
that allows the Popup
to be closed, but is not shown in the code below. I have set the Placement
as Center
to the containing Window
. From my experience, the way Placement
works is different on a Window
than other controls. In other controls, to get the Popup
centered, I had to use the HorzontalOffset
and VerticalOffset
since the top left corner is what is centered.
<local:DraggablePopup Width="300"
Height="200"
IsOpen="{Binding ElementName=WindowToggleButton,
Path=IsChecked}"
Placement="Center"
PlacementTarget="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
>
<Border Background="LightGray"
BorderBrush="Black"
BorderThickness="1" >
</Border>
</local:DraggablePopup>
History
- 2016/12/23: Initial version