WPF Fast Click Events become a Drag-Event - c#

I have the problem, that fast clicks become a drag-event.
I use the preventMouseButtonDown event to get the start position:
private void previewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_pointStartDrag = e.GetPosition(null);
}
The previewMouseMove-event delivers the actual position. I compare the two points to trigger my drag-event.
Vector d = _pointStartDrag - e.GetPosition(null);
if( |d.x| > minDx || |d.y| > minDy)
//here is my drag-event
Whatever minDx/y is set, it is reached by fast clicking two different poinst on my gui.
I don't know what went wrong. Any ideas?

I just answered this problem a day or two ago, but I can't find the question now, so I'll just do it again. You can use the SystemParameters.MinimumHorizontalDragDistance and SystemParameters.MinimumVerticalDragDistance properties for this purpose:
private bool IsConfirmedDrag(Point point)
{
bool horizontalMovement = Math.Abs(point.X - dragStartPosition.X) >
SystemParameters.MinimumHorizontalDragDistance;
bool verticalMovement = Math.Abs(point.Y - dragStartPosition.Y) >
SystemParameters.MinimumVerticalDragDistance;
return (horizontalMovement | verticalMovement);
}
It is used like this:
private void DragSourcePreviewMouseMove(object sender, MouseEventArgs e)
{
if (isMouseDown && IsConfirmedDrag(e.GetPosition(sender as ListBox)))
{
isMouseDown = false;
...
// Start Drag operation
}
}

Related

C# Custom display arrangement settings

So I am trying to make a little utility for managing my screens, I want to add a screen rearrangement system like in windows, see here: Windows Display Rearangment, I already have code worked out for getting and setting the exact screen coordinates I need, however I need to work out some sort of rearrangement dragging system that "snaps" to position like the windows implementation.
so far I have made some basic strides towards this, before coming to the realization the snapping will be a bit challenging, so here is the code I have so far, and here is an attached screenshot of the program as is. It consists of a panel with 3 panels inside it. For now I would like tips on when the mouse is released and the collision checks are run to snap similarly to windows display rearangment, Just a test bed for now, I am aware that this example of my test bed will currently only work with the dragging of the 3rd panel testing against the other 2 static panels, I will implement the functionality for all screens once we solve the issue :) Current
private Point MouseDownLocation;
private void panel2_MouseMove(object sender, MouseEventArgs e)
{
var pan = (Panel)sender;
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
pan.Left = e.X + pan.Left - MouseDownLocation.X;
pan.Top = e.Y + pan.Top - MouseDownLocation.Y;
}
}
private void panel2_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
var pan = (Panel)sender;
List<Panel> allpanels = new List<Panel>();
foreach (Panel pancomp in panel1.Controls)
{
if (pancomp == pan)
{
continue;
}
allpanels.Add(pancomp);
}
//var pan = (Panel)sender;
List<Panel> collisions = new List<Panel>();
foreach (Panel pancomp in allpanels)
{
if (pancomp == pan)
{
continue;
}
if (pan.Bounds.IntersectsWith(pancomp.Bounds))
{
//PUSH AWAY
collisions.Add(pancomp);
//listBox1.Items.Add("collided!");//panel2.Location.ToString());
}
}
collisions = collisions;
if (collisions.Count > 0)
{
//WE HAVE COLLISIONS LETS SNAP TO VALID PLACE
//MouseDownLocation
}
else
{
//WE HAVE NO COLLISIONS LETS SNAP TO PLACE
}
}
}
private void panel2_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
MouseDownLocation = e.Location;
}
}
Any advice would be greatly appreciated :)
Regards
James

Click and hold on textblock while dragging mouse to make value bigger/smaller

