I'm trying to implement a WPF application with drag&drop functionality using MouseDragElementBehavior.
But I just can't find a way to get the dropped elements position relative to its parent Canavs.
Example code:
namespace DragTest {
public partial class MainWindow : Window {
private Canvas _child;
public MainWindow() {
InitializeComponent();
Canvas parent = new Canvas();
parent.Width = 400;
parent.Height = 300;
parent.Background = new SolidColorBrush(Colors.LightGray);
_child = new Canvas();
_child.Width = 50;
_child.Height = 50;
_child.Background = new SolidColorBrush(Colors.Black);
MouseDragElementBehavior dragBehavior = new MouseDragElementBehavior();
dragBehavior.Attach(_child);
dragBehavior.DragBegun += onDragBegun;
dragBehavior.DragFinished += onDragFinished;
Canvas.SetLeft(_child, 0);
Canvas.SetTop(_child, 0);
parent.Children.Add(_child);
Content = parent;
}
private void onDragBegun(object sender, MouseEventArgs args) {
Debug.WriteLine(Canvas.GetLeft(_child));
}
private void onDragFinished(object sender, MouseEventArgs args) {
Debug.WriteLine(Canvas.GetLeft(_child));
}
}
}
After Dropping the child Canvas the value of Canvas.GetLeft(_child) is still 0.
Why that? Why doesn't it change?
Sure, I can get the new position by using dragBehavior.X, but that's the child Canvas' position in the main window, not the position relative to the parent Canvas. There must be a way to get it...
I just found a workaround:
private void onDragFinished(object sender, MouseEventArgs args) {
Point windowCoordinates = new Point(((MouseDragElementBehavior)sender).X, ((MouseDragElementBehavior)sender).Y);
Point screenCoordinates = this.PointToScreen(windowCoordinates);
Point parentCoordinates = _parent.PointFromScreen(screenCoordinates);
Debug.WriteLine(parentCoordinates);
}
So I simple convert the point to screen coordinates, then from screen coordinates to the parents coordinates.
Nevertheless there will be problems if the parent Canvas is in some ScrollView or somthing.
There doesn't seem to be a simple solution with this Drag&Drop approach...
Related
so I have this Window Application that simulates mouse movement, I made a few tests and managed to "record" mouse movement and play it once recorded. Now I wanna make a Viewlist with 2 columns ("X" and "Y) containing the coords of the route I want my mouse to follow. Coordinates can be added throught "ycoord" and "xcoord" textlabels and can be added with "button1" button to the viewlist. There's also a "button2" that works as a "toggle" button to turn it on and off. So, I want is that, every time I press left click form mouse (LButton) the movement runs as long as I hold this button, and, if I hold it again it starts over again. I already managed the textlabels to place content into the viewlist, but I don't know how to read the viewlist and convert them into mouse movement coords.
Thanks in advance for the help!
You have to save the X,Y coords in a List, so you can create a list of points, then you have to iterate it and use it with:
Look at the object Cursor here
Also you have the code:
private void MoveCursor()
{
// Set the Current cursor, move the cursor's Position,
// and set its clipping rectangle to the form.
this.Cursor = new Cursor(Cursor.Current.Handle);
Cursor.Position = new Point(Cursor.Position.X - 50, Cursor.Position.Y - 50);
Cursor.Clip = new Rectangle(this.Location, this.Size);
}
Try this:
public partial class Form1 : Form
{
private List<Point> _points = null;
public Form1()
{
InitializeComponent();
_points = new List<Point>();
this.MouseMove += Form1_MouseMove;
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
//Save the point
_points.Add(new Point(e.X, e.Y));
}
private void PerformRecord()
{
foreach (var point in _points)
{
this.Cursor = new Cursor(Cursor.Current.Handle);
Cursor.Position = new Point(point.X, point.Y);
}
}
private void button1_Click(object sender, EventArgs e)
{
PerformRecord();
}
}
I want to draw a semi-transparent rectangle on panel containing some user controls in it.
My main goal is to create selection rectangle similar to what the windows file explorer does.
And tried with Control.Paint Event but the rectangle shows beneath the user control. I want that semi-transparent rectangle to show on top of all controls present in the panel.
Here is what I tried:
class test
{
public FlowLayoutPanel flp;
public test()
{
flp = new FlowLayoutPanel();
flp.Controls.Add(c);//Here c is User Control
flp.Paint += (s, pe) =>
{
Rectangle f = new Rectangle();
f.Size = new Size(100, 100);
Brush b = new System.Drawing.SolidBrush(Color.FromArgb(25, Color.Red));
pe.Graphics.FillRectangle(b, f);
b.Dispose();
};
}
}
Here Rectangle is visible under the user control.
Here is an example of using a 2nd Form to overlay the selection.
I am using a TabPage tabPage5 as my container, you should be able to adapt to any other Control or even the whole Form..
I use two class level variables and prepare the overlay Form somwhere at the start..:
Form overlay = new Form();
Point m_Down = Point.Empty;
void prepareOverlay()
{
overlay.BackColor = Color.Fuchsia; // your selection color
overlay.Opacity = 0.2f; // tranparency
overlay.MinimizeBox = false; // prepare..
overlay.MaximizeBox = false;
overlay.Text = "";
overlay.ShowIcon = false;
overlay.ControlBox = false;
overlay.FormBorderStyle = FormBorderStyle.None;
overlay.Size = Size.Empty;
overlay.TopMost = true;
}
These events are needed for the parent container the user will drag over:
private void tabPage5_MouseDown(object sender, MouseEventArgs e)
{
m_Down = e.Location;
overlay.Size = Size.Empty;
overlay.Show();
}
private void tabPage5_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
overlay.Location = tabPage5.PointToScreen(m_Down);
overlay.Size = new Size(e.X - m_Down.X, e.Y - m_Down.Y);
}
}
private void tabPage5_MouseUp(object sender, MouseEventArgs e)
{
// do your stuff and then hide overlay..
overlay.Hide();
}
I have only coded for the standard case of a user dragging from the top left to the bottom right.
For the other cases you will need to do a little juggling with Math.Abs& Math.Min, calculating the Size in absolutes and using the coordinate minima for the Location..
To collect all covered Controls you will have to revert the overlay.Bounds to the client coordinates using selectionRect = tabPage5.RectangleToClient (overlay.Bounds); To collect nested Controls the selection could use a method like this one:
List<Control> controlSelection = new List<Control>();
List<Control> getControls(Control container, Rectangle rect)
{
controlSelection = new List<Control>();
foreach (Control ctl in container.Controls)
if (rect.Contains(ctl.Bounds))
{
controlSelection.Add(ctl);
foreach (Control ct in ctl.Controls) controlSelection.Add(ct); ;
}
return controlSelection;
}
To call it use:
Rectangle grabRect = tabPage5.RectangleToClient (overlay.Bounds);
controlSelection = getControls(tabPage5, grabRect);
Console.WriteLine(controlSelection.Count + " Controls grabbed.");
For multiple levels of nesting you will need a recursive function! For this case it may be better to leave the selectionRectangle is screen coordinates and to transform the tested controls' bound to screen as well..
And, of course it is up to you to decide if a selection must just hit (Rectangle.IntersectsWith()) or cover the targets completely; I have coded the latter (Rectangle.Contains()).
Child controls are always drawn after the parent control is drawn, so they will always show on top of anything you paint on the parent.
You will need to create a semi transparent child control and make sure the z-index of that child control is higher than the other controls.
Edit: This solution is not possible in WinForms. Or at least not with the standard controls.
In frameworks where this is possible, the control on top will likely block mouse and touch events from coming through.
I took the awesome code, from the answer above and been tweaking it. Made it so you can move the mouse in any direction and it'll draw & size the form accordingly. I confine the overlay, to the inside of the parent form. This is still a work, in progress.
public partial class Form1 : Form
{
Form overlay;
bool a, b;
int c, d, f, g;
Point ptNope, ptHold, ptTest, ptDown = Point.Empty;
List<Control> controlSelection = new List<Control>();
List<Control> getControls(Control container, Rectangle rect)
{
rect = RectangleToClient(rect);
controlSelection = new List<Control>();
foreach (Control ctl in container.Controls)
if (rect.Contains(ctl.Bounds))
{
controlSelection.Add(ctl);
foreach (Control ct in ctl.Controls) controlSelection.Add(ct);
}
return controlSelection;
}
public Form1()
{
InitializeComponent();
overlay = new Form()
{
FormBorderStyle = FormBorderStyle.None,
BackColor = Color.Fuchsia,
Opacity = 0.2f,
TopMost = true
};
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
ptNope.X = this.Left; ptNope.Y = this.Top;
ptDown = e.Location;
overlay.Show();
}
public static int vise(int value, int min, int max) { return (value < min) ? min : (value > max) ? max : value; }
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
ptHold = PointToScreen(ptDown);
ptTest = PointToScreen(e.Location);
a = (ptTest.X < ptHold.X); b = (ptTest.Y < ptHold.Y);
c = a ? ptTest.X : ptHold.X; c = vise(c, ptNope.X, ptNope.X + this.Width);
d = b ? ptTest.Y : ptHold.Y; d = vise(d, ptNope.Y, ptNope.Y + this.Height);
f = a ? (ptHold.X - ptTest.X) : (ptTest.X - ptHold.X); f = vise(f, 0, a ? (ptHold.X - ptNope.X) : ((ptNope.X + this.Width) - ptHold.X));
g = b ? (ptHold.Y - ptTest.Y) : (ptTest.Y - ptHold.Y); g = vise(g, 0, b ? (ptHold.Y - ptNope.Y) : ((ptNope.Y + this.Height) - ptHold.Y));
overlay.Left = c; overlay.Top = d; overlay.Width = f; overlay.Height = g;
controlSelection = getControls(this, Bounds);
foreach (Control c in controlSelection) { c.BackColor = SystemColors.Control; }
controlSelection = getControls(this, overlay.Bounds);
foreach (Control c in controlSelection) { c.BackColor = Color.Fuchsia; }
label1.Text = controlSelection.Count + " Control(s) grabbed";
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
overlay.Hide(); label1.Text = "";
}
}
Thoughts and ideas, for improvement are welcome.
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 have following problem, i want to generate a little canvas by a button click, after generating i want to move it by a key press event but i cant see the canvas in the event. How can i make it visible? (In the sourcecode of WPF not in XAML)
public void Button_Click_1(object sender, RoutedEventArgs e)
{
Canvas c = new Canvas();
c.Height = System.Windows.SystemParameters.PrimaryScreenHeight;
c.Width = System.Windows.SystemParameters.PrimaryScreenWidth;
c.Loaded += c_Loaded;
Grid.Children.Add(c);
Canvas ship = new Canvas();
ship.Background = Brushes.Aquamarine;
ship.Height = 30;
ship.Width = 30;
ship.KeyDown += ship_KeyDown;
Canvas.SetTop(ship, 50);
c.Children.Add(ship);
}
void ship_KeyDown(object sender, KeyEventArgs e)
{
canvas.Setleft(ship, canvas.Getleft(ship) +10); //here i can not see the object "ship" :(
}
Use parameter sender:
Canvas ship = (Canvas) sender;
You would need to add you c or ship to the layoutroot`
The easiest way is to add it to a <list> of UIElement and modify it with a foreach-loop
I have an application that gets one of its UI controls via an INativeHandleContract from a different AppDomain. When the size of the control changes the FrameworkElement in the host doesn't get an updated size. I was wondering if there was any way to fix this.
Here is the sample code (the XAML is just a blank window):
public partial class Window1 : Window
{
private StackPanel m_stackPanel;
private Expander m_expander;
private UIElement m_expanderAddIn;
public Window1()
{
InitializeComponent();
m_stackPanel = new StackPanel { Orientation = Orientation.Vertical };
m_stackPanel.Background = Brushes.Red;
m_expander = new Expander
{
ExpandDirection = ExpandDirection.Right,
Background=Brushes.Blue,
IsExpanded=true,
};
m_expander.Expanded += CheckStuff;
m_expander.Collapsed += CheckStuff;
Rectangle r = new Rectangle {Fill = Brushes.LightGray, Height = 300, Width = 300};
m_expander.Content = r;
m_expanderAddIn = FrameworkElementAdapters.ContractToViewAdapter(FrameworkElementAdapters.ViewToContractAdapter(m_expander));
m_stackPanel.Children.Add(m_expanderAddIn);
Content = m_stackPanel;
}
private void CheckStuff(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Expander: " + m_expander.DesiredSize);
Debug.WriteLine("Add in: " + m_expanderAddIn.DesiredSize);
Debug.WriteLine("Stack Panel: " + m_stackPanel.DesiredSize);
}
}
As you expand and collapse the Expander you would expect the height of the StackPanel to change but it doesn't. Any ideas would be useful, thanks.
That's really weird and I have no idea why it behaves like that. This is probably just a small example reproducing your problem so maybe this doesn't help you much but I got your example working with 3 changes.
Wrap the Expander in a "dummy" StackPanel and use that as the root element for m_expanderAddIn instead. This took care of the Height problem for the Expander.
Change the type of m_expanderAddIn from UIElement to FrameworkElement
Bind the Height of m_expanderAddIn to ActualHeight of m_expander
Code
public partial class Window1 : Window
{
private StackPanel m_stackPanel;
private Expander m_expander;
private FrameworkElement m_expanderAddIn;
public Window1()
{
InitializeComponent();
m_stackPanel = new StackPanel { Orientation = Orientation.Vertical };
m_stackPanel.Background = Brushes.Red;
m_expander = new Expander
{
ExpandDirection = ExpandDirection.Right,
Background=Brushes.Blue,
IsExpanded=true,
};
m_expander.Expanded += CheckStuff;
m_expander.Collapsed += CheckStuff;
Rectangle r = new Rectangle {Fill = Brushes.LightGray, Height = 300, Width = 300};
m_expander.Content = r;
StackPanel stackPanel = new StackPanel();
stackPanel.Children.Add(m_expander);
m_expanderAddIn = FrameworkElementAdapters.ContractToViewAdapter(FrameworkElementAdapters.ViewToContractAdapter(stackPanel));
Binding binding = new Binding("ActualHeight");
binding.Source = m_expander;
m_expanderAddIn.SetBinding(FrameworkElement.HeightProperty, binding);
m_stackPanel.Children.Add(m_expanderAddIn);
Content = m_stackPanel;
}
private void CheckStuff(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Expander: " + m_expander.DesiredSize);
Debug.WriteLine("Add in: " + m_expanderAddIn.DesiredSize);
Debug.WriteLine("Stack Panel: " + m_stackPanel.DesiredSize);
}
}