I have a List<Rectangle> myRectangles that are rendered in a Panel control. If I want to fire an event handler when the mouse is within any of myRectangles, is my best option to do 'foreach'? And then go do whatever myRectangle.OnMouseMove() would do?
This would seem to be a good use for Reactive LINQ, or Rx, but I am more familiar with using the former.
http://tomasp.net/blog/reactive-ii-csevents.aspx
You will want to optimize to determine which rectangles may be possibly intersected, but just use the OnMouseMove and then in your LINQ query you will find whichever rectangles are entered.
This could potentially be a performance hog.
Don't use controls for your rectangles, they are horribly expensive and can't overlap. You can't make it reliable with just the OnMouseMove() method, you'll miss the mouse moving outside of the panel when the user is moving the mouse fast and/or an edge of a rectangle is close to the panel border. It is easily solved with the Control.Capture property. Some sample code:
public event EventHandler InsideRectangles;
private List<Rectangle> mRectangles = new List<Rectangle>();
private bool mInside;
protected void OnInsideRectangles(EventArgs e) {
EventHandler handler = InsideRectangles;
if (handler != null) handler(this, e);
}
protected override void OnMouseMove(MouseEventArgs e) {
bool inside = false;
foreach (var rc in mRectangles) {
if (rc.Contains(e.Location)) {
inside = true;
break;
}
}
if (inside && !mInside) OnInsideRectangles(EventArgs.Empty);
this.Capture = inside;
mInside = inside;
base.OnMouseMove(e);
}
You could create a control which has two rectangles one inside another. The outside rectangle's OnMouseMove would be exposed. The outside rectangle would be invisible. This way you could let Windows manage the event calling and not have cluttered code.
I assume you must be using Winforms? If so, you might want to consider making the rectangles separate child controls (rendering whatever the rectangle looks like in OnPaint) and use the mouse events provided by Control.
Related
I have a method where I disable or enable some custom controls and then use a graphics object to draw lines and rectangles.
The gist of the method:
void MyMethod()
{
//...
mycontrol.enabled = false;
mycontrol.visible = false;
mycontrol.Invalidate();
mycontrol.Update();
GraphicsObject.DrawLines();
//...
}
Right after this method returns, the screen looks great. I have rectangles and lines where controls used to be.
However, after the click event handler returns (which called the above method). The controls which should be invisible draw over the lines and rectangles (leaving those area's blank - the same color as the background form).
Is there any way to fix this?
Thanks
As I mentioned in my comment if you are drawing on an object if you do not use the OnPaint Method or the Paint Event your custom drawing will not be automatically redrawn. Depending on what you are drawing on you can do something like( I am assuming you are drawing on a Form).
void MyMethod()
{
//...
mycontrol.enabled = false;
mycontrol.visible = false;
mycontrol.Invalidate();
mycontrol.Update();
this.Invalidate();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
//Conditional Logic to determine what you are drawing
// myPoints is a Point array that you fill elsewhere in your program
e.Graphics.DrawLines(new Pen(Brushes.Red), myPoints);
}
I am trying to change my mouse cursor at certain point when I'm dragging my mouse around in a wpf listview. However, when I set my mouse, it quickly gets overridden by something else, and get changed back to the drag cursor.
I am not sure where the cursor change comes from, it is certainly not from my code, so it has to be system. If it is system, then I have to intercept the event for cursor change, and handle the event in order for the cursor to show what I want right?
So is there a WPF equivalent of this Control.CursorChanged event? Or perhaps there's some other way to approach this problem?
Edit:
here's part of my code
private void SetDragCursor()
{
if (_badDragLoc)
{
Mouse.OverrideCursor = Cursors.No;
}
else
{
Mouse.OverrideCursor = Cursors.Arrow;
}
}
private void listView_DragOver(object sender, DragEventArgs e)
{
if (at a bad drag location)
{
_badDragLoc = true;
SetDragCursor();
}
}
I also have a drag leave event handler, in which I also have the SetDragCursor() method as well. When I step by step go through each line of code in debugger, the mouse turned into the drag cursor from the no cursor right after it enters the drag leave handler. Which is why I think it has to be the system.
If it indeed is the system, then if I can capture the event firing, I can then handle those event myself and not let it bubble through.
Thank you!
Just does not work like that, the way to set the cursor during a DragOver event is the following:
void listView__DragOver(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent("Images"))
{
e.Effects = DragDropEffects.None;
e.Handled = true;
}
}
depending on the value of DragDropEffects enum you assign to e.Effects the mouse will change cursor.
do not call Mouse.OverrideCursor because is not the right way.
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();
There is certainly a convenient way to do this :
I have implemented a "Move Window" on mouse drag behavior on my main form, and I would like the MouseClick/Move event to be intercepted by the form, not by controls that are in it.
I would like to find an Equivalent to/replicate the "KeyPreview" property for Mouse Events
Besides I want to avoid Redirecting the Mouse Event to the Main Form Method 12 times in 12 Controls' Mouse events individually (which is the ugly workaround I have Found so far)
Any Ideas ?
Subscribe to all controls MouseMove events (consider do it recursively for nested controls)
foreach (Control control in Controls)
control.MouseMove += RedirectMouseMove;
And raise MouseMove inside this event handler
private void RedirectMouseMove(object sender, MouseEventArgs e)
{
Control control = (Control)sender;
Point screenPoint = control.PointToScreen(new Point(e.X, e.Y));
Point formPoint = PointToClient(screenPoint);
MouseEventArgs args = new MouseEventArgs(e.Button, e.Clicks,
formPoint.X, formPoint.Y, e.Delta);
OnMouseMove(args);
}
Keep in mind that controls receive MouseEvents with local coordinates of control. So you need to convert it to form coordinates.
There are could be drawbacks with nested controls, but I leave it to you (e.g. call Parent.PointToClient)
UPDATE: You are still will be able to handle events of control - just subscribe to event one more time.
Based on your comments,
Implement the redirect functionality of the Mouse Event in a base class, then make all controls derive from that base class.
Thus, you only implement the functionality once and then all your controls will "rethrow" the mouse event to be caught by the Main Form.
Hope this helps.
Override the Control.PreProcessMessage Method:
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.preprocessmessage.aspx
Edit:
It seems PreProcessMessage might not work for mouse events. Try overriding WndPrc instead. It can intercept mouse messages for sure, but you need to see if it intercepts them in the order you want:
http://bytes.com/topic/c-sharp/answers/752144-preprocessmessage
You can use GlobalMouseKeyHook library to easily intercept system wide mouse position.
On mouse click you should then check if mouse location point intersects your form OR if the windows under your mouse is your form.
To do the latter thing you need WindowFromPoint API function:
[DllImport( "user32.dll", SetLastError = true )]
public static extern IntPtr WindowFromPoint( [In] POINTAPI Point );
private void _mouseListener_MouseClick( object sender, MouseEventArgs e )
{
var localPoint = this.PointToClient( e.Location );
bool containsPoint = this.ClientRectangle.Contains( localPoint );
var windowHandle = WindowFromPoint( e.Location );
var ctl = (Form)Form.FromHandle( windowHandle );
bool mainFormClicked = ctl != null && ctl.Handle == this.Handle;
if( containsPoint && mainFormClicked )
{
//form click is intercepted!
}
}
Actually I use this when i want to intercept click outside my form (there is no other way). In your case i'd bind to every control's MouseClick for performance sake (global hook is heavy).
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;