Silverlight MouseLeave Issue - silverlight

I'm trying to make a color picker in Silverlight similar to this one but I'm having trouble implementing the cursor in the large square area. In order to keep track of the mouse state I have an _isMouseDown variable. On the MouseLeave event _isMouseDown is set to false, so that if a user drags out of the large square area, releases, and then moves the mouse back, the color picker cursor won't "jump" to the mouse and follow it (because _isMouseDown would still be true). However the MouseLeave event also seems to fire when the cursor is mouse is moved quickly, resulting in the color picker cursor being "dropped."
The following code is enough to replicate the problem. Try dragging the mouse quickly and the ellipse will be "dropped". When the MouseLeave event is removed the problem vanishes. Is there any way to fix this "dropping" problem, but still have the behavior I mentioned above?
XAML:
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Canvas x:Name="LayoutRoot" Width="800" Height="600">
<Rectangle Width="800" Height="600" MouseLeftButtonDown="TestMouseDown"
MouseLeftButtonUp="TestMouseUp" MouseMove="TestMouseMove"
MouseLeave="TestMouseLeave">
<Rectangle.Fill>
<LinearGradientBrush>
<GradientStop Offset="0.00" Color="Crimson" />
<GradientStop Offset="1.00" Color="Azure" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Ellipse Name="TestEllipse" Width="50" Height="50" Fill="Green" />
</Canvas>
</UserControl>
C# codebehind:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SilverlightApplication1
{
public partial class MainPage : UserControl
{
private bool _isMouseDown;
public MainPage()
{
InitializeComponent();
}
private void TestMouseDown(object sender, MouseButtonEventArgs e)
{
_isMouseDown = true;
UpdatePosition(e.GetPosition(LayoutRoot));
}
private void TestMouseUp(object sender, MouseButtonEventArgs e)
{
_isMouseDown = false;
}
private void TestMouseMove(object sender, MouseEventArgs e)
{
if (_isMouseDown)
UpdatePosition(e.GetPosition(LayoutRoot));
}
private void TestMouseLeave(object sender, MouseEventArgs e)
{
_isMouseDown = false;
}
private void UpdatePosition(Point point)
{
Canvas.SetLeft(TestEllipse, point.X);
Canvas.SetTop(TestEllipse, point.Y);
}
}
}

You should take a look at the CaptureMouse method on UIElement. It should be helpful for you in this situation. With the mouse captured, you will continue to receive mouse events even when the mouse leave the element's area. You can then voluntarily release the mouse capture whenever that is appropriate.

However the MouseLeave event also
seems to fire when the cursor is mouse
is moved quickly, resulting in the
color picker cursor being "dropped."
the problem with your code is MouseLeave event fires not only at the time of mouse leaving the rectangle, this also fires when mouse enter the Ellipse.. because the mouse event is now routed to the ellipse control.. (not this fired when u move the mouse quickly)..
As KeithMahoney suggest, u can try CaptureMouse... or set _isMouseDown = true on the MouseEnter event of ellipse.. it may work.. i dint test ur code yet.. just telling u by seeing the code...

I had a similar problem, and just like you, I also changed the Canvas.Left and Canvas.Top of a control from my mouse events.
The thing is, when I changed the position of the control, I "moved" that control "underneath" the mouse, putting focus on that control instead, which then in turn caused a MOUSE-LEAVE event for the current control.
Consider switching the IsHistTestVisible to "FALSE" for the control that you manipulate, so that if you move it underneath the current location of the mouse cursor, it doesn't trigger any new events.
A complete solution and example can be found here
https://stackoverflow.com/a/13265880/1308645
I hope that helps.
Regards,
Martin

Related

Custom window drag handler interferes with scrollviewer bar drag