I want the same thing as in this Question:
Click and hold on button while dragging mouse to make value bigger/smaller
The only answer there was the Slider but that was not what the Question asker meant. And nor what i meant.
I know something like this is possible in iOs, but i want to use it in a windows app, but i cant seem to figure out how to do it.
I tried doing it with pointerpressed, pointerrelease and pointermoved. And it worked in a way but i stops working when you get out of the range from the textblock so it does only work when you drag on the textblock itself, and i want to do it so that you can drag over the whole screen if necessary.
this is what i have so far:
private void TextBlock_PointerPressed(object sender, PointerRoutedEventArgs e)
{
ystart = e.GetCurrentPoint(this).Position.Y;
clicked = true;
}
private void TextBlock_PointerReleased(object sender, PointerRoutedEventArgs e)
{
clicked = false;
}
private void TextBlock_PointerMoved(object sender, PointerRoutedEventArgs e)
{
if(clicked == true)
{
yend = e.GetCurrentPoint(this).Position.Y;
double difference;
int textgetal = Convert.ToInt32(Block.Text);
difference = ystart - yend;
textgetal = textgetal + (Convert.ToInt32(difference) / 10);
Block.Text = Convert.ToString(textgetal);
}
}
and like i said this works, but only inside the range of the textblock and not the whole screen like i want it.

DoDragDrop Stop/Cancel?

Forgive the rather poor code here.
I have a picture box PictureBox and a section Panel. I am trying to start a drag and drop operation so you click and drag the picture box, and dropping it into the Section creates a copy. This is fine.
But the problem is, lets say you click the picture box 9 times, no drag, just clicks. Then on the 10th turn, you do a correct drag and drop operation.... Then you get 10 duplicates added to the Section.
I am guessing that I need to respond to when a DragAndDrop is invalid and stop the drag and drop operation to prevent these duplicate buildups, but I do not know where to look to achieve this.
private void collectablePictureBox_MouseDown(object sender, MouseEventArgs e)
{
this.SelectedSection.AllowDrop = true;
this.SelectedSection.DragEnter += new DragEventHandler(this.CollectableSelectedSection_DragEnter);
this.SelectedSection.DragDrop += new DragEventHandler(this.CollectableSelectedSection_DragDrop);
this.collectablePictureBox.DoDragDrop(this.SelectedClassModel, DragDropEffects.Copy);
}
private void CollectablePictureBox_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
{
Console.WriteLine(e.Action);
}
private void CollectableSelectedSection_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Copy;
}
private void CollectableSelectedSection_DragDrop(object sender, DragEventArgs e)
{
Point position = this.SelectedSection.PointToClient(new Point(e.X, e.Y));
position.X -= this.SelectedClassModel.Width >> 1;
position.Y -= this.SelectedClassModel.Height >> 1;
this.SelectedSection.AllowDrop = false;
this.SelectedSection.DragEnter -= this.CollectableSelectedSection_DragEnter;
this.SelectedSection.DragDrop -= this.CollectableSelectedSection_DragDrop;
this.SelectedSection.AddItem(this.SelectedClassModel, position, this.SectionCanvasSnapToGridCheckBox.Checked);
}
Your problem comes from the fact that on every MouseDown event you add handlers to the appropriate events, so when you actually do the Drag and Drop those handlers will be called more than once. Currently I see two different ways of solving this:
One way to solve the problem would be to not start the Drag and Drop on the MouseDown event handler, but - based on this MSDN article - start it in a MouseMove handler instead, like this:
private void collectablePictureBox_MouseMove(object sender, MouseEventArgs e)
{
if (sender != null && e.LeftButton == MouseButtonState.Pressed)
{
this.SelectedSection.AllowDrop = true;
this.SelectedSection.DragEnter += new DragEventHandler(this.CollectableSelectedSection_DragEnter);
this.SelectedSection.DragDrop += new DragEventHandler(this.CollectableSelectedSection_DragDrop);
this.collectablePictureBox.DoDragDrop(this.SelectedClassModel, DragDropEffects.Copy);
}
}
Another way would be to also handle the MouseUp event of the PictureBox, and do a similar cleanup as in your CollectableSelectedSection_DragDrop handler.

C# WPF Capture Keyboard and Mouse

