how to make user control draggable only from specific places - c#

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.

Related

MouseDragPopupBehavior that is constrained to the window

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();
};
}
}

Switching Panels via Index Methods

I've been trying to solve my issue for quite a while and to be honest am getting nowhere. What i would like is when the user clicks the 'top' button on my panel it automatically goes to the top( and swaps with the one there.) and when they click the bottom button it automatically goes to the bottom. I'm setting the index panel manually but of course this doesnt work because its only viable for one panel (i have ten). Greatly appreciate some help in finding a method that can send the panel to the top of the stack regardless of its position.
Here is a image (basic) to help understand
Control ctrlToMove = (Control)this.bookControls[bookName];
int ctrlToMoveIndex = bookPanel.Controls.IndexOf(ctrlToMove);
int ctrlToSwapIndex = ctrlToMoveIndex - 5;
Control ctrlToSwap = bookPanel.Controls[ctrlToSwapIndex];
this.bookPanel.Controls.SetChildIndex(ctrlToMove, ctrlToSwapIndex);
this.bookPanel.Controls.SetChildIndex(ctrlToSwap, ctrlToMoveIndex);
Based on your drawing, I made a UserControl with a button on it:
void uc_ButtonClicked(object sender, EventArgs e) {
UserControl1 uc = sender as UserControl1;
if (uc != null) {
int childIndex = flowLayoutPanel1.Controls.GetChildIndex(uc);
if (childIndex > 0) {
UserControl1 ucTop = flowLayoutPanel1.Controls[0] as UserControl1;
flowLayoutPanel1.Controls.SetChildIndex(uc, 0);
flowLayoutPanel1.Controls.SetChildIndex(ucTop, childIndex);
}
}
}
According to your picture you have one control per row in panel. Thus I suggest you to use TableLayoutPanel instead of FlowLayoutPanel. Also I'd create user control for items in panel. E.g. it will have name PriorityUserControl and four buttons to increase, decrease, maximize, minimize it's 'priority' (I placed buttons horizontally just to save place on screen):
Next, create four events in this user control:
public event EventHandler PriorityMaximized;
public event EventHandler PriorityIncreased;
public event EventHandler PriorityDecreased;
public event EventHandler PriorityMinimized;
And rise appropriate event when button clicked:
private void topButton_Click(object sender, EventArgs e)
{
if (PriorityMaximized != null)
PriorityMaximized(this, EventArgs.Empty);
}
That's it. We have user control which tells whether it want to move up or down. Now add user controls to TableLayoutPanel (either manually or dynamically) and subscribe same event handlers of these four events to ALL user controls. Something like:
// create user control and attach event handlers
PriorityUserControl control = new PriorityUserControl();
control.PriorityMaximized += priorityUserControl_PriorityMaximized;
control.PriorityMinimized += priorityUserControl_PriorityMinimized;
control.PriorityIncreased += priorityUserControl_PriorityIncreased;
control.PriorityDecreased += priorityUserControl_PriorityDecreased;
// add another row to table
panel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
panel.RowCount = panel.RowStyles.Count;
// add control table layout panel
panel.Controls.Add(control);
panel.SetRow(control, panel.RowCount - 1);
Good. All you should do now is implement these event handlers. It's simple. E.g. decreasing priority (i.e. moving down):
private void priorityUserControl_PriorityDecreased(object sender, EventArgs e)
{
// sender is a control where you clicked Down button
Control currentControl = (Control)sender;
// get position in panel
var position = panel.GetPositionFromControl(currentControl);
// just to be sure control is not one at the bottom
if (position.Row == panel.RowCount - 1)
return;
// we want to switch with control beneath current
Control controlToSwitch = panel.GetControlFromPosition(0, position.Row + 1);
// move both controls
panel.SetRow(currentControl, position.Row + 1);
panel.SetRow(controlToSwitch, position.Row);
}
Now implementation of maximizing priority (i.e. moving to top):
private void priorityUserControl_PriorityMaximized(object sender, EventArgs e)
{
Control currentControl = (Control)sender;
var position = panel.GetPositionFromControl(currentControl);
if (position.Row == 0 || panel.RowCount < 2)
return;
Control topControl = panel.GetControlFromPosition(0, 0);
panel.SetRow(currentControl, 0);
panel.SetRow(topControl, position.Row);
}
I believe you will create rest two handlers by yourself.
The key of what you want is setting up a clear and extendable algorithm capable to deal with the different positions of the Panels. Here you have a simple code showing certain approach to this problem:
public partial class Form1 : Form
{
int[] panelLocations;
Point[] pointLocations;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
panelLocations = new int[5];
pointLocations = new Point[5];
panelLocations[1] = 1;
panelLocations[2] = 2;
panelLocations[3] = 3;
pointLocations[1] = new Point(panel1.Left, panel1.Top);
pointLocations[2] = new Point(panel2.Left, panel2.Top);
pointLocations[3] = new Point(panel3.Left, panel3.Top);
}
private void relocate(int curPanel, bool goTop)
{
int curLoc = panelLocations[curPanel];
int newLoc = curLoc - 1;
if (!goTop)
{
newLoc = curLoc + 1;
}
if (newLoc < 1) newLoc = 3;
if (newLoc > 3) newLoc = 1;
if (newLoc != curLoc)
{
int otherIndex = Array.IndexOf(panelLocations, newLoc);
panelLocations[curPanel] = newLoc;
relocatePanel(curPanel);
panelLocations[otherIndex] = curLoc;
relocatePanel(otherIndex);
}
}
private void relocatePanel(int curIndex)
{
if (curIndex == 1)
{
panel1.Location = pointLocations[panelLocations[1]];
}
else if (curIndex == 2)
{
panel2.Location = pointLocations[panelLocations[2]];
}
else if (curIndex == 3)
{
panel3.Location = pointLocations[panelLocations[3]];
}
}
private void buttonTop1_Click(object sender, EventArgs e)
{
relocate(1, true);
}
private void buttonBottom1_Click(object sender, EventArgs e)
{
relocate(1, false);
}
}
Open a new project, add 3 panels (Panel1, Panel2 and Panel3... better put different background colors) and include two buttons (buttonUp and buttonDown). This code will make the Panel1 to go up and down (by changing its position with the other panels).
The idea is pretty simple: at the start you store the positions of all the Panels in an array. In another array, you store where each panel is located every time (1 is the original position of Panel1, etc.).
It is a quite simple code which you can improve and extend as much as required, but the idea is pretty reliable and you can use it in any case: a set of fixed positions through which the panels will be moving.

Adding UI components dynamically to my Winform

I'm trying to create components on the fly, so, I know how to make this, but, how can I access this component on the fly?
For example:
public Form1
{
Label label1 = new Label();
label1.AutoSize = true;
label1.Location = new System.Drawing.Point(e.X, e.Y);
label1.Name = string.Format("label{0}", labelsCount.ToString());
label1.Size = new System.Drawing.Size(35, 13);
label1.TabIndex = 2;
label1.Text = string.Format("Label -> {0}", labelsCount.ToString());
label1.Click += new System.EventHandler(this.label1_Click);
this.Controls.Add(label1);
label1.BringToFront();
label1.Show();
labelsCount++;
}
When I click on the label, I want to get the label's information (like position, text and name)
How I can do this? Or, what is the best way to do this?
And, to access the component based on position of the panel, inside of form, how I can do this?
Sender of event is your lablel. Simply cast sender object to Label type:
void label1_Click(object sender, EventArgs e)
{
Label label = (Label)sender;
// use
// label.Name
// label.Location
}

How can i make the Canvas in a event visible?

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

Position of dragged&dropped element (MouseDragElementBehavior)

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...

Categories