I have a window with window style set to None that handles OnPreviewMouseDown, OnPreviewMouseUp, and OnMouseMove so that the window can be dragged from anywhere.
The windows code behind looks like this:
bool inDrag;
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnPreviewMouseLeftButtonDown(e);
inDrag = true;
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (inDrag && e.LeftButton == MouseButtonState.Pressed)
{
this.DragMove();
}
}
protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnPreviewMouseLeftButtonUp(e);
if (inDrag)
{
inDrag = false;
}
}
Now problem is I have a large menu in this window that has a scroll bar which works with the scroll wheel and by clicking to a position on the scrollviewer but not when clicking and dragging the scroll bar itself. Also if I click and hold and move my cursor not on top of the window the scrolling works again. This has led me to believe that the above dragging implementation is blocking the scrollviewers drag functionality.
I tried manually raising the event with .RaiseEvent(e) in OnMouseMove but this causes a stack overflow exception when I move my mouse over the window.
How can I get my Scrollviewer to respond to mouse movements without removing my click and drag window behavior?
<!--ScrollViewer subscribed to PreviewMouseDown to set inDrag to false-->
<ScrollViewer Visibility="Visible" Mouse.PreviewMouseDown="ScrollViewer_PreviewMouseDown">
<!--The Grid fill all the available space and is subscribed to PreviewMouseDown event to set inDrag to true-->
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Mouse.PreviewMouseDown="Grid_PreviewMouseDown" Background="Transparent">
<!--My menu-->
</Grid>
</ScrollViewer>
Note that if there is "blank space" (no Background set including that of the Content),
the PreviewMouseDown event won't be raised despite the subscription.
If needed, to work around that you can set the Background of the Grid to Transparent and the event will be raised even on seemingly blank space.

WPF ListView and ScrollViewer hide MouseLeftButtonDown

To demostrate the problem I have this Xaml:
<DockPanel MouseLeftButtonDown="DockPanel_MouseLeftButtonDown" MouseLeftButtonUp="DockPanel_MouseLeftButtonUp">
<ListView>
<ListViewItem>ListViewItem</ListViewItem>
</ListView>
<TextBlock>TextBlock</TextBlock>
</DockPanel>
and the event handlers are :
private void DockPanel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("DockPanel_MouseLeftButtonDown");
}
private void DockPanel_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("DockPanel_MouseLeftButtonUp");
}
When you run the app and click on the words TextBlock you get MouseDown fired followed by MouseUp. So far so good. But when you click on the words ListViewItem only MouseUp is fired. Same problem for ScrollViewer (List view includes it so I am guessing it's the same problem).
Does anybody know why and if this can be fixed.
By fixed I mean get it to fire not try to use another event or another mechanism all together.
First the problem:
As suspected the problem is in ScrollViewer: http://referencesource.microsoft.com/#PresentationFramework/Framework/System/Windows/Controls/ScrollViewer.cs,488ab4a977a015eb
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (Focus())
e.Handled = true;
base.OnMouseLeftButtonDown(e);
}
As you can see it sets MouseButtonEventArgs.Handled to true which stops the bubbling of the event.
Now the solution - it is in the way you add the handler:
MyListView.AddHandler(
ListView.MouseLeftButtonDownEvent,
new MouseButtonEventHandler(ListView_MouseLeftButtonDown),
true);
Note the last parameter (true) it causes the handler to be invoked even if the EventArgs.Hanlded was set to true.
Then you can reset it:
private void ListView_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
e.Handled = false;
}
I had somewhat similar situation when ScrollViewer was blocking my MouseLeftButtonDown event. I had a content control wrapped into ScrollViewer:
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ContentControl x:Name="Details" />
</ScrollViewer>
and this was inside of Popup which had a drag/drop behavior. So, because my behavior was not receiving this event, it did not work. When I added IsHitTestVisible="True" to ScrollViewer, my behavior started to work, but of course my ContentControl was not responding to any clicks. Then I saw this:
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (Focus())
e.Handled = true;
base.OnMouseLeftButtonDown(e);
}
and tried to add Focusable="False" to exclude ScrollViewer from my click - it works. My behavior works and controls inside of ContentControl are getting all mouse events.
<ScrollViewer VerticalScrollBarVisibility="Auto" Focusable="False">
<ContentControl x:Name="Details" />
</ScrollViewer>
Hope it will help somebody.

How to stop FocusManager from Moving the focus outside an open Popup when using IsFocusScope option

