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.
Related
I'm new to WPF, so please be patient with me. I have a bidimensional int array (8x8), a uniform grid (8 rows and 8 columns). In each cell of my grid there is a CheckBox. When I click on a checkbox I wish that the corresponding element in my array to change from "0" to "1". When I uncheck again the CheckBox, I wish that this change be reflected in my array.
Further, I will take each row from in my int[8,8] matrix (for example 10010101 - first checkbox in the uniform grid is checked, the second is not checked and so on), convert it to a decimal number and send it over the serial port when I click a button.
Then I want to change the sending process ( process all 8 rows and send the data) in such way that it takes place every time I click on a checkbox from my grid.
I'm stucked with the Bindings.
I hope someone can give me an advice on how should I to do this.
This is my XAML code. Somehow I succeeded to make a binding for my first CheckBox but I'm not understanding well why it's working and If I did the best coding. If I continue in this way I should create a property for every element in my array and I'm sure this is not the right way to do it (because I hardcoded data[0,0]).
<Window x:Class="CheckBox_Matrix_Binding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="220" Width="200" ResizeMode="NoResize" >
<Grid x:Name="mainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="4*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<UniformGrid x:Name="checkBoxGrid" Grid.Row="0" Rows="3" Columns="3" >
<CheckBox x:Name="ChekBox0" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding Mode=TwoWay, Path=DataProperty}" />
<CheckBox x:Name="ChekBox1" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox x:Name="ChekBox2" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox x:Name="ChekBox3" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox x:Name="ChekBox4" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox x:Name="ChekBox5" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox x:Name="ChekBox6" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox x:Name="ChekBox7" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox x:Name="ChekBox8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</UniformGrid>
<Grid Grid.Row="1" x:Name="buttonsGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="btnSetTrue" VerticalAlignment="Center" Grid.Column="0" Content="Set _True" Margin="5" Click="btnSetTrue_Click" ></Button>
<Button x:Name="btnSetFalse" VerticalAlignment="Center" Grid.Column="1" Content="Set _False" Margin="5" Click="btnSetFalse_Click" ></Button>
</Grid>
</Grid>
</Window>
This is my code behind:
using System;
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
namespace CheckBox_Matrix_Binding
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private DataMatrix _dataMatrix = new DataMatrix() ;
public bool _DataMatrixProperty
{
get { return _dataMatrix.DataProperty; }
set
{
_dataMatrix.DataProperty = value;
}
}
public MainWindow()
{
InitializeComponent();
checkBoxGrid.DataContext = _dataMatrix;
}
private void ChekBox0_Checked(object sender, RoutedEventArgs e)
{
}
private void btnSetTrue_Click(object sender, RoutedEventArgs e)
{
_dataMatrix.DataProperty = true;
}
private void btnSetFalse_Click(object sender, RoutedEventArgs e)
{
_dataMatrix.DataProperty = false;
}
}
//THe class DataMatrix implements INotifyPropertyChanged interface
public class DataMatrix: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool[,] data;
public bool DataProperty //this is a property
{
get { return data[0, 0]; }
set {
data[0, 0] = value;
OnPropertyChanged("DataProperty");
}
}
public DataMatrix()
{
data = new bool[3, 3];
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Should I use another approach?
Thankyou in advance :)
I am creating a WPF Application in VS 2012.
My MainWindow acts as a header/footer wrapper for the pages that are navigated to.
Within my footer I have added custom back/forward buttons, but I want their visibility to change based off of the CanGoBack and CanGoForward properties.
I can create C# routines that will hide/show the buttons but the function only runs once at initialization. I need these functions to fire off every time a new page is loaded. Any ideas?
Is is a simple example where I use Grids as Pages and i use a Grid array to save the instances of the grids. I hope this would help in your scenario.
XAML:
<Window x:Class="PageNavigation.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>
<Grid Name="page1" Background="Beige">
<Label VerticalAlignment="Center" HorizontalAlignment="Center">Page1</Label>
</Grid>
<Grid Name="page2" Background="Blue" Visibility="Hidden">
<Label VerticalAlignment="Center" HorizontalAlignment="Center">Page2</Label>
</Grid>
<Grid Name="page3" Background="Green" Visibility="Hidden" >
<Label VerticalAlignment="Center" HorizontalAlignment="Center">Page3</Label>
</Grid>
<Grid Name="page4" Background="Cyan" Visibility="Hidden">
<Label VerticalAlignment="Center" HorizontalAlignment="Center">Page4</Label>
</Grid>
<Button Name="btn_Next" HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Next" Click="btn_Next_Click"/>
<Button Name="btn_Previous" HorizontalAlignment="Left" VerticalAlignment="Bottom" Content="Previous" Click="btn_Previous_Click"/>
</Grid>
</Window>
C#:
public partial class MainWindow : Window
{
Grid[] pages;
int activePage = 0;
public MainWindow()
{
InitializeComponent();
pages = new Grid[4];
pages[0] = this.page1;
pages[1] = this.page2;
pages[2] = this.page3;
pages[3] = this.page4;
}
void Next() {
if (activePage<pages.Length-1)
{
pages[activePage].Visibility = Visibility.Hidden;
activePage++;
pages[activePage].Visibility = Visibility.Visible;
}
}
void Previous()
{
if (activePage > 0)
{
pages[activePage].Visibility = Visibility.Hidden;
activePage--;
pages[activePage].Visibility = Visibility.Visible;
}
}
private void btn_Previous_Click(object sender, RoutedEventArgs e)
{
Previous();
}
private void btn_Next_Click(object sender, RoutedEventArgs e)
{
Next();
}
}
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
In WPF MVVM environment, I'm trying to ensure that focus is given to a TextBox. What happens is that the Cursor appears in the TextBox but is not flashing and the TextBox does not have focus. The code is:
I have a Popup containing this UserControl:
<UserControl x:Class="Rendevous.BusinessModules.AdministrationModule.LogonView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="300"
xmlns:my="clr-namespace:Csla.Xaml;assembly=Csla.Xaml"
xmlns:ViewModels="clr-namespace:Rendevous.Common.ViewModels;assembly=Common"
Visibility="{Binding Path=IsViewVisible}"
FocusManager.FocusedElement="{Binding Path=passCodeTextBox}"
ViewModels:FocusExtension.IsFocused="{Binding IsPassCodeFocused}">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="Pass Code:"
VerticalAlignment="Center" />
<TextBox Grid.Column="1" Name="passCodeTextBox" Width="100"
VerticalAlignment="Center"
Margin="3"
Text="{Binding Path=PassCode, UpdateSourceTrigger=PropertyChanged}"
ViewModels:FocusExtension.IsFocused="{Binding IsPassCodeFocused}" />
<Button Grid.Column="2" VerticalAlignment="Center" Margin="3" Name="loginButton"
Content="Log In" />
<Button Grid.Column="3"
VerticalAlignment="Center"
Margin="3"
Name="cancelButton"
Content="Cancel" />
</Grid>
(I've removed some Button handling stuff!)
In my ViewModel, I have code like:
public void LogonButtonClicked(object sender, ExecuteEventArgs e)
{
if (securityService.Login(PassCode))
{
eventBroker.Invoke(EventName.CloseLogonView, this);
}
else
{
IsViewVisible = Visibility.Hidden;
msgService.ShowError("Pass Code was not recognised", "Logon Error");
IsViewVisible = Visibility.Visible;
PassCode = "";
IsPassCodeFocused = true;
}
}
I am using an attached property:
public class FocusExtension
{
public static readonly DependencyProperty IsFocusedProperty = DependencyProperty.RegisterAttached("IsFocused", typeof(bool?), typeof(FocusExtension), new FrameworkPropertyMetadata(IsFocusedChanged));
public static bool? GetIsFocused(DependencyObject element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (bool?)element.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject element, bool? value)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(IsFocusedProperty, value);
}
private static void IsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement fe = (FrameworkElement)d;
if (e.OldValue == null)
{
fe.GotFocus += FrameworkElement_GotFocus;
fe.LostFocus += FrameworkElement_LostFocus;
}
if ((bool)e.NewValue)
{
fe.Focus();
}
}
private static void FrameworkElement_GotFocus(object sender, RoutedEventArgs e)
{
((FrameworkElement)sender).SetValue(IsFocusedProperty, true);
}
private static void FrameworkElement_LostFocus(object sender, RoutedEventArgs e)
{
((FrameworkElement)sender).SetValue(IsFocusedProperty, false);
}
}
}
What happens is that the cursor appears in the TextBox but is not flashing. The TextBox does not have focus because nothing appears when you type. If you click on it, it works fine.
What have I done wrong?
I couldn't reproduce it using the code you provided, but two things I noticed are:
1) On LogonView, I think your intent was
FocusManager.FocusedElement="{Binding ElementName=passCodeTextBox}"
and not
FocusManager.FocusedElement="{Binding Path=passCodeTextBox}"
2) It looks like IsFocused is applied in multiple places. I'd try setting a breakpoint in IsFocusedChanged() and see which control gets it last.
Between that, and watching FocusManager.FocusedElement ( http://msdn.microsoft.com/en-us/library/system.windows.input.focusmanager.focusedelement.aspx ) and Keyboard.FocusedElement ( http://msdn.microsoft.com/en-us/library/system.windows.input.keyboard.focusedelement ) you should be able to track down where focus is really going.
XAML:
<Window x:Class="WorkOut.ToggleButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:WorkOut.Code"
Title="ToggleButton" Height="300" Width="300">
<Grid>
<StackPanel Orientation="Vertical">
<ToolBar Height="40" VerticalAlignment="Top">
<Button Margin="0,3,0,3" Padding="2" HorizontalContentAlignment="Left"
Command="{x:Static s:MyCanvas.AddNewTab}"
CommandTarget="{Binding ElementName=MyCanvas}">
<Button.Content>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Margin="3" Text="Append New Tab" VerticalAlignment="Center" Grid.Column="1"/>
</Grid>
</Button.Content>
</Button>
</ToolBar>
<Grid x:Name="MyGrid">
</Grid>
</StackPanel>
</Grid>
Code:
public ToggleButton()
{
InitializeComponent();
MyCanvas MyCanvas1 = new MyCanvas();
MyCanvas1.Name = "MyCanvas";
MyCanvas1.Background = System.Windows.Media.Brushes.LightBlue;
MyCanvas1.Height = 100;
MyCanvas1.Width = 100;
MyCanvas1.HorizontalAlignment = HorizontalAlignment.Left;
MyGrid.Children.Add(MyCanvas1);
MyCanvas MyCanvas2 = new MyCanvas();
MyCanvas2.Name = "MyCanvas";
MyCanvas2.Background = System.Windows.Media.Brushes.Beige;
MyCanvas2.Height = 100;
MyCanvas2.Width = 100;
MyCanvas2.HorizontalAlignment = HorizontalAlignment.Right;
MyGrid.Children.Add(MyCanvas2);
}
class MyCanvas : Canvas
{
public static RoutedCommand AddNewTab = new RoutedCommand();
public MyCanvas()
{
this.CommandBindings.Add(new CommandBinding(MyCanvas.AddNewTab, AddNewTab_Executed, AddNewTab_Enabled));
}
private void AddNewTab_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show (this.Background.ToString());
}
private void AddNewTab_Enabled(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
}
The above code create two canvas on grid control and the addnew button in tool bar disabled, eventhough it is bound to MyCanvas element.
may be i am following a wrong approach...
any help, much appreciated.
Thanks
KJ
Probably because your command returns on CanExecute() false, because it has no command target.
Have you tried in the CanExecute handler setting the event has handled?
private void AddNewTab_Enabled(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true
}