I have a winform on which i want to allow the user to move a control.
The control is (for now) a vertical line : label with border and a width of 1.
The context is not very important but i'll give it to you anyways. I have a background with some graphics and i'd like the user to be able to slide a guideline above the graphics. The graphics are made with the NPlots library. It looks something like this:
http://www.ibme.de/pictures/xtm-window-graphic-ramp-signals.png
If i can find out how the user can click and drag the label/line control around the screen, i can solve my guideline problem. Please help.
The code for this can get a bit complex, but essentially you will need to capture the MouseDown, MouseMove, and MouseUp events on your form. Something like this:
public void Form1_MouseDown(object sender, MouseEventArgs e)
{
if(e.Button != MouseButton.Left)
return;
// Might want to pad these values a bit if the line is only 1px,
// might be hard for the user to hit directly
if(e.Y == myControl.Top)
{
if(e.X >= myControl.Left && e.X <= myControl.Left + myControl.Width)
{
_capturingMoves = true;
return;
}
}
_capturingMoves = false;
}
public void Form1_MouseMove(object sender, MouseEventArgs e)
{
if(!_capturingMoves)
return;
// Calculate the delta's and move the line here
}
public void Form1_MouseUp(object sender, MouseEventArgs e)
{
if(_capturingMoves)
{
_capturingMoves = false;
// Do any final placement
}
}
In WinForms, you can handle the MouseDown, MouseMove and MouseUp events of a control. On MouseDown, set some bit or reference telling your form what control the mouse was clicked on, and capture the X and Y of the mouse from MouseEventArgs. On MouseMove, if a control is set, adjust its X and Y by the difference between your last captured X and Y and the current coordinates. On MouseUp, release the control.
I would set up an "edit mode" for this; when the user enters this mode, the current event handlers of your form's controls should be detached, and the movement handlers attached. If you want to persist or revert these changes (like you're making a custom form designer your client can use to customize window layouts), you'll also need to be able to take some sort of snapshot of the before and after layouts of the controls.
I wrote a component to do exactly that: move a control on a form (or move a borderless form on the screen). You can even use it from the designer, without writing any code.
http://www.thomaslevesque.com/2009/05/06/windows-forms-automatically-drag-and-drop-controls-dragmove/
Here's an extension method that you can use for any control. It uses Rx and is based on the A Brief Introduction to the Reactive Extensions for .NET, Rx post and sample by Wes Dyer.
public static class FormExtensions
{
public static void EnableDragging(this Control c)
{
// Long way, but strongly typed.
var downs =
from down in Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
eh => new MouseEventHandler(eh),
eh => c.MouseDown += eh,
eh => c.MouseDown -= eh)
select new { down.EventArgs.X, down.EventArgs.Y };
// Short way.
var moves = from move in Observable.FromEvent<MouseEventArgs>(c, "MouseMove")
select new { move.EventArgs.X, move.EventArgs.Y };
var ups = Observable.FromEvent<MouseEventArgs>(c, "MouseUp");
var drags = from down in downs
from move in moves.TakeUntil(ups)
select new Point { X = move.X - down.X, Y = move.Y - down.Y };
drags.Subscribe(drag => c.SetBounds(c.Location.X + drag.X, c.Location.Y + drag.Y, 0, 0, BoundsSpecified.Location));
}
}
Usage:
Button button1 = new Button();
button1.EnableDragging();
Well in all honesty there is a simpler way where by you initialize a global boolean variable called whatever you like, in this case, isMouseClicked. On your control you wish to allow dragging you go to its mouse down event,
Make sure these event are your control events not your forms event.
if (e.button == MouseButtons.left)
//this is where you set the boolean to true
Then go to its mouse move event
if (isMouseClicked == true)
//You then set your location of your control. See below:
Button1.Location = new Point(MousePosition.X, MousePosition.Y);
On your mouse up make sure to set your isMouseClicked to false;
Related
I am capturing click coordinates within a PictureBox but I want to achieve the same thing with a WebBrowser. After some research I found out that it is not possible to subscribe to the Mouse Click event of a WebBrowser control.
What are the possible ways to capture the clicks? Is there a sort of element which would allow me to navigate through the page but still capture the click?
I tried to create a transparent panel but transparent color doesn't mean see through as I see and when the element is in the back also doesn't capture the click, being able to capture the clicks with panel being behind the WebBrowser would also work.
PictureBox code:
private void uploadedPNG_MouseClick(object sender, MouseEventArgs e)
{
if(uploadedPNG.Image != null && !string.IsNullOrEmpty(deviceHeight.Text) && !string.IsNullOrEmpty(deviceWidth.Text))
{
mouseX = e.X;
mouseY = e.Y;
targetHeight = Int32.Parse(deviceHeight.Text);
targetWidth = Int32.Parse(deviceWidth.Text);
int outPutWidth = (mouseX * targetWidth) / uploadedPNG.Width;
int outPutHeight = (mouseY * targetHeight) / uploadedPNG.Height;
ConsoleText.Text = "Clicked X coordinate " + outPutWidth + " Clicked Y coordinate " + outPutHeight;
}
}
The WebBrowser itself doesn't provide Mouse Click coordinates: you're not actually clicking on the Control client area, you're clicking on the content of the HtmlDocument.
You can use the HtmlDocument.Click or HtmlDocument.MouseDown events to retrieve the Mouse pointer coordinates on an initialized HtmlDocument.
Note:
The HtmlElementEventArgs object returns the Mouse coordinates in both absolute coordinates (the whole Document area), in e.ClientMousePosition and relative to the clicked HtmlElement, in e.OffsetMousePosition.
This can be tricky, because you need to subscribe to the Click event when the current HtmlDocument is already created: you cannot subscribe to the event of the default Document object:
i.e., subscribing to the event in Form.Load with:
webBrowser1.Document.Click += (obj, evt) => { /*Do something */ };
will not accomplish anything. The event will never be raised: the Document is null thus, of course, it's not referency any current/active HtmlDocument.
An HtmlDocument is ready when the WebBrowser.DocumentCompleted event is raised and its ReadyState is set to WebBrowserReadyState.Complete.
You can subscribe to the Document.Click event when the Document is fully loaded, then remove the event before the WebBrowser navigates to a new page, creating a new document.
Also, the DocumentCompleted event may be raised multiple times for a single HTMLpage, so you need to be sure that you don't subscribe to the same event multiple times:
Note:
A HtmlDocument may contain more than one Frame/IFrame and each Frame may have its own HtmlDocument; IFrames have one each for sure. Refer to the notes in this question for more informations on this matter:
How to get an HtmlElement value inside Frames/IFrames?
An example:
bool WebBrowserDocumentEventSet = false;
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
WebBrowser wb = (sender as WebBrowser);
if (wb.ReadyState == WebBrowserReadyState.Complete && WebBrowserDocumentEventSet == false)
{
WebBrowserDocumentEventSet = true;
wb.Document.MouseDown += this.OnHtmlDocumentClick;
}
}
private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
(sender as WebBrowser).Document.MouseDown -= this.OnHtmlDocumentClick;
WebBrowserDocumentEventSet = false;
}
protected void OnHtmlDocumentClick(object sender, HtmlElementEventArgs e)
{
Console.WriteLine(e.ClientMousePosition);
}
I have a ListView with the View set to LargeIcon.
I specifically need to detect when multiple items were selected by dragging a selection box around them with the mouse.
(For example I don't want to know when items were selected by CTRL + Click)
I thought I could simply do it by keeping track of whether the mouse was down while it was moving which would indicate dragging, then on mouse up if it was a drag then I can set another variable to indicate this.
In my example below mouseDown is set to true, but when I keep the mouse down and move it isDrag is never set to true and I can't see what I'm doing wrong.
(Edit: isDrag becomes true if I remove the if clause which is weird because as I said mouseDown is definitely true).
I realise the code is a little longer than it needs to be but it's for clarity.
bool mouseDown;
bool isDrag;
bool wasDrag;
private void listView1_MouseDown(object sender, MouseEventArgs args)
{
wasDrag = false;
mouseDown = true;
}
private void listView1_MouseMove(object sender, MouseEventArgs args)
{
if (mouseDown)
isDrag = true; // <-- Never becomes true, even though mouseDown is true
}
private void listView1_MouseUp(object sender, MouseEventArgs args)
{
if (isDrag)
wasDrag = true;
mouseDown = false;
isDrag = false;
}
I know it'll be something stupid. Please put me out of my misery.
Alternatively if someone knows of a better was to detect a dragging selection (what's the proper term?) then I'm all ears.
Can you try this:
private void listView1_MouseMove(object sender, MouseEventArgs args)
{
isDrag = mouseDown;
}
I think for some reason, your event listView1_MouseUp still fires, which makes your isDrag variable set to other than the intended value. Try to put breakpoints on both MouseMove and MouseUp events to see the sequence with which they are firing.
After further investigation I've discovered that for a ListView control the MouseMove event will not fire while MouseDown is still occurring and fires immediately after releasing the mouse.
I can only assume that the logic built into this control that allows you to select multiple files by dragging a selection is messing with these events and essentially making them synchronous.
I've put together a basic workaround for this. It's not ideal but it does the job so I thought I'd share.
Basically when the mouse goes down I record the position. When the mouse goes up I check to see if it has moved more than a certain distance in any direction. If it has not I consider it a click, if it has I consider it a drag.
// Records the mouse position on mousedown
int beforeMoveX;
int beforeMoveY;
// How far in pixels the mouse must move in any direction
// before we consider this a drag rather than a click
int moveBounds = 20;
private void listView1_MouseDown(object sender, MouseEventArgs e)
{
// Save the mouse position
beforeMoveX = e.X;
beforeMoveY= e.Y;
}
private void listView1_MouseUp(object sender, MouseEventArgs e)
{
// Did we move more than the bounds in any direction?
if (e.X < (beforeMoveX - moveBounds) ||
e.X > (beforeMoveX + moveBounds) ||
e.Y < (beforeMoveY - moveBounds) ||
e.Y > (beforeMoveY + moveBounds))
{
// DRAGGED!
}
else
{
// NOT DRAGGED!
}
}
I am creating a Word Add-In and in order to allow dragging something from a custom task pane to the document, I have followed the following guide:
http://msdn.microsoft.com/en-us/library/office/hh780901(v=office.14).aspx
There are some real drawbacks using this approach.
First, the transparent Windows Form (or WPF in my case) that catches the drop event is the size of the Window, not the document, and RangeFromPoint always returns a value, even if we aren't over the document (for instance, if we are over the Ribbon). So once you drag something and this form is created, no matter where you drop, it will be placed in the document. There is no graceful way to cancel once you've started.
My question is:
Has anyone done any work with Drag and Drop in a Word Add In, and found a better way to handle it than the supplied example by Microsoft?
It would be nice to either use the current solution, but know when the user is not dragging over the document or have that transparent window only show over the document area.
Hope you already had your answer.
I got a solution for my own.
So, my requirement:
I have a custom pane, which contains a listbox, each item is a normal string. When I drag an item from the listbox into the document, at a specific location, I want to insert a merge field at that location. The name of the merge field is the text of the item.
It was simple at first, then I got a problem just like you describe in your question.
About the code
So, there is a listbox, you need to handle mouseDown and mouseMove, don't worry about mouseUp.
In mouseDown handler, I record the boundary, if the mouse moves out of that boundary, the drag will start.
Then, in listBox_MouseMoveHandler, I check position of the mouse to start the dragdrop. And I have to use DragDropEffects.Copy for the DoDragDrop method.
DoDragDrop((sender as ListControl).SelectedValue, DragDropEffects.Copy);
With that option, SelectedValue will be inserted at the drop position, and after it is inserted, it will also be selected.
Then, I just check if selection is not empty, and replace the selected text with the merge field. Of course, I collapsed the selection before DoDragDrop. And that is the whole trick.
private int _selectedItemIndex;
private Rectangle dragBoxFromMouseDown;
private void CustomizationForListBox(ListBox listBox)
{
listBox.ItemHeight = 25;
listBox.DrawMode = DrawMode.OwnerDrawFixed;
listBox.DrawItem += ListBox_DrawItem;
listBox.MouseDoubleClick += listBox_MouseDoubleClick;
listBox.MouseMove += listBox_MouseMoveHandler;
listBox.MouseUp += listBox_MouseUp;
listBox.MouseDown += (sender, e) =>
{
// Handle drag/drop
if (e.Button == MouseButtons.Left)
{
_selectedItemIndex = listBox.IndexFromPoint(e.Location);
// Remember the point where the mouse down occurred. The DragSize indicates
// the size that the mouse can move before a drag event should be started.
Size dragSize = SystemInformation.DragSize;
// Create a rectangle using the DragSize, with the mouse position being
// at the center of the rectangle.
dragBoxFromMouseDown = new Rectangle(new Point(e.X - (dragSize.Width / 2),
e.Y - (dragSize.Height / 2)), dragSize);
}
};
}
private void listBox_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
// Reset the drag rectangle when the mouse button is raised.
dragBoxFromMouseDown = Rectangle.Empty;
}
}
private void listBox_MouseMoveHandler(object sender, MouseEventArgs e)
{
// Handle drag and drop
// To check if the Mouse left button is clicked
if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
{
// If the mouse moves outside the rectangle, start the drag.
if (dragBoxFromMouseDown != Rectangle.Empty &&
!dragBoxFromMouseDown.Contains(e.X, e.Y))
{
// Collapse current selection, now we know nothing is selected
Globals.ThisAddIn.Application.Selection.Collapse(WdCollapseDirection.wdCollapseEnd);
//Start Drag Drop
DoDragDrop((sender as ListControl).SelectedValue, DragDropEffects.Copy);
if (_selectedItemIndex != -1)
{
// If the drag/drop was successful, there dropped text must be selected
if (!String.IsNullOrWhiteSpace(Globals.ThisAddIn.Application.Selection.Text))
{
// Replace the selected text with a merge field MergeFieldHelper.InsertSingleMergeField(mergeFieldInfos[_selectedItemIndex].Name);
}
}
}
}
}
Hi
I have my custom control on which I draw color border by overriding OnPaint method. However I would like to change the border color of my control if mouse enter the area of control and if mouse leave the control. At first I wanted to react on event mouseLeave and mouseEnter and repaint control border with a proper color. However in my control is several textboxes,labels etc - so events mouseEnter and mouseLeave fire quite a lot of times and this causes that my control blinks (because of many redraws).
Is there any better way to find a proper moment to redraw control then react on mouseLeave and mouseEnter ??
You should invalidate your control only if the mouse is over it. You can check the position of the mouse by inspecting the static MousePosition variable, available for all controls. Just add a check to conditionally invalidate your control.
The simplest way to do this is to perform these checks from within the MouseEnter and MouseLeave events, then invalidate appropriately.
protected override void OnMouseEnter(EventArgs e)
{
var mousePos = this.PointToClient(MousePosition);
if (this.ClientRectangle.Contains(mousePos))
{
this.Invalidate(invalidateChildren: true);
}
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
var mousePos = this.PointToClient(MousePosition);
if (!this.ClientRectangle.Contains(mousePos))
{
this.Invalidate(invalidateChildren: true);
}
base.OnMouseLeave(e);
}
For a more robust way to handle this, you need to determine whether or not the mouse actually enters or leaves your control. You would need to keep two variables to keep state, one to tell if the mouse is currently over your control and one to tell if the mouse was over your control (since the last time checked). If these are different, then invalidate your control. You'll get the added bonus of knowing whether the mouse is over your control so you can perform some operations in your paint method conditionally.
private bool wasMouseOver;
private bool isMouseOver;
public bool IsMouseOver { get { return isMouseOver; } }
private void CheckMousePosition()
{
var mousePos = this.PointToClient(MousePosition);
wasMouseOver = isMouseOver;
isMouseOver = this.ClientRectangle.Contains(mousePos);
if (isMouseOver != wasMouseOver)
this.Invalidate(invalidateChildren: true);
}
// then register this method to the mouse events
EventHandler mouseHandler = (sender, e) => CheckMousePosition();
MouseEnter += mouseHandler;
MouseLeave += mouseHandler;
MouseMove += (sender, e) => CheckMousePosition();
I'm trying to accomplish what I thought would be somewhat straight forward, but apparently isn't.
What I'd like to accomplish is as follows. The user presses a button in a toolbar, this allows the state of the UI to change. Now that the state has changed when the user clicks mouse-down they can create a box on the screen by dragging the mouse. Upon the mouse-up I want to return the x,y coord.
So basically something like this
protected void MyUI_MouseDown(object inSender , MouseEventArgs inArgs)
{
switch(myState)
{
case CreateBox:
Rectangle rect = DrawBox();
}
}
public Rectangle DrawBox()
{
myDrawFlag = true;
}
private MyUI_MouseMove(object inSender , MouseEventArgs inArgs)
{
if(myDrawFlag)
{
DrawBox(inArgs.X , inArgs.Y);
}
}
Basically, I'm not sure how to get from point A to point C. The mouse down changes the state of the UI and lets me draw on the screen via the Mouse Move - but I want to return the value upon the Mouse UP.
I know I'm doing something wrong - could someone tell me what?
Edit 1: Yes, there is nothing in DrawBox(). Basically my question is how do I have that method not return UNTIL, I get the mouse-up event?
Edit 2: I am tracking the mouse movement events. As the mouse moves I am updating the start X,Y and the new endpoint X,Y. I still think I'm not asking this question correctly.
I don't want return DrawBox() until the mouse-up event. Mouse-Down should just notify the UI it can draw a box on the screen. Mouse-Move (which I'm using) updates the coordinates. And then Mouse-Up should tell the UI it can't draw anymore and ONLY THEN should DrawBox() return with the points.
The idea to wait inside the MouseDown method until die MouseUp event is fired is not the way to go. You have to think event driven. Better create/save your final object in the MouseUp method.
protected void MyUI_MouseDown(object inSender, MouseEventArgs inArgs)
{
switch(myState)
{
case CreateBox:
Rectangle rect = new Rectangle(inArgs.X, inArgs.Y, 0, 0);
break;
}
}
protected void MyUI_MouseUp(object inSender, MouseEventArgs inArgs)
{
rect.Width = inArgs.X - rect.X;
rect.Height = inArgs.Y - rect.Y;
// now save/draw your object
}
You can store your points in a struct and then fill up the struct in the MouseUp event, and reuse it where you wish.
struct coords
{
int x;
int y;
}
coords my_coords;
protected void MyGui_MouseUp(object sender, MouseEventArgs args)
{
my_coords.x = args.x;
my_coords.y = args.y;
}
//here you can use my_coords to what you need...
You need to handle the MouseUp event. The MouseMove event you are handling will fire every pixel change in distance that the mouse moves so you are in effect calling the DrawBox(int, int) method hundreds of times. If you handle the MouseUp event instead you will get the coordinates when the mouse button is released.
As far as i see it, you have 3 states:
Normal, which doesn require any explanation.
DesignMode, When the button in he toolbar is clicked.
DrawingBox, When you are actually drawing the box
Now you want the following transition:
Normal -> DesigMode -> OnDrawingBox -> AfterDrawingBox.
This can implemented using a state machine or an enum. With the enum, your code will be something like this:
enum DrawBoxState { Normal, DesignMode, DrawingBox }
DrawBoxState _currentState = DrawBoxState.Normal;
Point _startPoint;
Point _endPoint;
void OnToolbarButtonClicked(object sender, EventArgs e)
{
switch (_currentState)
{
case DrawBoxState.Normal:
_currentState = DrawBoxState.DesignMode;
break;
default:
_currentState = DrawBoxState.Normal;
break;
}
}
void OnMouseDown(object sender, MouseEventArgs e)
{
switch (_currentState)
{
case DrawBoxState.DesignMode:
{
_currentState = DrawBoxState.OnDrawingBox;
_startPoint = e.Location; // not sure if this is right
}
break;
}
}
void OnMouseUp(object sender, MouseEventArgs e)
{
switch (_currentState)
{
case DrawBoxState.DrawingBox:
{
_currentState = DrawBoxState.Normal;
_endPoint = e.Location;
}
break;
}
}
Now you can simply, raise an event every time the state changes, and adept changes on that event. e.g. if state changes to normal, check if startPoint and endPoint have been set and therefore do something with the value's.