Seemingly strange WPF behaviour: textbox lostfocus event and button click event - wpf

The following behaviour is occurring with the .net 4.0 code set out further below:
Click on textbox so it gains focus, and then click button:
As code stands, lostfocus handler is called, but not buttonclick handler
Comment out MessageBox.Show("handlelostfocus") and then click handler is called
Set breakpoint in handlelostfocus and breakpoint is hit, but click handler is not called
Are these bugs or behaviour by design - if later, is there any further explanation?
<Window x:Class="WpfApplication4.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">
<Grid>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="216,194,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="197,108,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" />
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
textBox1.LostFocus += new RoutedEventHandler(handlelostfocus);
}
private void handlelostfocus(object sender, RoutedEventArgs e)
{
MessageBox.Show("handlelostfocus");
}
private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("click");
}
}

Change ClickMode property of Button to "Press"
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="216,194,0,0" ClickMode="Press" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" MouseUp="button1_MouseUp" MouseLeftButtonUp="button1_MouseLeftButtonUp" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="197,108,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" />

The 'click' in this case never occurs because as H.B. indicated you are interrupting the UI/event logic by showing the modal messagebox so there is never a mouse down event on the button.
Try replacing the messagebox with a non-modal wndow such as this:
new Window() { Width = 300, Height = 100, Title = "handlelostfocus" }.Show();
and you will see that the events still occur because you are not drawing the focus away from the main window in the middle of the event logic.

You interrupt the click logic, to get a click both mouse-down and its mouse-up need to occur consecutively on the Button; thus the observed behavior seems fine to me.

Related

Same click event for both Button and Grid

I read about Routed events today and tried to provide same click event handler for both a normal button and a custom grid button. Stackpanel is handling the routed button click event, and to invoke same handler I am firing a click event from grid's mousedown event. Code is without error but not working as expected.
Button click brings the messagebox but clicking the grid with mouse does nothing.
<Window x:Class="RoutedEventPr.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="550" Width="525">
<StackPanel Background="Transparent" Button.Click="Button_Click_1">
<Grid Width="200" Height="100" Background="Aqua" MouseDown="Grid_MouseDown_1">
<Ellipse StrokeThickness="4" Width="200" Height="100" Fill="Beige"/>
<TextBlock Text="Press" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
<Button x:Name="SureBtn" Content="Pause !" Width="200" Margin="0 10 0 0"/>
<Image HorizontalAlignment="Center" VerticalAlignment="Center" Width="200" Height="200" Source="I://fancybtn.jpg"/>
<Label Content="Start the game"/>
</StackPanel>
private void Grid_MouseDown_1(object sender, MouseButtonEventArgs e)
{
RaiseEvent(new RoutedEventArgs(Button.ClickEvent, this));
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
MessageBox.Show("Are you sure !");
}
Button.Click event is routed event with bubbling strategy set to Bubble.
That means it will bubble up to visual parent till root until it was handled. In your case, you raise an event from Window, so it will bubble up from Window to its parent which is null.
You have to raise the event from child control of StackPanel so that it can bubble up to StackPanel.
XAML
<Grid x:Name="grid" Width="200" Height="100" Background="Aqua"
MouseDown="Grid_MouseDown_1">
<Ellipse StrokeThickness="4" Width="200" Height="100" Fill="Beige"/>
<TextBlock Text="Press" HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
Code behind
private void Grid_MouseDown_1(object sender, MouseButtonEventArgs e)
{
grid.RaiseEvent(new RoutedEventArgs(Button.ClickEvent, this));
}
I did below changes and it works now.
private void Grid_MouseDown_1(object sender, MouseButtonEventArgs e)
{
SureBtn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent, this));
}

How to cancel a left mouseclick on button release (Preview...Up)

