The following behavior allows you to drag a popup around with the mouse.
How can it be modified to constrain the element being dragged to some parent element, eg the Application window?
I don't want to allow the popup to be dragged out of the main window.
public class MouseDragPopupBehavior : Behavior<Popup>
{
private bool mouseDown;
private Point oldMousePosition;
protected override void OnAttached()
{
AssociatedObject.MouseLeftButtonDown += (s, e) =>
{
mouseDown = true;
oldMousePosition = AssociatedObject.PointToScreen(e.GetPosition(AssociatedObject));
AssociatedObject.Child.CaptureMouse();
};
AssociatedObject.MouseMove += (s, e) =>
{
if (!mouseDown) return;
var newMousePosition = AssociatedObject.PointToScreen(e.GetPosition(AssociatedObject));
var offset = newMousePosition - oldMousePosition;
oldMousePosition = newMousePosition;
AssociatedObject.HorizontalOffset += offset.X;
AssociatedObject.VerticalOffset += offset.Y;
};
AssociatedObject.MouseLeftButtonUp += (s, e) =>
{
mouseDown = false;
AssociatedObject.Child.ReleaseMouseCapture();
};
}
}
It's better to constrain popup not to the window itself, but to the root control of the window, because you likely want to restrict it to the client area of the window (without titlebar and such), and the easiest way I'm aware to get that client area is to just work with the root control inside window.
In case of popup, it's also easier to work with root element inside, because interactions between popup itself and visual tree are complicated.
What you need is to get the bounding rectangle of your popup in screen coordinates, the bounding rectange of contraining control (the root of the window) in screen coordinates, and check if one contains the other. Here is sample code:
public class MouseDragPopupBehavior : Behavior<Popup>
{
private bool mouseDown;
private Point oldMousePosition;
private FrameworkElement constraint;
protected override void OnAttached() {
// obtain root of the window (might fail in general case, adjust to your specific case if necessary)
constraint = (FrameworkElement) Application.Current.MainWindow.Content;
AssociatedObject.MouseLeftButtonDown += (s, e) =>
{
mouseDown = true;
oldMousePosition = AssociatedObject.PointToScreen(e.GetPosition(AssociatedObject));
AssociatedObject.Child.CaptureMouse();
};
AssociatedObject.MouseMove += (s, e) =>
{
if (!mouseDown) return;
var newMousePosition = AssociatedObject.PointToScreen(e.GetPosition(AssociatedObject));
var offset = newMousePosition - oldMousePosition;
// get root control of popup (might fail in general case, adjust if necessary)
var popupRoot = (FrameworkElement)AssociatedObject.Child;
// obtain (x,y) of popup in screen coordinates
var popupLeftTop = popupRoot.PointToScreen(new Point(0, 0));
// obtain bounds
var popupBounds = new Rect(popupLeftTop.X + offset.X, popupLeftTop.Y + offset.Y, popupRoot.ActualWidth, popupRoot.ActualHeight);
// do the same with contraint control
var constraintLeftTop = constraint.PointToScreen(new Point(0, 0));
var constraintBounds = new Rect(constraintLeftTop.X, constraintLeftTop.Y, constraint.ActualWidth, constraint.ActualHeight);
// if bounding rect of constraint does not contain popup - that means popup is not fully inside the constraint, then do not apply offset
if (!constraintBounds.Contains(popupBounds))
return;
// otherwise continue
oldMousePosition = newMousePosition;
AssociatedObject.HorizontalOffset += offset.X;
AssociatedObject.VerticalOffset += offset.Y;
};
AssociatedObject.MouseLeftButtonUp += (s, e) =>
{
mouseDown = false;
AssociatedObject.Child.ReleaseMouseCapture();
};
}
}
Related
Okay, so I have a Grid with 4 child Images, each positioned in all four corners of the Grid like so.
The purpose of the bottom two Child Images, is to stretch the grid. So when the user drags these corners, the Grid will stretch.
My problem is that the Children will stretch too, which I don't want.
Is there any way to ensure that the size of the children does not stretch with the Grid?
Thanks.
P.S. I could detach the Child Images from the Grid, but it is much simpler and more elegant this way as the x/y positions remain at the edges of the Grid perfectly, I just want to ensure they do not Scale with it, if possible.
Create Grid and add Children
//ImageGrid
ImageControl.Height = 500;
ImageControl.Width = 500;
ImageControl.ManipulationMode = ManipulationModes.All;
canvas.Children.Add(ImageControl);
ImageControl.Children.Add(tickBtn);
ImageControl.Children.Add(delBtn);
ImageControl.Children.Add(skewBtn);
ImageControl.Children.Add(strBtn);
ImageManipulation.Image_Drag = new CompositeTransform();
ImageManipulation.Image_Drag.CenterX = imW / 2;
ImageManipulation.Image_Drag.CenterY = imH / 2;
ImageControl.RenderTransform = ImageManipulation.Image_Drag;
strBtn.ManipulationDelta += ImageManipulation.resBtn_Manip;
skewBtn.ManipulationDelta += MaskManipulation.skewBtn_Manip;
dragControl.ManipulationDelta += ImageManipulation.movBtn_Manip;
Manipulation Class
public static CompositeTransform Image_Drag;
public static void resBtn_Manip(object sender, ManipulationDeltaRoutedEventArgs e)
{
Mask_Drag.ScaleX += e.Delta.Translation.X;
Mask_Drag.ScaleY += e.Delta.Translation.X;
}
public static void movBtn_Manip(object sender, ManipulationDeltaRoutedEventArgs e)
{
Mask_Drag.TranslateX += e.Delta.Translation.X;
Mask_Drag.TranslateY += e.Delta.Translation.Y;
}
public static void skewBtn_Manip(object sender, ManipulationDeltaRoutedEventArgs e)
{
Mask_Drag.ScaleX -= e.Delta.Translation.X;
Mask_Drag.ScaleY += e.Delta.Translation.Y;
if (Mask_Drag.ScaleX < 0.5)
{
Mask_Drag.ScaleX = 0.5;
}
if (Mask_Drag.ScaleY < 0.5)
{
Mask_Drag.ScaleY = 0.5;
}
}
the user should be able to draw a straight line on a panel similar to drawing a straight line in paint .
the user clicks on the panel and when he moves the mouse the line should also move along with the mouse (i.e similar to drawing a staright line in paint) and when the user releases the mouse the line should have been drawn from the original point of click to this release point .
i.e not a free hand line.
is there any animation for this ?
How about this? :
public class LinePanel : Panel
{
public LinePanel()
{
this.MouseDown += (src, e) => { LineStartPos = LineEndPos = e.Location; Capture = true; Invalidate(); };
this.MouseMove += (src, e) => { if (Capture) { LineEndPos = e.Location; Invalidate(); } };
this.MouseUp += (src, e) => { if (Capture) { LineEndPos = e.Location; } Capture = false; Invalidate(); };
}
private Point LineStartPos, LineEndPos;
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (LineStartPos != LineEndPos)
e.Graphics.DrawLine(new Pen(Color.Black, 2), LineStartPos, LineEndPos);
}
}
To test you can just add a new LinePanel() to the Controls collection of your form, and set location/size or anchor / dock paramaters to size it.
im working now on a UserControl which ive made draggable using the code below (which is quite known and used). This UserControl looks and is used in a similar to MessageBox (the gray color and the blue rectangle) , the task is to make this UserControl draggable only from the blue rectangle just as any MessageBox , not as its now draggable from any place inside it!
any suggestions on how to be able to do this? thanks in advance!
below the code used to drag the UserControl
public UserControl1(Data data, Settings settings)
{
InitializeComponent();
MouseLeftButtonDown += new MouseButtonEventHandler(root_MouseLeftButtonDown);
MouseLeftButtonUp += new MouseButtonEventHandler(root_MouseLeftButtonUp);
MouseMove += new MouseEventHandler(root_MouseMove);
}
...
private void root_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var element = sender as FrameworkElement;
anchorPoint = e.GetPosition(null);
element.CaptureMouse();
isInDrag = true;
e.Handled = true;
}
private void root_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (isInDrag)
{
var element = sender as FrameworkElement;
element.ReleaseMouseCapture();
isInDrag = false;
e.Handled = true;
}
}
private void root_MouseMove(object sender, MouseEventArgs e)
{
if (isInDrag)
{
var element = sender as FrameworkElement;
currentPoint = e.GetPosition(null);
UIElement parentElement = (UIElement)this.Parent;
maxHeightParent = parentElement.RenderSize.Height;
maxWidthParent = parentElement.RenderSize.Width;
maxHeight = RenderSize.Height;
maxWidth = RenderSize.Width;
//Window.ActualHeightProperty
//element.ActualHeight
transform.X += (currentPoint.X - anchorPoint.X);
transform.Y += (currentPoint.Y - anchorPoint.Y);
this.RenderTransform = transform;
anchorPoint = currentPoint;
}
}
}
Two ways to solve this, which are actually the same way with different syntax:
Via code - in the constructor, instead of
MouseLeftButtonDown += new MouseButtonEventHandler(root_MouseLeftButtonDown);
MouseLeftButtonUp += new MouseButtonEventHandler(root_MouseLeftButtonUp);
MouseMove += new MouseEventHandler(root_MouseMove);
use
myBlueRect.MouseLeftButtonDown +=
new MouseButtonEventHandler(root_MouseLeftButtonDown);`
myBlueRect.MouseLeftButtonUp +=
new MouseButtonEventHandler(root_MouseLeftButtonUp);
myBlueRect.MouseMove += new MouseEventHandler(root_MouseMove);
Via XAML - in the tag of the relevant element that you want to grag from (lets say it's a Grid):
<Grid x:Name="myBlueRect"
MouseLeftButtonDown="root_MouseLeftButtonDown"
MouseLeftButtonUp="root_MouseLeftButtonUp"
MouseMove="root_MouseMove"
.../>
Two basic options I think about:
Inside your UserControl make the rectangle a Border (or other control), and put the events on this Border and not on the UserControl.
If from some reason you do want the events to be on the UserControl, put a Hidden Border on the place of the rectangle, and in the event do the dragging only if the mouse is over this Border.
I am using OxyPlot for charting in my application. I want to extend the OxyPlot library to include a rubber-banding line series, similar to how a polyline might rubber-band in a CAD application.
I have written an example for this.
[Example("LineSeries rubberbanding")]
public static PlotModel MouseRubberbandingEvent()
{
var model = new PlotModel("Rubberbanding",
"Left click to add line and press Esc to end.")
{
LegendSymbolLength = 40
};
// Add a line series
var s1 = new LineSeries("LineSeries1")
{
Color = OxyColors.SkyBlue,
MarkerType = MarkerType.Circle,
MarkerSize = 6,
MarkerStroke = OxyColors.White,
MarkerFill = OxyColors.SkyBlue,
MarkerStrokeThickness = 1.5
};
model.Series.Add(s1);
s1.Points.Add(new DataPoint(10,
10));
IDataPoint tempDataPoint = new DataPoint(0,0);
s1.Points.Add(tempDataPoint);
// Remember to refresh/invalidate of the plot
model.RefreshPlot(false);
bool isRubberbanding = false;
// Subscribe to the mouse down event on the line series
model.MouseDown += (s, e) =>
{
// only handle the left mouse button (right button can still be used to pan)
if (e.ChangedButton == OxyMouseButton.Left)
{
s1.Points.Add(s1.InverseTransform(e.Position));
isRubberbanding = true;
// Remember to refresh/invalidate of the plot
model.RefreshPlot(false);
// Set the event arguments to handled - no other handlers will be called.
e.Handled = true;
}
};
// Subscribe to the mouse down event on the line series
s1.MouseDown += (s, e) =>
{
// only handle the left mouse button (right button can still be used to pan)
if (
(e.ChangedButton == OxyMouseButton.Left)
&&
(isRubberbanding)
)
{
s1.Points.Add(s1.InverseTransform(e.Position));
isRubberbanding = true;
// Remember to refresh/invalidate of the plot
model.RefreshPlot(false);
// Set the event arguments to handled - no other handlers will be called.
e.Handled = true;
}
};
model.MouseMove += (s, e) =>
{
if (isRubberbanding)
{
var point = s1.InverseTransform(new ScreenPoint(e.Position.X-8,
e.Position.Y-8));
tempDataPoint.X = point.X;
tempDataPoint.Y = point.Y;
s1.Points.Remove(tempDataPoint);
s1.Points.Add(tempDataPoint);
model.RefreshPlot(false);
}
};
model.MouseUp += (s, e) =>
{
if (isRubberbanding)
{
s1.LineStyle = LineStyle.Solid;
model.RefreshPlot(false);
e.Handled = true;
}
};
return model;
}
The rubber-banding works fine when I offset the mouse cursor by 8 pixels. However, when I place the cursor to be just beneath the rubber-banding line, OxyPlot does not fire on the mouse down event for either the plot model or for the line series.
Please suggest why the mouse down event is not firing. I have raised the same question to OxyPlot, but there is no reply to this.
When I clicked on a control,
How to get cursor position relative to upper left corner of a (winforms) control ?
C#, VS 2005
PS:
I'm asking on context of tooltip "show" method which need that coordinates ..
This is my code to set tooltips on a composite control, might give you a clue (LED derivers from UserControl):
public LED()
{
InitializeComponent();
m_Image = global::AdvAdmittance.Controls.Properties.Resources.ledgray_small;
m_ToolTip = new ToolTip();
m_ToolTip.AutoPopDelay = 5000;
m_ToolTip.InitialDelay = 1000;
m_ToolTip.ReshowDelay = 500;
m_ToolTip.ShowAlways = true;
m_LedPictureBox.MouseHover += new EventHandler(m_LedPictureBox_MouseHover);
m_LedPictureBox.MouseLeave += new EventHandler(m_LedPictureBox_MouseLeave);
m_LedPictureBox.Click += new EventHandler(m_LedPictureBox_Click);
}
void m_LedPictureBox_MouseHover(object sender, EventArgs e)
{
if (m_ToolTipText != string.Empty)
{
Point toolTipPoint = this.Parent.PointToClient(Cursor.Position);
toolTipPoint.Y -= 20;
m_ToolTip.Show(m_ToolTipText, this.Parent, toolTipPoint);
}
}
void m_LedPictureBox_MouseLeave(object sender, EventArgs e)
{
m_ToolTip.Hide(this.m_LedPictureBox);
}
Ahh, Thanks for an answer.
All I need is a PointToClient method.
I hope (maybe) it will be useful for other people, here "my" code.
I took almost all code from http://support.microsoft.com/kb/322634 and modified three lines:
void treeView1_MouseMove(object sender, MouseEventArgs e)
{
// Get the node at the current mouse pointer location.
TreeNode theNode = this.treeView1.GetNodeAt(e.X, e.Y);
// Set a ToolTip only if the mouse pointer is actually paused on a node.
if ((theNode != null))
{
// Verify that the tag property is not "null".
if (theNode.Tag != null)
{
// Change the ToolTip only if the pointer moved to a new node.
if (theNode.Tag.ToString() != this.toolTip1.GetToolTip(this.treeView1))
{
//this.toolTip1.SetToolTip(this.treeView1, theNode.Tag.ToString());
Point c = System.Windows.Forms.Cursor.Position;
Point p = treeView1.PointToClient(c);
this.toolTip1.Show(theNode.Tag.ToString(), treeView1, p);
}
}
else
{
this.toolTip1.SetToolTip(this.treeView1, "");
}
}
else // Pointer is not over a node so clear the ToolTip.
{
this.toolTip1.SetToolTip(this.treeView1, "");
}
}
Have a look at
Windows Forms Coordinates
Control.PointToClient Method
C# Get a control’s position on a
form
Control PointToClient() vs
PointToScreen()