I am trying to make an app to generate a tierlist.
Each tier consists of a flowlayout and you basically move the controls around by drag-and-drop.
The problem is that even though I can move the elements with drag-and-drop, the element is always added to the end of the list, and not where you drop the pointer.
Is there any way to maintain order during drag-and-drop?
These are the methods that are in the flowlayout
private void flow_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Move;
}
private void flow_DragDrop(object sender, DragEventArgs e)
{
((UserControl1)e.Data.GetData(typeof(UserControl1))).Parent = (Panel)sender;
}
While these are from the usercontrol:
private void UserControl1_MouseDown(object sender, MouseEventArgs e)
{
this.DoDragDrop(this, DragDropEffects.Move);
}
You can opt this solution to reorder the dropped controls into a FlowLayoutPanel control. The part that utilizes the Control.ControlCollection.GetChildIndex and Control.ControlCollection.SetChildIndex methods.
Let's say you have a custom Control or UserControl named DragDropControl:
public class DragDropControl : Control
{
public DragDropControl()
{
AllowDrop = true;
BackColor = Color.LightSteelBlue;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
TextRenderer.DrawText(e.Graphics, Text, Font,
ClientRectangle, Color.Black,
TextFormatFlags.HorizontalCenter |
TextFormatFlags.VerticalCenter);
}
}
Note: From what I see in the images, just use a simple Label control instead.
Let's create a custom FlowLayoutPanel and encapsulate all the required functionalities to not repeat that in your implementation for each FLP.
public class DragDropFlowLayoutPanel : FlowLayoutPanel
{
public DragDropFlowLayoutPanel()
{
AllowDrop = true;
}
[DefaultValue(true)]
public override bool AllowDrop
{
get => base.AllowDrop;
set => base.AllowDrop = value;
}
The custom FLP implements the mouse, drag and drop events of its children as well.
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
if (e.Control is DragDropControl)
{
e.Control.DragOver += OnControlDragOver;
e.Control.DragDrop += OnControlDragDrop;
e.Control.MouseDown += OnControlMouseDown;
}
}
protected override void OnControlRemoved(ControlEventArgs e)
{
base.OnControlRemoved(e);
e.Control.DragOver -= OnControlDragOver;
e.Control.DragDrop -= OnControlDragDrop;
e.Control.MouseDown -= OnControlMouseDown;
}
Handle the child controls MouseDown event to do the drag:
private void OnControlMouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
var control = sender as DragDropControl;
DoDragDrop(control, DragDropEffects.Move);
}
}
Handle the DragEnter and DragOver methods and events of the FLP and its children to set the drop effect.
protected override void OnDragEnter(DragEventArgs e)
{
base.OnDragEnter(e);
if (e.Data.GetDataPresent(typeof(DragDropControl)))
e.Effect = DragDropEffects.Move;
}
protected override void OnDragOver(DragEventArgs e)
{
base.OnDragOver(e);
if (e.Data.GetDataPresent(typeof(DragDropControl)))
e.Effect = DragDropEffects.Move;
}
private void OnControlDragOver(object sender, DragEventArgs e)
{
if (e.Data.GetData(typeof(DragDropControl)) is DragDropControl ddc)
{
var p = PointToClient(new Point(e.X, e.Y));
if (GetChildAtPoint(p) == ddc)
e.Effect = DragDropEffects.None;
else
e.Effect = DragDropEffects.Move;
}
}
Finally, call from the DragDrop method override and event the DropControl method and pass the DragEventArgs param.
protected override void OnDragDrop(DragEventArgs e)
{
base.OnDragDrop(e);
DropControl(e);
}
private void OnControlDragDrop(object sender, DragEventArgs e)
{
DropControl(e);
}
The dropped control takes the index of the control under the mouse position if any otherwise it will be inserted at the end of the Controls collection.
private void DropControl(DragEventArgs e)
{
if (e.Data.GetData(typeof(DragDropControl)) is DragDropControl ddc)
{
var p = PointToClient(new Point(e.X, e.Y));
var child = GetChildAtPoint(p);
var index = child == null
? Controls.Count
: Controls.GetChildIndex(child);
ddc.Parent = this;
Controls.SetChildIndex(ddc, index);
}
}
}
Put it all together.
public class DragDropFlowLayoutPanel : FlowLayoutPanel
{
public DragDropFlowLayoutPanel()
{
AllowDrop = true;
}
[DefaultValue(true)]
public override bool AllowDrop
{
get => base.AllowDrop;
set => base.AllowDrop = value;
}
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
if (e.Control is DragDropControl)
{
e.Control.DragOver += OnControlDragOver;
e.Control.DragDrop += OnControlDragDrop;
e.Control.MouseDown += OnControlMouseDown;
}
}
protected override void OnControlRemoved(ControlEventArgs e)
{
base.OnControlRemoved(e);
e.Control.DragOver -= OnControlDragOver;
e.Control.DragDrop -= OnControlDragDrop;
e.Control.MouseDown -= OnControlMouseDown;
}
protected override void OnDragEnter(DragEventArgs e)
{
base.OnDragEnter(e);
if (e.Data.GetDataPresent(typeof(DragDropControl)))
e.Effect = DragDropEffects.Move;
}
protected override void OnDragOver(DragEventArgs e)
{
base.OnDragOver(e);
if (e.Data.GetDataPresent(typeof(DragDropControl)))
e.Effect = DragDropEffects.Move;
}
protected override void OnDragDrop(DragEventArgs e)
{
base.OnDragDrop(e);
DropControl(e);
}
private void OnControlDragOver(object sender, DragEventArgs e)
{
if (e.Data.GetData(typeof(DragDropControl)) is DragDropControl ddc)
{
var p = PointToClient(new Point(e.X, e.Y));
if (GetChildAtPoint(p) == ddc)
e.Effect = DragDropEffects.None;
else
e.Effect = DragDropEffects.Move;
}
}
private void OnControlDragDrop(object sender, DragEventArgs e)
{
DropControl(e);
}
private void OnControlMouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
var control = sender as DragDropControl;
DoDragDrop(control, DragDropEffects.Move);
}
}
private void DropControl(DragEventArgs e)
{
if (e.Data.GetData(typeof(DragDropControl)) is DragDropControl ddc)
{
var p = PointToClient(new Point(e.X, e.Y));
var child = GetChildAtPoint(p);
var index = child == null
? Controls.Count
: Controls.GetChildIndex(child);
ddc.Parent = this;
Controls.SetChildIndex(ddc, index);
}
}
}
Related
Let's assume the following Situation:
a Control (e.g. a Button) has an attached behavior to enable a Drag&Drop-Operation
<Button Content="test">
<i:Interaction.Behaviors>
<SimpleDragBehavior/>
</i:Interaction.Behaviors>
</Button>
And the SimpleDragBehavior
public class SimpleDragBehavior: Behavior<Button>
{
protected override void OnAttached ()
{
AssociatedObject.MouseLeftButtonDown += OnAssociatedObjectMouseLeftButtonDown;
AssociatedObject.MouseLeftButtonUp += OnAssociatedObjectMouseLeftButtonUp;
AssociatedObject.MouseMove += OnAssociatedObjectMouseMove;
mouseIsDown = false;
}
private bool mouseIsDown;
private void OnAssociatedObjectMouseMove (object sender, MouseEventArgs e)
{
if (mouseIsDown)
{
AssociatedObject.Background = new SolidColorBrush(Colors.Red);
DragDrop.DoDragDrop((DependencyObject)sender,
AssociatedObject.Content,
DragDropEffects.Link);
}
}
private void OnAssociatedObjectMouseLeftButtonUp (object sender, MouseButtonEventArgs e)
{
mouseIsDown = false;
}
private void OnAssociatedObjectMouseLeftButtonDown (object sender, MouseButtonEventArgs e)
{
mouseIsDown = true;
}
}
The task now is to determine when the drag ends, to restore the orignal backgound of the button.
This is no problem when droped on an drop-target. But how do i recognize a drop on something which isn't a drop-target? In the worst case: outside the window?
DragDrop.DoDragDrop returns after drag-and-drop operation is completed.
Yes, "Initiates a drag-and-drop operation" is confusing, since it could be read as "start drag-and-drop and return":
private void OnAssociatedObjectMouseMove (object sender, MouseEventArgs e)
{
if (mouseIsDown)
{
AssociatedObject.Background = new SolidColorBrush(Colors.Red);
var effects = DragDrop.DoDragDrop((DependencyObject)sender,
AssociatedObject.Content,
DragDropEffects.Link);
// this line will be executed, when drag/drop will complete:
AssociatedObject.Background = //restore color here;
if (effects == DragDropEffects.None)
{
// nothing was dragged
}
else
{
// inspect operation result here
}
}
}
I'm trying to move the control named pictureBox1 by dragging it around. The problem is, when it moves, it keeps moving from a location to another location around the mouse, but it does follow it...
This is my code. and I would really appreciate it if you could help me
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
bool selected = false;
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
selected = true;
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (selected == true)
{
pictureBox1.Location = e.Location;
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
selected = false;
}
}
All you need:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private Point MouseDownLocation;
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
MouseDownLocation = e.Location;
}
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
pictureBox1.Left = e.X + pictureBox1.Left - MouseDownLocation.X;
pictureBox1.Top = e.Y + pictureBox1.Top - MouseDownLocation.Y;
}
}
}
You can also use the extension:
public static class CmponentsExtensions
{
//Management of mouse drag and drop
#region Menu and Mouse
private static bool mouseDown;
private static Point lastLocation;
/// <summary>
/// To enable control to be moved around with mouse
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="control"></param>
public static void moveItselfWithMouse<T>(this T control) where T: Control
{
control.MouseDown += (o, e)=> { mouseDown = true; lastLocation = e.Location; };
control.MouseMove += (o, e) =>
{
if (mouseDown)
{
control.Location = new Point((control.Location.X - lastLocation.X) + e.X, (control.Location.Y - lastLocation.Y) + e.Y);
control.Update();
}
};
control.MouseUp += (o, e) => { mouseDown = false; } ;
}
public static void moveOtherWithMouse<T>(this T control, Control movedObject) where T : Control
{
control.MouseDown += (o, e) => { mouseDown = true; lastLocation = e.Location; };
control.MouseMove += (o, e) =>
{
if (mouseDown)
{
movedObject.Location = new Point((movedObject.Location.X - lastLocation.X) + e.X, (movedObject.Location.Y - lastLocation.Y) + e.Y);
movedObject.Update();
}
};
control.MouseUp += (o, e) => { mouseDown = false; };
}
#endregion
}
Then you need to use it with some control:
In this case pictureBox1 moved the whole Form
pictureBox1.moveOtherWithMouse(this);
In this case you move only pictureBox:
pictureBox1.moveItselfWithMouse();
try this to move pictureBox control at runtime using mouse
private void pictureBox7_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
xPos = e.X;
yPos = e.Y;
}
}
private void pictureBox7_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
PictureBox p = sender as PictureBox;
if (p != null)
{
if (e.Button == MouseButtons.Left)
{
p.Top += (e.Y - yPos);
p.Left += (e.X - xPos);
}
}
}
Is it possible to unsubscribe an event handler from an protected override void?
protected override void OnViewLoaded(object sender, ViewLoadedEventArg e)
{
base.OnViewLoaded(sender, e);
list = VisualTreeUtil.FindFirstInTree<ListView>(Application.Current.MainWindow, "ListView");
ConfigureAndSuperviseInputControls(this.list);
ScrollViewer scroll = VisualTreeUtil.FindFirstInTree<ScrollViewer>(this.list);
scroll.ScrollChanged+=new ScrollChangedEventHandler(scroll_ScrollChanged);
}
void scroll_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
ConfigureAndSuperviseInputControls(this.list);
ScrollViewer sb = e.OriginalSource as ScrollViewer;
if (sb.ContentVerticalOffset==sb.ScrollableHeight)
{
scroll.ScrollChanged-=new ScrollChangedEventHandler(scroll_ScrollChanged);
}
}
My problem is that i dont get acces to the scroll obejct in the scroll_ScrollChanged Method.
This code is not tested but can't you simple cast the sender object to the ScrollViewer and unsubscribe from the event like that:
protected override void OnViewLoaded(object sender, ViewLoadedEventArg e)
{
base.OnViewLoaded(sender, e);
list = VisualTreeUtil.FindFirstInTree<ListView>(Application.Current.MainWindow, "ListView");
ConfigureAndSuperviseInputControls(this.list);
ScrollViewer scroll = VisualTreeUtil.FindFirstInTree<ScrollViewer>(this.list);
scroll.ScrollChanged+=new ScrollChangedEventHandler(scroll_ScrollChanged);
}
void scroll_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
ConfigureAndSuperviseInputControls(this.list);
ScrollViewer scroll = (ScrollViewer)sender;
if (scroll.ContentVerticalOffset==scroll.ScrollableHeight)
{
scroll.ScrollChanged-=new ScrollChangedEventHandler(scroll_ScrollChanged);
}
}
Sender should be a reference to the object that you require.
protected override void OnViewLoaded(object sender, ViewLoadedEventArg e)
{
base.OnViewLoaded(sender, e);
list = VisualTreeUtil.FindFirstInTree<ListView>(Application.Current.MainWindow, "ListView");
ConfigureAndSuperviseInputControls(this.list);
ScrollViewer scroll = VisualTreeUtil.FindFirstInTree<ScrollViewer>(this.list);
scroll.ScrollChanged+=new ScrollChangedEventHandler(scroll_ScrollChanged);
}
void scroll_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
ConfigureAndSuperviseInputControls(this.list);
ScrollViewer sb = sender as ScrollViewer;
if (sb.ContentVerticalOffset==sb.ScrollableHeight)
{
scroll.ScrollChanged-=new ScrollChangedEventHandler(scroll_ScrollChanged);
}
}
How can i prevent the right click from selecting an item in my listview both in click and double click?
You could use this code, I think it should do the work. You need to set some bool variable to indicate that the right mouse has been clicked in your MouseDown, then Clear selected items, if SelectedIndexChanged event handler fired because of the right click and then reset the indicator on MouseUp event. Check the code:
bool rightClicked = false;
private void listView1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
rightClicked = true;
}
else
{
rightClicked = false;
}
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
if (rightClicked)
{
listView1.SelectedItems.Clear();
}
}
private void listView1_MouseUp(object sender, MouseEventArgs e)
{
rightClicked = false;
}
EDIT: This is the best I could do, it preserves the selection but flickers. the solution could be implemented using some custom drawing of the items but that requires too much time. I leave that to you.
bool rightClicked = false;
int [] lviListIndex = null;
private void listView1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
rightClicked = true;
lviListIndex = new int[listView1.SelectedItems.Count];
listView1.SelectedIndices.CopyTo(lviListIndex, 0);
}
else
{
rightClicked = false;
}
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
if (rightClicked)
{
listView1.SelectedIndices.Clear();
}
}
private void listView1_MouseUp(object sender, MouseEventArgs e)
{
if (rightClicked)
{
listView1.SelectedIndexChanged -= new System.EventHandler(listView1_SelectedIndexChanged);
if (lviListIndex != null)
{
foreach (int index in lviListIndex)
{
listView1.SelectedIndices.Add(index);
}
}
lviListIndex = null;
listView1.SelectedIndexChanged += new System.EventHandler(listView1_SelectedIndexChanged);
}
rightClicked = false;
}
Subclass the listview and add this override:
protected override void WndProc(ref Message m)
{
const int WM_RBUTTONUP = 0x0205;
const int WM_RBUTTONDOWN = 0x0204;
if ((m.Msg != WM_RBUTTONDOWN) && (m.Msg != WM_RBUTTONUP))
{
base.WndProc(ref m);
}
}
This works by ignoring messages from the right button of the mouse.
you can check like this if (e.Button == MouseButtons.Left)
I just wanna find out if there's a way I could minimize code clutter in my application.
I have written code/s similar to this:
private void btnNext_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
btnNext.Opacity = 1;
}
private void btnNext_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
btnNext.Opacity = 0.5;
}
private void btnShowAll_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
btnShowAll.Opacity = 1;
}
private void btnShowAll_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
btnShowAll.Opacity = 0.5;
}
private void btnPrev_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
btnPrev.Opacity = 1;
}
private void btnPrev_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
btnPrev.Opacity = 0.5;
}
private void btnSearch_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
btnSearch.Opacity = 1;
}
private void btnSearch_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
btnSearch.Opacity = 0.5;
}
private void btnSearchStore_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
btnSearchStore.Opacity = 1;
}
private void btnSearchStore_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
btnSearchStore.Opacity = 0.5;
}
private void btnCloseSearch_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
btnCloseSearch.Opacity = 1;
}
private void btnCloseSearch_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
btnCloseSearch.Opacity = 0.5;
}
private void btnHome_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
btnHome.Opacity = 1;
}
private void btnHome_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
btnHome.Opacity = 0.5;
}
and so on and so forth...
Do I need to create a 'function' that will run initially? Or do I have to create another class just so I can 'organize' them?
Any suggestions?
You could rewrite all those functions into 2:
private void FadeBtn_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
Button btn = (Button)sender;
btn.Opacity = 1;
}
private void FadeBtn_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
Button btn = (Button)sender;
btn.Opacity = 0.5;
}
And then point all of the buttons MouseEnter and MouseLeave events to those functions.
You need to have ChangeButtonOpacity method:
private void ChangeButtonOpacity(Button button, double newOpacity)
{
button.Opacity = newOpacity;
}
And you can implement your handlers as:
private void btn_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
ChangeButtonOpacity((Button)sender, 1);
}
private void btn_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
ChangeButtonOpacity((Button)sender, 0.5);
}
In this way you will need only two handlers.
Create a Mouse Enter Event and register all the buttons with it. Inside the method you will notice I cast the sender object as a button. So what ever button calls it you can perform this opacity action on.
private void ButtonMouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
Button button = (Button) sender;
button.Opacity = 1;
}
As far I can see, in your case you can shorten to:
private void btn_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
(sender as Button).Opacity = 1;
}
private void btn_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
(sender as Button).Opacity = 0.5;
}
In the designer, you can choose these event handlers then instead of creating new ones for each button.
Perhaps you can use the Tag property of the button if your not using it for anything else, Then you can do the following.
private void btn_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
(sender as Button).Opacity = (double)((sender as Button).Tag);
}
private void btn_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
(sender as Button).Opacity = 0.5;
}
This would allow you to setup different values for different buttons using only two handlers.