So I have a control named UI_TileInformation which needs to be kept inside its parent container (a canvas), while allowing the user to drag it freely. This solution was helpful in getting it to function, it now drags perfectly fine. However, it can be dragged outside of the bounds of its container. This solution was no help as I do not see where the _transform variable comes from.
The control has the following default variables:
protected bool isDragging;
private Point clickPosition;
As well as these event handlers:
this.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(Control_MouseLeftButtonDown);
this.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(Control_MouseLeftButtonUp);
this.PreviewMouseMove += new MouseEventHandler(Control_MouseMove);
I am fairly new to c# and WPF but I figured out that those event handlers would only work with when previewed (PreviewMouseMove as opposed to MouseMove)
As for the code that makes it drag, it is similar to the first link above:
private void Control_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
isDragging = true;
var draggableControl = sender as UI_TileInformation;
clickPosition = e.GetPosition(this);
draggableControl.CaptureMouse();
}
private void Control_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
isDragging = false;
var draggableControl = sender as UI_TileInformation;
draggableControl.ReleaseMouseCapture();
}
private void Control_MouseMove(object sender, MouseEventArgs e)
{
var draggableControl = sender as UI_TileInformation;
if (isDragging && draggableControl != null)
{
Point positionInUIElement = e.GetPosition(this);
var transform = draggableControl.RenderTransform as TranslateTransform;
if (transform == null)
{
transform = new TranslateTransform();
draggableControl.RenderTransform = transform;
}
transform.X = positionInCanvas.X - clickPosition.X;
transform.Y = positionInCanvas.Y - clickPosition.Y;
}
}
How can I get my control to stay inside the canvas?
Related
I'm making an application with drag and drop and what i basically want is to create a new object when i drag the object from a list and into the "Main area". I Have an abstract class
public abstract class SymbolModel
And 2 (need a lot more) classes inhereting from it
public class ValveModel : SymbolModel
public class LightBulbModel : SymbolModel
When i drag and drop it shows up but when i drag multiple they're all the same. I've made a click function which hits all of them where i only want to do it on the one clicked.
My dragfrom method looks like this:
private void UIElement_OnMouseMove(object sender, MouseEventArgs e)
{
TextBlock txtBlock = sender as TextBlock;
if (txtBlock == null) return;
if (!(txtBlock.DataContext is SymbolModel)) return;
if (e.LeftButton == MouseButtonState.Pressed)
{
DataObject data = new DataObject();
data.SetData("Object", (SymbolModel) txtBlock.DataContext);
DragDrop.DoDragDrop(this, data, DragDropEffects.Copy | DragDropEffects.Move);
}
}
My drop method looks like this:
private void SymbolView_OnDrop(object sender, DragEventArgs e)
{
Point pos = e.GetPosition(SymbolViewControl);
Console.WriteLine(e.Data.GetData("Object").ToString());
SymbolModel obj = (SymbolModel) e.Data.GetData("Object");
obj.CanvasTopImage = pos.Y;
obj.CanvasLeftImage = pos.X;
_symbolViewModel.Symbols.Add(obj);
}
And my click method is here:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
if (!(sender is Button btn)) return;
if (!(btn.DataContext is SymbolModel)) return;
SymbolModel symbol = (SymbolModel) btn.DataContext;
foreach (SymbolModel sym in _symbolViewModel.Symbols)
{
Console.WriteLine(sym.Id);
}
if (symbol.ImageName.Equals("valve_green.png"))
{
symbol.ImageName = "valve_red.png";
}
else
{
symbol.ImageName = "valve_green.png";
}
}
What i want to happen here is that when it's dropped it becomes a new entity independent of the others.
Hope this makes sense! Thank you!
So i got it to work by changing my drop method to:
private void SymbolView_OnDrop(object sender, DragEventArgs e)
{
Point pos = e.GetPosition(SymbolViewControl);
Console.WriteLine(e.Data.GetData("Object").ToString());
SymbolModel obj = (SymbolModel) e.Data.GetData("Object");
Type t = obj.GetType();
var symbol = (SymbolModel)Activator.CreateInstance(t);
symbol.CanvasTopImage = pos.Y;
symbol.CanvasLeftImage = pos.X;
_symbolViewModel.Symbols.Add(symbol);
}
Its seems that the Activator was what i needed.
I have a WPF application and over a part of it I had a card which displayed Javascript. My problem was that I wasn't able to scroll with the mouse over the Javascript card, because it was swallowing the events. I solved that by hooking into the PreviewMouseWheel event (described here) sent by UIElement:
protected void AddListeners(BorderedCanvas borderedCanvas)
{
borderedCanvas.PreviewMouseWheel += PreviewMouseWheel;
}
protected void RemoveListeners(BorderedCanvas borderedCanvas)
{
borderedCanvas.PreviewMouseWheel -= PreviewMouseWheel;
}
protected static void PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (!e.Handled)
{
e.Handled = true;
var eventArg =
new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{
RoutedEvent = UIElement.MouseWheelEvent,
Source = sender
};
var parent = VisualTreeHelper.GetParent((UIElement)sender) as UIElement;
parent?.RaiseEvent(eventArg);
}
}
Now, scrolling with the mouse works perfectly fine.
However, scrolling with touch doesn't work.
So, I modified AddListeners:
protected void AddListeners(BorderedCanvas borderedCanvas)
{
borderedCanvas.PreviewMouseWheel += PreviewMouseWheel;
borderedCanvas.PreviewTouchDown += new EventHandler<TouchEventArgs>(OnTouchDown);
borderedCanvas.PreviewTouchMove += new EventHandler<TouchEventArgs>(OnTouchMove);
}
And I added these methods:
protected static void OnTouchDown(object sender, TouchEventArgs e)
{
if (!e.Handled)
{
e.Handled = true;
var eventArg =
new TouchEventArgs(e.TouchDevice, e.Timestamp)
{
RoutedEvent = UIElement.TouchDownEvent,
Source = sender
};
var parent = VisualTreeHelper.GetParent((UIElement)sender) as UIElement;
parent?.RaiseEvent(eventArg);
}
}
protected static void OnTouchMove(object sender, TouchEventArgs e)
{
if (!e.Handled)
{
e.Handled = true;
var eventArg =
new TouchEventArgs(e.TouchDevice, e.Timestamp)
{
RoutedEvent = UIElement.TouchMoveEvent,
Source = sender
};
var parent = VisualTreeHelper.GetParent((UIElement)sender) as UIElement;
parent?.RaiseEvent(eventArg);
}
}
However, scrolling with touch still doesn't work.
Any idea what I did wrong?
To me it seems that I treated the mouse scroll and the touch scroll in a similar way, but the first works and the second doesn't.
You need to set the ScrollViewer.PanningMode for it to work with the touch.
<ScrollViewer PanningMode="Both"/>
The default value for the PanningMode is None.
I'm trying to drag a rectangle element called "Rec" using the mouse. I can drag it to the location I want and the rectangle stays there, but when I try to drag it again it returns to the first location and the dragging starts from there. I want to drag it from where I left it the last time. I just don't get where the problem is.
I have one Canvas only and all my elements are inside it, the Canvas is called the "maincanvas". I use the following very simple events to for dragging.
Point originalPosition = new Point(0, 0);
private void Rec_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
Point CurrPosition = Mouse.GetPosition(MainCanvas);
Canvas.SetLeft(e.Source as UIElement, -( originalPosition.X - CurrPosition.X));
Canvas.SetTop(e.Source as UIElement, -(originalPosition.Y - CurrPosition.Y));
}
private void Rec_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
UIElement a = e.Source as UIElement;
a.CaptureMouse();
Rec.MouseMove += Rec_MouseMove;
originalPosition = Mouse.GetPosition(MainCanvas);
}
private void Rec_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Rec.MouseMove -= Rec_MouseMove;
UIElement a = e.Source as UIElement;
a.ReleaseMouseCapture();
originalPosition = new Point(0, 0);
}
I hope you guys could help me.
You should handle the mouse events on the Canvas like this:
<Canvas MouseLeftButtonDown="CanvasMouseLeftButtonDown"
MouseLeftButtonUp="CanvasMouseLeftButtonUp"
MouseMove="CanvasMouseMove">
...
</Canvas>
In the mouse down handler you would get the element that should be dragged by the MouseButtonEventArgs's OriginalSource property:
private UIElement draggedElement;
private Point lastMousePos;
private void CanvasMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource != sender)
{
IInputElement canvas = (IInputElement)sender;
canvas.CaptureMouse();
draggedElement = e.OriginalSource as UIElement;
lastMousePos = e.GetPosition(canvas);
}
}
private void CanvasMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
((IInputElement)sender).ReleaseMouseCapture();
draggedElement = null;
}
private void CanvasMouseMove(object sender, MouseEventArgs e)
{
if (draggedElement != null)
{
var p = e.GetPosition((IInputElement)sender);
var dx = p.X - lastMousePos.X;
var dy = p.Y - lastMousePos.Y;
lastMousePos = p;
Canvas.SetLeft(draggedElement, Canvas.GetLeft(draggedElement) + dx);
Canvas.SetTop(draggedElement, Canvas.GetTop(draggedElement) + dy);
}
}
I have a range of drag drop activities all working fine on my quiz. (From answering questions to selecting an avatar)
There is a lot of repetition of the code and I am thinking it would be better implemented from a class and call the method each time that I use a drag drop.
Firstly can this be done? and secondly would I need a new method for the dragging and dropping?
Any thoughts or rough ideas would be great.
private void pictureBox2_MouseDown(object sender, MouseEventArgs e)
{
pictureBox2.DoDragDrop(pictureBox2.Image, DragDropEffects.Copy);
}
private void panel1_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Copy;
}
private void panel1_DragDrop(object sender, DragEventArgs e)
{
//Set background image of panel to selected avatar'
panel1.BackgroundImage = (Image)e.Data.GetData(DataFormats.Bitmap);
}
If all you want is to avoid duplicating the same events of several controls you should use common events:
private void commonPBox_MouseDown(object sender, MouseEventArgs e)
{
PictureBox PB = sender as PictureBox;
if (PB == null) return; //or throw an exception
PB.DoDragDrop(PB.Image, DragDropEffects.Copy);
}
private void commonPanel_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Copy;
}
private void commonPanel_DragDrop(object sender, DragEventArgs e)
{
Panel Pan = sender as Panel;
if (Pan == null) return; //or throw an exception
//Set background image of panel to selected avatar
Pan.BackgroundImage = (Image)e.Data.GetData(DataFormats.Bitmap);
}
Select the respective controls and enter the event names into the respective event name slots in the event pane of the properties tab.
Note how I cast the sender param of the event to get a types reference to the control that triggers the event. If the cast goes wrong the reference is set to null.
If you want more control and more flexibilty you may consider creating a DragAndDropcontroller class..:
static class DnDCtl
{
static List<Control> Targets = new List<Control>();
static List<Control> Sources = new List<Control>();
static public void RegisterSource(Control ctl)
{
if (!Sources.Contains(ctl) )
{
Sources.Add(ctl);
ctl.MouseDown += ctl_MouseDown;
}
}
static public void UnregisterSource(Control ctl)
{
if (Sources.Contains(ctl))
{
Sources.Remove(ctl);
}
}
static public void RegisterTarget(Control ctl)
{
if (!Targets.Contains(ctl))
{
Targets.Add(ctl);
ctl.DragEnter += ctl_DragEnter;
ctl.DragDrop += ctl_DragDrop;
ctl.AllowDrop = true;
}
}
static public void UnregisterTarget(Control ctl)
{
if (Targets.Contains(ctl))
{
Targets.Remove(ctl);
ctl.DragEnter -= ctl_DragEnter;
ctl.DragDrop -= ctl_DragDrop;
}
}
static void ctl_MouseDown(object sender, MouseEventArgs e)
{
PictureBox PB = sender as PictureBox;
if (PB != null) PB.DoDragDrop(PB.Image, DragDropEffects.Copy);
Panel Pan = sender as Panel;
if (Pan != null) Pan.DoDragDrop(Pan.BackgroundImage, DragDropEffects.Copy);
}
static void ctl_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Copy;
}
static void ctl_DragDrop(object sender, DragEventArgs e)
{
Panel Pan = sender as Panel;
if (Pan != null) Pan.BackgroundImage = (Image)e.Data.GetData(DataFormats.Bitmap);
PictureBox PB = sender as PictureBox;
if (PB != null) PB.BackgroundImage = (Image)e.Data.GetData(DataFormats.Bitmap);
}
}
Notes:
I have coded symmetric actions for Panels and PictureBoxes to show how you can handle different controls.
I have created but not used Lists of sources and targets. For more complex projects you will find them useful.
I have coded both Register and Unregister methods. You can register after some condition and unregister when it no longer applies
Such a Controller is good for dynamically alowing or disallowing Drag&Drop for controls, esp. when you create them dynamically.
You could also pass around delegates to decouple the dragdrop action from the controller.
Also note that some folks sometimes do prefer to use the MouseMove event over MouseDown esp. as it won't so easily interfere with making a selection.
ListView has a dedicated event instead, ListView.ItemDrag, which obviously should be used when registering!
I assume that previewdrop/drop events fired when it detect a drag target with an element as a drop target . In this case , my drop target is a textbox and my drag target is a label. Both of them are dynamically created from DB . I am using DragAdorner to clone the element that i am dragging , before implementing the DragAdorner , my DnD works well but after i use the dragadorner , it won't work . And i notice my previewdrop event is not firing when i try to debug .
Here are my codes :
tbox.Drop += new DragEventHandler(tbox_PreviewDrop); // text box , Drop Target
tbox.DragOver += new DragEventHandler(tbox_DragOver);
Label lbl = new Label(); // Label , Drag Target
lbl.Content = s;
lbl.Width = Double.NaN;
lbl.Height = 40;
lbl.FontSize = 19;
lbl.PreviewMouseDown += new MouseButtonEventHandler(lbl_MouseDown);
lbl.PreviewMouseMove += new MouseEventHandler(lbl_MouseMove);
lbl.PreviewGiveFeedback += new GiveFeedbackEventHandler(lbl_GiveFeedback);
private void lbl_MouseDown(object sender, MouseButtonEventArgs e)
{
startPoint = e.GetPosition(this);
// Mouse.OverrideCursor = Cursors.None;
}
private void lbl_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
// Mouse.OverrideCursor = Cursors.None;
var source = sender as UIElement;
Label lbl = sender as Label;
Point current = e.GetPosition(this);
Vector diff = startPoint - current;
if (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
{
adorner = new DragAdorner(lbl, e.GetPosition(lbl));
AdornerLayer.GetAdornerLayer(lbl).Add(adorner);
var dragData = new DataObject(this);
DragDrop.DoDragDrop(source, dragData, DragDropEffects.Copy);
AdornerLayer.GetAdornerLayer(lbl).Remove(adorner);
}
startPoint = current;
}
}
private void lbl_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
if (adorner != null)
{
Label lbl = sender as Label;
var pos = lbl.PointFromScreen(GetMousePosition());
adorner.UpdatePosition(pos);
e.Handled = true;
}
}
private void tbox_PreviewDrop(object sender, DragEventArgs e)
{
(sender as TextBox).Text = string.Empty; // Empty the textbox from previous answer.
(sender as TextBox).Background = Brushes.White;
e.Effects = DragDropEffects.Move;
e.Handled = true;
}
private void tbox_DragOver(object sender, DragEventArgs e)
{
e.Handled = true;
e.Effects = DragDropEffects.Move;
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(ref Win32Point pt);
[StructLayout(LayoutKind.Sequential)]
internal struct Win32Point
{
public Int32 X;
public Int32 Y;
};
public static Point GetMousePosition()
{
Win32Point w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
return new Point(w32Mouse.X, w32Mouse.Y);
}
private Point startPoint;
private DragAdorner adorner;
And the adorner class file :
public class DragAdorner : Adorner {
public DragAdorner(UIElement adornedElement, Point offset)
: base(adornedElement) {
this.offset = offset;
vbrush = new VisualBrush(AdornedElement);
//vbrush.Opacity = .7;
}
public void UpdatePosition(Point location) {
this.location = location;
this.InvalidateVisual();
}
protected override void OnRender(DrawingContext dc) {
var p = location;
p.Offset(-offset.X, -offset.Y);
dc.DrawRectangle(vbrush, null, new Rect(p, this.RenderSize));
}
private Brush vbrush;
private Point location;
private Point offset;
}
I seen http://www.adorkable.us/books/wpf_control_development.pdf ( page 103 ) but its way too complicated for me as i am a newbie .
It is because of my GiveFeedBack event that is in conflict with other events?
Since your DragAdorner is always under your cursor, it will be the object receiving the drop. If you set IsHitTestVisible = false; in the constructor for the Adorner, it should fix this.
Even though you haven't set AllowDrop on the Adorner, since it is under the cursor, it will intercept the drop attempt. But since it doesn't accept drop, it will just cancel it.
Update
The other issue is that you are setting the allowed effects in your drag operation to DragDropEffects.Copy, but in the DragOver and Drop handlers, you're trying to do a DragDropEffects.Move. This won't work, as those are not the same operation. These must match. If you want to enable both operations on drag, you can specify both with a bitwise or:
DragDrop.DoDragDrop(source, dragData, DragDropEffects.Copy | DragDropEffects.Move);
Update 2
If you want to drop anything other than a string onto a TextBox, you have to use the PreviewDrop and PreviewDragOver events. Otherwise, the TextBox's default handling will ignore anything else. So it would look like this:
tbox.PreviewDrop += new DragEventHandler(tbox_PreviewDrop);
tbox.PreviewDragOver += new DragEventHandler(tbox_DragOver);
Try setting background color for your label and see if it will work properly.