So I have a WPF Window that captures mouse events on an image. I am doing this by the following code:
<Image Name="imgPrimaryImage" Width="512" Height="512" RenderOptions.BitmapScalingMode="NearestNeighbor" Margin="5"
Source="{Binding Path=ImageMgr.ImageSource}"
MouseLeftButtonDown="OnMouseLeftButtonDown"
MouseMove="OnMouseMove"
MouseLeftButtonUp="OnMouseLeftButtonUp"/>
Application Functionality: While the user moves the mouse left and right it changes the size of the image so long as they have the left mouse button pressed.
Question: Is is possible to also capture keyboard events while capturing the mouse move event.
End Result: I want to be able to change the mouse speed based on CTRL and SHIFT pressed. I have the code I need to change the mouse speed, I am just wondering how I can get it so that if the user is holding CTRL while they left click and drag on the image it changes the speed.
If anyone has any insight on this (i.e. articles, literature, or advice) that would be excellent. Thank you and if there is any additional information needed please let me know.
To sum up comments if you want to check state of keyboard keys you can use Keyboard class which provides IsKeyDown method
var isShift = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
var isCtrl = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
var isAlt = Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt);
or use its Modifiers property
var isShift = Keyboard.Modifiers.HasFlag(ModifierKeys.Shift);
var isCtrl = Keyboard.Modifiers.HasFlag(ModifierKeys.Control);
var isAlt = Keyboard.Modifiers.HasFlag(ModifierKeys.Alt);
Set boolean flags based on what keys are pressed in the key pressed event.
In the OnMouseMove record the mouse position if null. Otherwise calculate the distance traveled, and amplify it or dampen it based on the speed up or slow down flags you've set already.
To dampen or amplify, once you have the X and Y change from the last point, multiply by 2, or divide by 2... (you can choose your own numbers), now add the new YX change to the current mouse XY coordinates and set the mouse position.
Here is what the MouseMove would look like, and some of the private variables needed. In my Example you have to have Forms included as a reference. I did not include Forms in my Include statements, because it mucks up IntelliSense in WPF applications. You will still need to maintain those _speedUp and _slowDown variables with your KeyDown events
private bool entering = true;
private Point _previousPoint;
private bool _speedUp;
private bool _slowDown;
private double _speedMod = 2;
private double _slowMod = .5;
private void OnMouseMove(object sender, MouseEventArgs e)
{
Point curr = new Point(System.Windows.Forms.Cursor.Position.X, System.Windows.Forms.Cursor.Position.Y);
if (entering)
{
_previousPoint = curr;
entering = false;
}
if (_previousPoint == curr)
return; // The mouse hasn't really moved
Vector delta = curr - _previousPoint;
if (_slowDown && !_speedUp)
delta *= _slowMod;
else if (_speedUp && !_slowDown)
delta *= _speedMod;
else
{
_previousPoint = curr;
return; //no modifiers... lets not do anything
}
Point newPoint = _previousPoint + delta;
_previousPoint = newPoint;
//Set the point
System.Windows.Forms.Cursor.Position = new System.Drawing.Point((int)newPoint.X, (int)newPoint.Y);
}
EDIT: I put the key down events in my window definition, and it works just fine. Although as pointed out in the comments of this thread, using Keyboard.IsKeyDown is much simpler. I also edited the code above to not cause weird jumping issues
private void Window_KeyDown(object sender, KeyEventArgs e)
{
_slowDown = true;
if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl)
_slowDown = true;
else if (e.Key == Key.LeftShift || e.Key == Key.RightShift)
_speedUp = true;
}
private void Window_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl)
_slowDown = false;
else if (e.Key == Key.LeftShift || e.Key == Key.RightShift)
_speedUp = false;
}
private void Window_MouseLeave(object sender, MouseEventArgs e)
{
entering = true;
}

Reorder a winforms listbox using drag and drop?