I have two controls a ToolBar and a TextBox. The ToolBar has a Button which opens a Popup with another Button in it.
Current behavior: if i click inside the TextBox and it becomes focused and then click the button from ToolBar which opens a Popup the TextBox is still focused and receives all Keyboard input.
Now i know this is the default behavior for items inside a FocusScope which the ToolBar is, but i don't need this behavior when a popup is open. How can i avoid it ?
Here is the example:
<Window x:Class="WpfApplication67.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
</Window.Resources>
<StackPanel HorizontalAlignment="Left" Width="400">
<ToolBar>
<ToggleButton Name="openButton">Open Popup</ToggleButton>
<Popup Focusable="True"
Placement="Right"
StaysOpen="False"
IsOpen="{Binding IsChecked, ElementName=openButton, Mode=TwoWay}"
PlacementTarget="{Binding ElementName=openButton}">
<StackPanel
Width="100"
Height="100"
Background="DarkGray"
Focusable="True">
<Button>More</Button>
</StackPanel>
</Popup>
</ToolBar>
<TextBox Text="Set focus on this..." />
</StackPanel>
EDIT:
I'm striving to find an explanation about Who moves the focus on button click inside a nested FocusScope and How i can stop some Buttons (like the one inside a Popup) from doing it.
You mainly have three requirements (correct me if am wrong):
If pop up is opened, focus should be inside popUp i.e. on StackPanel.
On close of popUp, focus should retained back to textBox.
When button inside popUp is clicked, focus should not leave the popUp.
Let's pick these requirements one by one.
If pop up is opened, focus should be inside popUp i.e. on StackPanel.
Like I mentioned in the comments, put focus on stackPanel on Opened event of PopUp:
private void Popup_Opened(object sender, EventArgs e)
{
stackPanel.Focus();
}
On close of popUp, focus should retained back to textBox.
Hook Closed event and put focus back on TextBox:
private void Popup_Closed(object sender, EventArgs e)
{
textBox.Focus();
}
When button inside popUp is clicked, focus should not leave the popUp.
Now, comes the tricky part. As mentioned in the comments as soon as you click on button inside popUp, focus is moved outside of PopUp.
What you can do prevent is to attach a handler to PreviewLostKeyboardFocus event of stackPanel. In handler check for condition if keyBoard focus is within popUp, set e.Handled = true so that event gets handled here only and no bubble event is raised which will force the keyboard focus outside of stackPanel.
That being said in case you have another TextBox inside stackPanel besies button, handling event won't allow you to move focus within popUp as well. To avoid such situations you can check if new focused element doesn't belong within stackPanel then only handle the event.
Here's the code to achieve that (add handler on PreviewLostKeyboardFocus event of StackPanel):
private void stackPanel_PreviewLostKeyboardFocus(object sender,
KeyboardFocusChangedEventArgs e)
{
var newFocusedChild = e.NewFocus as FrameworkElement;
bool isMovingWithinPanel = newFocusedChild != null
&& newFocusedChild.Parent == stackPanel;
// If keyboard focus is within stackPanel and new focused element is
// outside of stackPanel boundaries then only handle the event.
if (stackPanel.IsKeyboardFocusWithin && !isMovingWithinPanel)
e.Handled = true;
}
In this situation, there are two ways, both options have been added in the handler of Click event for openButton.
First
The easiest option, it is clear focus for your keyboard, like this:
private void openButton_Click(object sender, RoutedEventArgs e)
{
Keyboard.ClearFocus();
}
Second
A more universal method it is move Focus to the parent:
private void openButton_Click(object sender, RoutedEventArgs e)
{
FrameworkElement parent = (FrameworkElement)MyTextBox.Parent;
while (parent != null && parent is IInputElement && !((IInputElement)parent).Focusable)
{
parent = (FrameworkElement)parent.Parent;
}
DependencyObject scope = FocusManager.GetFocusScope(MyTextBox);
FocusManager.SetFocusedElement(scope, parent as IInputElement);
}
For the latter case, I created attached behavior to make it more convenient to use, which can be founded here:
Set focus back to its parent?
Edit
If you want to be when you close Popup focus back to the TextBox, then add handlers of events Opened and Closed for Popup like this:
private void MyPopup_Opened(object sender, EventArgs e)
{
Keyboard.ClearFocus();
StackPanelInPopup.Focus();
}
private void MyPopup_Closed(object sender, EventArgs e)
{
MyTextBox.Focus();
}