I am developing a dragScrollViewer and are experiencing some weird stuff when using "e.Handled=true" in the OnPreviewMouseLeftButtonUp function.
On the left mousedown, it is not known if the user wants to "click" on the below or if he wants to "swipe/drag" with the mouse. If he has started to "swipe/drag", I would like to "eat/cancel" the mouse click.
This should be very simple... a "e.Handled = true" in the OnPreviewMouseLeftButtonUp function should stop the mouseclick from hitting the higher level buttons (below the mouse). However this gives a very strange behavior... the click (and it's coordinates) is stored and is thrown later (next time the user clicks).
I don't know if there is something wrong my code or if there is a bug in the WPF Routed Events framework... is anyone able to reproduce the problem? (in order to make the code simpler, I have removed all dragging code)
How to reproduce the problem:
Make a clean project WPF project in Visual Studio.
Insert example source code DragScroller.cs/MainWindow.xaml/MainWindow.xaml.cs
Compile and click on a button - result: console writes "Inside dragScroller button clicked"
Next click on a button and start swiping (still staying inside the button area - result: mouse click is cancelled
Now click the "Outside dragScroller" - result: console writes "Inside dragScroller button clicked" (this is the stored "mouse" click)
Is there a better way to cancel the mouse click, if the decision to cancel the click is first known when the user releases the mouse button?
DragScroller.cs:
using System.Windows.Controls;
using System.Windows.Input;
namespace dragScroller
{
public class DragScrollViewer : ScrollViewer
{
private bool mouseDown;
private bool isDragging;
private int dragMoveCount;
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
CancelMouseDrag();
}
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnPreviewMouseLeftButtonDown(e);
dragMoveCount = 0;
mouseDown = true;
}
protected override void OnPreviewMouseMove(MouseEventArgs e)
{
base.OnPreviewMouseMove(e);
dragMoveCount++;
if (!mouseDown || isDragging || !(dragMoveCount > MoveTicksBeforeDrag)) return;
Cursor = Cursors.ScrollAll;
isDragging = true;
}
protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnPreviewMouseLeftButtonUp(e);
if (isDragging && dragMoveCount > MoveTicksBeforeDrag)
{
e.Handled = true;// Calling e.Handled here, has an unwanted effect on the next "up" event
CancelMouseDrag();
}
dragMoveCount = 0;
Cursor = Cursors.Arrow;
}
private void CancelMouseDrag()
{
isDragging = false;
mouseDown = false;
Cursor = Cursors.Arrow;
dragMoveCount = 0;
}
private const double MoveTicksBeforeDrag = 5; //times to call previewMouseMove before starting to drag (else click)
}
}
MainWindows.xaml:
<Window x:Class="dragScroller.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dragScroller="clr-namespace:dragScroller"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button Content="Outside dragScroller" Click="Button_Click" />
<dragScroller:DragScrollViewer x:Name="dragScroller" Friction="0.2" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Visible">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Button Content="Button #1" Grid.Column="0" Grid.Row="0" Click="Button_Click_Inside_DragScroller"></Button>
<Button Content="Button #2" Grid.Column="1" Grid.Row="0" Click="Button_Click_Inside_DragScroller"></Button>
<Button Content="Button #3" Grid.Column="2" Grid.Row="1" Click="Button_Click_Inside_DragScroller"></Button>
<Button Content="Button #4" Grid.Column="3" Grid.Row="1" Click="Button_Click_Inside_DragScroller"></Button>
<Button Content="Button #5" Grid.Column="4" Grid.Row="0" Click="Button_Click_Inside_DragScroller"></Button>
<Button Content="Button #6" Grid.Column="5" Grid.Row="0" Click="Button_Click_Inside_DragScroller"></Button>
<Button Content="Button #7" Grid.Column="6" Grid.Row="1" Click="Button_Click_Inside_DragScroller"></Button>
<Button Content="Button #8" Grid.Column="7" Grid.Row="1" Click="Button_Click_Inside_DragScroller"></Button>
</Grid>
</dragScroller:DragScrollViewer>
</StackPanel>
</Window>
MainWindows.xaml.cs:
namespace dragScroller
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Outside dragScroller button clicked");
}
private void Button_Click_Inside_DragScroller(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Inside dragScroller button clicked");
}
}
}
I modified the OnPreviewMouseLeftButtonUp Method and manage to fixed this behavior.
The wrong event handler is called because the button is still in focus.
if you move the focus back to the main window it should work as expected:
protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnPreviewMouseLeftButtonUp(e);
if (isDragging && dragMoveCount > MoveTicksBeforeDrag)
{
e.Handled = true;// Calling e.Handled here, has an unwanted effect on the next "up" event
var x = e.Source as Button;
if (x != null)
{
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(x), Application.Current.MainWindow);
}
CancelMouseDrag();
}
dragMoveCount = 0;
Cursor = Cursors.Arrow;
}
hope it helps.

Binding when element is instantiated in code behind

I have two properties that need to be binded. One is Value and it is the UserControl's property and the second one is property from an image element. The image element is inside Window. The Window is intestated in the code behind (UserControl).
What do I need to add to enable proper binding between image src and property Value. The code is shown below.
<UserControl x:Class="nnnn">
<UserControl.Resources>
<propSheet:BitmapConverter x:Key="bitmapConverter" />
<Window x:Key="imagePopup"
Width="640"
Height="480">
<Grid Background="LightGray">
<Image Grid.Row="0" Source="{Binding Path=Value, Converter={StaticResource bitmapConverter}}" />
<TextBlock Text="{Binding Path=Value}"></TextBlock>
<Button Grid.Row="1"
Width="25"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Click="OpenFile_Click">
...
</Button>
</Grid>
</Window>
</UserControl.Resources>
<Grid>
<Button Click="ViewImage_Click">View Image</Button>
</Grid>
You would have to set the Window's DataContext property to the UserControl instance before showing it:
private void ViewImage_Click(object sender, RoutedEventArgs e)
{
var window = Resources["imagePopup"] as Window;
window.DataContext = this; // the UserControl
window.Show();
}