Is this a simple process?
I'm only writing a quick hacky UI for an internal tool.
I don't want to spend an age on it.
Here's a quick down and dirty app. Basically I created a Form with a button and a ListBox. On button click, the ListBox gets populated with the date of the next 20 days (had to use something just for testing). Then, it allows drag and drop within the ListBox for reordering:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.listBox1.AllowDrop = true;
}
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i <= 20; i++)
{
this.listBox1.Items.Add(DateTime.Now.AddDays(i));
}
}
private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
if (this.listBox1.SelectedItem == null) return;
this.listBox1.DoDragDrop(this.listBox1.SelectedItem, DragDropEffects.Move);
}
private void listBox1_DragOver(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Move;
}
private void listBox1_DragDrop(object sender, DragEventArgs e)
{
Point point = listBox1.PointToClient(new Point(e.X, e.Y));
int index = this.listBox1.IndexFromPoint(point);
if (index < 0) index = this.listBox1.Items.Count-1;
object data = e.Data.GetData(typeof(DateTime));
this.listBox1.Items.Remove(data);
this.listBox1.Items.Insert(index, data);
}
7 Years Late. But for anybody new, here is the code.
private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
if (this.listBox1.SelectedItem == null) return;
this.listBox1.DoDragDrop(this.listBox1.SelectedItem, DragDropEffects.Move);
}
private void listBox1_DragOver(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Move;
}
private void listBox1_DragDrop(object sender, DragEventArgs e)
{
Point point = listBox1.PointToClient(new Point(e.X, e.Y));
int index = this.listBox1.IndexFromPoint(point);
if (index < 0) index = this.listBox1.Items.Count - 1;
object data = listBox1.SelectedItem;
this.listBox1.Items.Remove(data);
this.listBox1.Items.Insert(index, data);
}
private void itemcreator_Load(object sender, EventArgs e)
{
this.listBox1.AllowDrop = true;
}
The first time it takes a few hours if you never implemented drag and drop, want to get it done right and have to read through the docs. Especially the immediate feedback and restoring the list if the user cancels the operation require some thoughts. Encapsulating the behavior into a reusable user control will take some time, too.
If you have never done drag and drop at all, have a look at this drag and drop example from the MSDN. This would be a good starting point and it should take you maybe half a day to get the thing working.
This relies on #BFree's answer above - thanks it helped a lot.
I ran into an error when trying to use the solution because I was using a DataSource for my listbox. Just for completeness, you get this error if you try to remove or add an item to the listbox directly:
// Causes error
this.listBox1.Items.Remove(data);
Error:
System.ArgumentException: 'Items collection cannot be modified when the DataSource property is set.'
Solution: Update the datasource itself, and then rebind to your listbox. Program.SelectedReports is a BindingList.
Code:
private void listboxSelectedReports_DragDrop(object sender, DragEventArgs e)
{
// Get the point where item was dropped.
Point point = listboxSelectedReports.PointToClient(new Point(e.X, e.Y));
// Get the index of the item where the point was dropped
int index = this.listboxSelectedReports.IndexFromPoint(point);
// if index is invalid, put item at the end of the list.
if (index < 0) index = this.listboxSelectedReports.Items.Count - 1;
// Get the item's data.
ReportModel data = (ReportModel)e.Data.GetData(typeof(ReportModel));
// Update the property we use to control sorting within the original datasource
int newSortOrder = 0;
foreach (ReportModel report in Program.SelectedReports) {
// match sorted item on unique property
if (data.Id == report.Id)
{
report.SortOrder = index;
if (index == 0) {
// only increment our new sort order if index is 0
newSortOrder += 1;
}
} else {
// skip our dropped item's index
if (newSortOrder == index) {
newSortOrder += 1;
}
report.SortOrder = newSortOrder;
newSortOrder += 1;
}
}
// Sort original list and reset the list box datasource.
// Note: Tried other things, Reset(), Invalidate(). Updating DataSource was only way I found that worked??
Program.SelectedReports = new BindingList<ReportModel>(Program.SelectedReports.OrderBy(x => x.SortOrder).ToList());
listboxSelectedReports.DataSource = Program.SelectedReports;
listboxSelectedReports.DisplayMember = "Name";
listboxSelectedReports.ValueMember = "ID";
}
Other notes:
BindingList is under this namespace:
using System.ComponentModel;
When dynamically adding items to the list, make sure you populate your sorting property. I used an integer field 'SortOrder'.
When you remove an item, I don't have to worry about updating the Sorting property, as it will just create a number gap which is ok in my situation, YMMV.
To be honest, there could be a better sorting algorithm other than a foreach loop, but in my situation, I am dealing with a very limited number of items.
An alternative is using the list-view control, which is the control Explorer uses to display the contents of folders. It is more complicated, but implements item dragging for you.

Categories