I have a canvas object and I am sprinkling fantastic controls all over it. I'm using a ScaleTransform object to scale the canvas so I can zoom in/out.
I have wired up the controls so that I can drag them around, and the drag and drop works well by using MouseLeftButtonDown, MouseLeftButtonUp, and MouseMove. Now, I want to work on enabling an event when I click only on the Canvas. When I read the docs for the canvas object, I see that the MouseLeftButtonDown event only fires when it's over a UIElement.
Occurs when the left mouse button is
pressed (or when the tip of the stylus
touches the tablet PC) while the mouse
pointer is over a UIElement.
(Inherited from UIElement.)
Unfortunately, I want the opposite behavior. I want to know when the mouse is clicked on the Canvas while the mouse pounter isn't over any controls. Since I'm new to Silverlight, I could be doing this the wrong way. Is there something I have overlooked? Am I going about this the wrong way? I'm looking for a little help, and perhaps a lot of direction.
I'm no Silverlight guru, but could you add a transparent UIElement to the Canvas, below all other UIElements, and use it to determine if the user has clicked outside of any of the other drag/drop-able elements.
You want to know when a click happens on the Canvas and isn't over other controls?
The most natural thing is to capture the Canvas's MouseLeftButtonDown. Inside of that event, take a peak to see where the click happened. Then peak at the UIElements under the click. I recommend you keep everything in absolute coordinates to keep things straight. Something like:
void Page_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point p = e.GetPosition(null);
var elements = VisualTreeHelper.FindElementsInHostCoordinates(p, App.Current.RootVisual);
foreach (var element in elements)
{
//Figure out if you're over a particular UI element
}
}
I think you may be interpreting the documentation wrong. According to MSDN, Canvas itself is an implementation of a UIElement:
System.Windows.UIElement
System.Windows.FrameworkElement
System.Windows.Controls.Panel
System.Windows.Controls.Canvas
From my experience, and correct me if I'm wrong, MouseLeftButtonDown usually only fires for the top-most UIElement clicked. So if you implement MouseLeftButtonDown for your Canvas, it should only fire when the Canvas is clicked, and NOT when the buttons are clicked. I'd say try it out first.
In WPF, I think this would be easily solved by routed events. However, Silverlight didn't get this feature. You may want to check out VisualTreeHelper.FindElementsInHostCoordinates. This article covers it a little bit.
http://www.andybeaulieu.com/Default.aspx?tabid=67&EntryID=95
Related
My UserControl is a kind of a container that has a set of controls inside. One of the UI behaviours I designed is that the move over nested control makes it selected for some keyboard triggered actions.
The other way of setting up the nested control that is targeted to receive keyboard input is with arrow keys that change focus to certain control within my UserControl.
The problem is that in most cases my UserControl has scrollbars and switching between its elements with arrow keys causes contents to move. Because of that movement it seems that mousemove event is launched also when the mouse arrow stands still, but its over my usercontrol. In the end, the wrong nested control is being selected.
I tried to set a boolean flag to temporary lock the actions of mousemove event while the arrow keys handling function is launched, but it seems to not to work at all.
Does anybody have an idea how to prevent that unwanted triggering of mousemove event and avoid the problems it causes?
I did some debugging/experiments and it appears that though mouse stays still over controll the MouseMove is actually called frequently and not only when control move is caused by some UI behaviour (scrolling of parent controll called using keyboard).
It also seems like it is called twice every single second (but probably only when application is launched form VS during debug session?), the calls are separated by few hundred parts of seconds and for now i couldnt determine what causes those.
Solution (maybe not only, and not the best) is to store last position of cursor and compare it inside event to check if mouse was really moved from last MouseMove call. I achieved it using System.Windows.Forms.Cursor.Position because position from MouseEventArgs will be diffrent depending on circumstances but still will be inconclusive to determine if mouse cursor was moved.
Point lastCursorPosition = new Point();
private void panelPictures_MouseMove(object sender, MouseEventArgs e)
{
if (System.Windows.Forms.Cursor.Position != lastCursorPosition)
{
Console.WriteLine("mouse moved");
lastCursorPosition = System.Windows.Forms.Cursor.Position;
}
else
{
Console.WriteLine("mouse in place");
}
}
It is not easy to explain the problem without an image and without code. For those interested, I have made a sample C#/WPF project that can be downloaded from http://rapidshare.com/files/461745095/02.WPFControlEvents.rar
It is a very small project (45KB). The problem is as follows (with my apologies for the contorted description):
A "fancy" (and rather ugly) button contains a stackpanel which in turn contains 1.) a label, 2.) a canvas, 3.) two (2) concentric ellipses, all laid out from left to right.
There is a MouseEnter and a MouseLeave event handler for the stackpanel which displays a message in the title bar indicating whether the mouse is over the stackpanel or outside of it.
The problem is: when the mouse is over the label (in turn contained in the stackpanel), the mouse is (correctly) reported as being over the stackpanel. When the mouse is moved over the canvas (which is also contained in the stackpanel), the mouse is (incorrectly?) reported as not being over the stackpanel, yet when the mouse is moved a little further to the right (over the two ellipses, which are on the canvas), the mouse is reported to be over the stackpanel again.
Why is it that when the mouse is over the canvas it is reported as not being over the stackpanel but when the mouse is over the ellipses (which are painted on the canvas) it is reported as being on the stackpanel ?.
thank you for shedding light into the problem,
John.
Chances are you need to set the Background of the Canvas to Transparent. This will allow it to be "hit testable" and report mouse over events.
More info can be found here, but Canvas has a null background by default.
I have a large user drawn control that fills most of the screen area in an application.
I would like to simulate some "onMouseHover" behaviour, I cant really use the userControl event as the mouse is almost always on that control so it fires all of the time.
How can I detect the mouse "hovering" over part of my user drawn control?
(If it helps an image of the app can be found at : http://www.benbun.co.uk/st3/ayv the control is the large "year calendar")
You could handle MouseMove events instead of MouseHover. Then you could calculate based on the X,Y location of the mouse whether or not the cursor is in the part of your control you are interested in creating "hover" behavior for.
I have a custom Canvas control (inherited from Canvas) overlaid over a large area of User Controls. The idea is to draw paths between user controls (i.e. connector lines).
To capture mouse movement, I call Mouse.Capture(theCanvas) on MouseDown. This works beautifully, but the user controls under the canvas obviously no longer receive mouse events. Mouse.DirectlyOver always shows the canvas, so I can't really fake it by peeking at the current position and seeing which user control it's over.
So, I still need the Canvas for drawing paths, but how can I solve this one of the following ways:
Peek under the Canvas and see what the topmost control is right under it?
Get this MouseDown -> Track MouseMoves -> MouseUp workflow to work on the canvas without mouse captures?
Any other ideas welcome...
I'd agree that those are your two options. If you want to only forward some clicks to your usercontrols, then go with option 1, and hit test the controls under the canvas.
If you need your usercontrols to behave as though there is nothing covering them (textboxes, buttons etc), then i'd recommend using the PreviewMouseMove event on the user control's parent, as this can pick up and optionally "handle" events before the controls get at the event, but it won't block the event if you don't set handled to true
I want to show a tooltip when hovering over a button and as long as the mouse is over the button the tooltip should follow the mouse. What is the correct way to achieve that?
When I add a MouseMove event that calls tooltip.Show(...) with the updated mouse position it flickers extremely, and also redraws the tooltip when the mouse rests. And if it is an OwnerDraw tooltip I can see the default system tooltip style "fighting" with the self-drawn tooltip.
Indeed, with .Net 2.0 the ToolTip object has been altered. Before 2.0, there were some inconsistency problems when the ToolTip text was changed while the ToolTip was active, or with some other situations.
Since 2.0, the Tooltip is hidden each time something happens what could affect the currently active Tooltip.
While this solved some problems, it now causes some events being fired right after e.g. a SetToolTip(), even if this function has been called from within this very event, resulting in an endless loop of ToolTip draw/hide until the mouse moves away from the ToolTip area.
My own workaround is to check whether the ToolTip is already the same and omitting the Set ToolTip() if so. (simply omitting the next event by a static flag as suggested above can cause problems as there is no guarantee that there will be a new event right after, e.g. if the mouse has just touched the ToolTip area and moved away already).
Also, using OnMouseHover just to display a Tooltip disables the internal timer functionality of the ToolTip component as well as causing many many unnecessary events and therefore wastes processor time. The Popup Event of the ToolTip component serves well as point of action.
In this special case, however, OnMouse Hover is necessary to track the mouse movement.
Anyways, altering the ToolTip position causes a complete redraw of the Tooltip and therefore flicker. This can be reduced for a motionless mouse by checking whether the mouse position has changed between two events.
Unfortunately, the ToolTip component has no way to change the position of the ToolTip adn is shown always relative to the current mouse position. So the only way to have it follow the mouse is to close and redraw it.
it MAY help to set the UseFading and/or UseAnimation properties to false so the flicker can be further reduced.
OK, this may be complete overkill, and probably not the best solution, but I think a fun little hack nonthless.
Basically, I'm drawing a ListView at the location of the mouse. Some code:
ListView v = new ListView();
public Form1()
{
InitializeComponent();
v.Items.Add("Foo");
v.Height = 30;
v.Width = 50;
this.button1.Controls.Add(v);
v.MouseMove += new MouseEventHandler(v_MouseMove);
v.BackColor = SystemColors.Info;
this.button1.MouseMove += new MouseEventHandler(button1_MouseMove);
}
void v_MouseMove(object sender, MouseEventArgs e)
{
v.Location = new Point(v.Location.X + e.Location.X, v.Location.Y + e.Location.Y);
}
void button1_MouseMove(object sender, MouseEventArgs e)
{
v.Location = e.Location;
}
I've noticed that when manually showing a tooltip with OnMouseHover, OnMouseMove gets called one more time after the tooltip is shown. As a hack, I've ignored the next OnMouseMove call immediately following the tooltip being shown (using a flag). Perhaps a similar phenomenon is occurring?