Alt+<Key> shortcut for combobox opening

Let's assume that we have the following XAML:
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
<Button Content="_Test" Margin="12" Width="200" Height="30" Click="OnClick" />
<ComboBox Margin="12" Width="200" Height="30" >
<ComboBox.Items>
<ComboBoxItem>First</ComboBoxItem>
<ComboBoxItem>Second</ComboBoxItem>
<ComboBoxItem>Third</ComboBoxItem>
</ComboBox.Items>
</ComboBox>
</StackPanel>
Alt+T shortcut will press the button. How can I make Alt+R shortcut opening the combobox dropdown?
Update: BTW, I'm aware about Label's Target property and I know that I can create KeyBinding (or something similar) and handle for example Ctrl+R, but I'm looking for more simple way.
I found the following solution. Not so simple as I expected, but I can live with it.
First, I need to specify the name of the ComboBox:
<ComboBox x:Name="ResourcesComboBox" Margin="12" Width="200" Height="30" >
<ComboBox.Items>
<ComboBoxItem>First</ComboBoxItem>
<ComboBoxItem>Second</ComboBoxItem>
<ComboBoxItem>Third</ComboBoxItem>
</ComboBox.Items>
</ComboBox>
Second, register in the view constructor the access key 'R' and open ComboBox in the event handler:
public MainView()
{
InitializeComponent();
AccessKeyManager.Register("R", ResourcesComboBox);
AccessKeyManager.AddAccessKeyPressedHandler(ResourcesComboBox, AccessKeyPressedEventHandler);
}
//...
private void AccessKeyPressedEventHandler(object sender, AccessKeyPressedEventArgs e)
{
ResourcesComboBox.IsDropDownOpen = true;
}

Drag and Drop WPF ComboBox, Buttons, Radio Buttons, etc

I have a problem, i've already read tutorials, blogs, etc about drag and drop on WPF (i'm using VS10).
The problem is I need to have a toolbox with buttons,combobox, radio button,etc sothe user can drag it and drop it(copy) on a work space (canvas or whatever).
I managed to do drag and drop from textbox and images but that doesn't work for me, when i tried on buttons or combobox it just doesnt work, i assume it is cause of the click event by default, i don't know what the problem is tho. Here is what i've tried with a button.
<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="350" Width="525">
<Grid>
<TextBox Height="22" HorizontalAlignment="Left" Margin="84,36,0,0" Name="textBox1" VerticalAlignment="Top" Width="103" Text="Drag" />
<TextBox Height="40" HorizontalAlignment="Left" Margin="225,136,0,0" Name="textBox3" VerticalAlignment="Top" Width="124" Text="Drop" />
<Label Content="DragLabel" Height="26" HorizontalAlignment="Left" Margin="284,36,0,0" Name="label1" VerticalAlignment="Top" Width="80" MouseDown="label1_MouseDown" />
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="84,122,0,0" Name="button1" VerticalAlignment="Top" Width="75" MouseDown="button1_MouseDown" AllowDrop="True" IsEnabled="True" Click="button1_Click" />
<Rectangle Height="100" HorizontalAlignment="Left" Margin="149,199,0,0" Name="rectangle1" Stroke="Black" VerticalAlignment="Top" Width="200" AllowDrop="True" Fill="#FFDCA1A1" />
</Grid>
My Code Behind ...
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void label1_MouseDown(object sender, MouseButtonEventArgs e)
{
Label lbl = (Label)sender;
DragDrop.DoDragDrop(lbl, lbl.Content, DragDropEffects.Copy);
}
private void button1_MouseDown(object sender, MouseButtonEventArgs e)
{
var dependencyObject = (Button)sender;
DragDrop.DoDragDrop(dependencyObject, dependencyObject, DragDropEffects.Move);
}
private void button1_Click(object sender, RoutedEventArgs e)
{
return;
}
}
Thank You in advance guys. Btw sry about my english :s...
Thx again!
Luis
Have you tried using the PreviewMouseDown event instead of MouseDown? Your code will get called before the Button can capture the click.
WPF elements normally use RoutedEvents which often have a corresponding "Preview" event that uses the Tunneling Routing Strategy, which will be sent to all parents before the element that actually raised the event. This allows you to perform your operation in response to the MouseDown before the Button gets a chance to try to execute a click action.
private void button1_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var dependencyObject = (Button)sender;
DragDrop.DoDragDrop(dependencyObject, dependencyObject, DragDropEffects.Move);
}
will work as mentioned by Abe

Resources