Custom window drag handler interferes with scrollviewer bar drag - wpf

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.

Related

How to click controls inside Popup with StaysOpen as False

I have a Popup with a ListView inside. I'd like to set StaysOpen as False so that it properly closes when I click away from the popup. However, this means that all mouse events are intercepted. As such, I don't get any mouse events inside my ListView.
Here's my current setup. I've removed all styling to make it easier to see what's going on.
<Popup Name="puSearchResults" StaysOpen="False"
AllowsTransparency="True"
LostFocus="puSearchResults_LostFocus"
LostKeyboardFocus="puSearchResults_LostKeyboardFocus"
LostMouseCapture="puSearchResults_LostMouseCapture" >
<ListView Name="lvSearchResults"
MouseLeftButtonDown="lbSearchResults_MouseLeftButtonDown"
SelectionChanged="lvSearchResults_SelectionChanged"/>
</Popup>
In this case, MouseLeftButtonDown only works if I set StaysOpen to True, but then the Popup doesn't disappear if I click away.
Ideas?
I would add mouse events to the ListView or to the objects in your ListView's data templete.
Then you could do this:
private void lvSearchResults_MouseEnter(object sender, MouseEventArgs e)
{
puSearchResults.StaysOpen = true;
}
private void lvSearchResults_MouseLeave(object sender, MouseEventArgs e)
{
puSearchResults.StaysOpen = false;
}
EDIT
Similar question that should help: Close Wpf Popup On click of item of its own control template

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();
}

Canvas in ScrollViewer (Preview)MouseButtonDown event order

If we have
<ScrollViewer Name="scroll_viewer" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Canvas Name="canvas" Height="200" Width="200">
<Rectangle Fill="AliceBlue" Width="100" Height="100"/>
</Canvas>
</ScrollViewer>
with handlers for:
scroll_viewer.PreviewMouseLeftButtonDown
scroll_viewer.MouseLeftButtonDown
canvas.PreviewMouseLeftButtonDown
Then if we click in the Rectangle we get scroll_viewer_PreviewMouseLeftButtonDown called first then canvas_PreviewMouseLeftButtonDown but scroll_viewer_MouseLeftButtonDown is not called.
I want to handle the click event first in the canvas - if an object is clicked I want to handled the event (for object drag). If no canvas object is clicked I want to handle event in scroll_viewer (to manage scrollview panning with the mouse).
How to manage this given that the call order is the oposite of what i want and that the non perview version scroll_viewer.MouseLeftButtonDown is not called?
UPDATE:
From this post: Silverlight forums
((FrameworkElement)scroll_viewer.GetValue(ScrollViewer.ContentProperty)).MouseLeftButtonDown += scroll_viewer_MouseLeftButtonDown;
DOES work ie does get called after the preview events - can some explain why this less than obvious syntax is required?
The problem is that the ScrollViewer already handles the MouseLeftButtonDown event internally, like so:
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
if (base.Focus())
e.Handled = true;
base.OnMouseLeftButtonDown(e);
}
You can "fix" this using a custom class, like so:
public class MyScrollViewer : ScrollViewer {
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
base.OnMouseLeftButtonDown(e);
e.Handled = false;
}
}
SIDE NOTE: You should use x:Name in XAML, not Name. Otherwise you may run into compilation errors using the above class.
Alternatively, you could attach your handler for all MouseLeftButtonDown events, including handled ones. So instead of:
this.scroll_viewer.MouseLeftButtonDown += new MouseButtonEventHandler(scroll_viewer_MouseLeftButtonDown);
You'd use:
this.scroll_viewer.AddHandler(ScrollViewer.MouseLeftButtonDownEvent, new MouseButtonEventHandler(this.scroll_viewer_MouseLeftButtonDown), true);
The Preview events follow a routing strategy similar to the Tunneling strategy, meaning that the event starts at the top of the element tree, and travels down it. So it would hit your ScrollViewer first, then your Canvas.
The non-Preview events follow a routing strategy similar to the Bubbling strategy, meaning that events start on the object they occurred on, and travel up the element tree. In this case, the Canvas would get hit first, then the ScrollViewer.
You can read more about the Routing strategies here
As a side note, for Canvas objects to be visible for HitTest events, they need to have a non-transparent background. So if you have a Canvas with no background color specified, it will default to Transparent and not be visible for HitTests.

Cant drag and move a WPF Form

I design a WPF form with Window Style=None. So I Cannot see the drag bar in the form. How can I move the Form with WindowStyle=None Property?
I am using a main window to hold pages (creating a navigation style program), and in the code behind of my main window, I inserted this...
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
// Begin dragging the window
this.DragMove();
}
... and it works like a charm. This is with windowstyle=none. Its nice in the sense that you can click anywhere on the app and move it instead of just being limited to a top bar.
See this question.
Basically you use the Window.DragMove method for this.
In our application we have Windows with WindowStyle set to "none", we implemented the functionality to drag the Window, but only from the header rather than from any point in the Window. We did this by adding a Border as a header, then adding a Thumb to fill the entire Border. We then handle the DragDelta method on the Thumb in the code-behind for the Window.
<Border
Name="headerBorder"
Width="Auto"
Height="50"
VerticalAlignment="Top"
CornerRadius="5,5,0,0"
DockPanel.Dock="Top"
Background="{StaticResource BackgroundBrush}"
BorderThickness="1,1,1,1"
BorderBrush="{StaticResource BorderBrush}">
<Grid>
<Thumb
x:Name="headerThumb"
Opacity="0"
Background="{x:Null}"
Foreground="{x:Null}"
DragDelta="headerThumb_DragDelta"/>
</Grid>
</Border>
Then in the code-behind we have the following event handler...
private void headerThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
Left = Left + e.HorizontalChange;
Top = Top + e.VerticalChange;
}
I don't know if this is any better than the other method, it's just the way we did it.
either inside the windows on load function or inside the grid's on load function use a deligate to trigger the DragMove() method on Mouse Click
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
this.MouseLeftButtonDown += delegate{DragMove();};
}
If you simply add this.DragMove(); and you are using Bing Maps, then you will get some frustrating behavior when trying to pan the map.
Using TabbyCool's answer was a good solution however, you can not drag the window against the top of the screen to maximise it.
My solution was just checking that the position.Y of my click relative to my top bar grid was less than a suitable amount.
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
Point pt = e.GetPosition(topBar);
Debug.WriteLine(pt.Y);
if (pt.Y < topBar.ActualHeight)
{
DragMove();
}
}

Silverlight MouseLeave Issue

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

Resources