WPF - Maximizing borderless window by taking in account the user taskbar

I am creating a WPF window with a custom chrome, so I setted ResizeMode="NoResize" and WindowStyle="None" to implement my own chrome. However, there is an issue while maximizing the borderless window: it takes the whole screen.
I found the following trick to fix part of the issue:
http://chiafong6799.wordpress.com/2009/02/05/maximizing-a-borderlessno-caption-window/
This successfully restrain the window size to prevent from covering a taskbar. However, if the user have his taskbar positionned at the left or at the top, this won't work, as the window is at position 0,0.
Is there any way to retrieve more accurately the available area, or to query the user taskbar's position so I can position the maximized window accordingly?
I had a quick play around and it seems that setting the Windows Left and Top properties is ignored when setting WindowState.Maximized with a borderless form.
One workaround would be to ignore the WindowState functions and create your own Maximize/Restore functions
Rough example.
public partial class MainWindow : Window
{
private Rect _restoreLocation;
public MainWindow()
{
InitializeComponent();
}
private void MaximizeWindow()
{
_restoreLocation = new Rect { Width = Width, Height = Height, X = Left, Y = Top };
System.Windows.Forms.Screen currentScreen;
currentScreen = System.Windows.Forms.Screen.FromPoint(System.Windows.Forms.Cursor.Position);
Height = currentScreen.WorkingArea.Height;
Width = currentScreen.WorkingArea.Width;
Left = currentScreen.WorkingArea.X;
Top = currentScreen.WorkingArea.Y;
}
private void Restore()
{
Height = _restoreLocation.Height;
Width = _restoreLocation.Width;
Left = _restoreLocation.X;
Top = _restoreLocation.Y;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
MaximizeWindow();
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
Restore();
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
DragMove();
}
base.OnMouseMove(e);
}
}
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="74.608" Width="171.708" ResizeMode="NoResize" WindowStyle="None">
<Grid>
<Button Content="Max" HorizontalAlignment="Left" Margin="0,29,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click_1"/>
<Button Content="Restore" HorizontalAlignment="Left" Margin="80,29,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click_2"/>
</Grid>
</Window>
Obviously you will want to clean this code up, but it seems to work wherever the Taskbar is located, However you may need to add some logic to get the correct Left, Top if the users font DPI is larger than 100%
Another way to do this is by handling the WM_GETMINMAXINFO Win32 message. The code here shows how to do that.
Note that there are a few things that I would do differently, such as returning IntPtr.Zero instead of (System.IntPtr)0 in WindowProc and making MONITOR_DEFAULTTONEAREST a constant. But that's just coding style changes, and doesn't affect the net result.
Also make sure to pay attention to the update where the WindowProc is hooked during the SourceInitialized event instead of OnApplyTemplate. That's the better place to do it. If you're implementing a class derived from Window, then another option is to override OnSourceInitialized to hook the WindowProc instead of attaching to the event. That's what I normally do.

Slider MouseLeftButtonDown Doesn't Work?

When trying to use a Slider control I'd like to listen to the MouseLeftButtonDown and MouseLeftButtonUp. I have handlers set up for both events. The MouseLeftButtonUp works as expected. MouseLeftButtonDown is not raised at all.
Any ideas why?
I've done a bit of googling and it seems that the WPF doesn't fire either. One of the solutions (in this post) was to use the Preview version of the events which is something silverlight doesn't support.
Is there any simple solution to this that I'm not seeing?
Thanks
J
It happens because Slider handles mouse down/up events. Internally its implemented as two RepeatButtons and a thumb in the middle. When you click on left or right side of the slider your mouse events are handled by RepeatButtons, and you don't get them.
If you still want to handle handled event you can use AddHandler() method. Here is Silverlight example:
XAML
<Slider Width="100"
VerticalAlignment="Top"
Minimum="0"
Maximum="100"
Name="sl" />
C#
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
sl.AddHandler(MouseLeftButtonDownEvent, new MouseButtonEventHandler(Slider_MouseLeftButtonDown), true);
sl.AddHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(Slider_MouseLeftButtonUp), true);
}
private void Slider_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
}
private void Slider_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
}
}
In WPF situation is almost same (small differences in names).

Resources