How programatically collapse Expander in Silverlight? - wpf

I need to be able to collapse expander on clicking anywhere outside the expander area. I am wondering what technique can be used. Any advice is highly appreciated.
Expander XAML - set binding to isExpanded:
<toolkit:Expander Header="Tasks" IsExpanded="{Binding IsExpanded}">
Code behind:
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value == _isExpanded)
return;
_isExpanded = value;
OnPropertyChanged("IsExpanded");
}
}

I assume you are using code behind.
Can you please try this in the Expander's MouseLeave & MouseEnter handlers
private void expander1_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
Application.Current.RootVisual.MouseLeftButtonDown += RootVisual_MouseLeftButtonDown;
}
void RootVisual_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.expander1.IsExpanded = false;
}
private void expander1_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
Application.Current.RootVisual.MouseLeftButtonDown -= RootVisual_MouseLeftButtonDown;
}

Related

TextBox (PasswordBox) SelectAll method doesn't work

I have some concept question here. I know how to select all text in TextBox or in PasswordBox. Via GotKeyboardFocus and PreviewMouseLeftButtonDown events, you know. This works fine.
XAML:
PreviewMouseLeftButtonDown="PasswordOnPreviewMouseDown"
GotKeyboardFocus="SelectAllPassword"
CodeBehind
private void SelectAllPassword(Object sender, RoutedEventArgs e)
{
var pb = (sender as PasswordBox);
if (pb != null)
pb.SelectAll();
}
private void PasswordOnPreviewMouseDown(Object sender, MouseButtonEventArgs e)
{
var pb = (sender as PasswordBox);
if (pb != null)
if (!pb.IsKeyboardFocusWithin)
{
e.Handled = true;
pb.Focus();
}
}
But question is - why this doesn't work?
XAML:
PreviewMouseLeftButtonDown="PasswordOnPreviewMouseDown"
CodeBehind:
private void PasswordOnPreviewMouseDown(Object sender, MouseButtonEventArgs e)
{
_txtPassword.SelectAll();
e.Handled = true;
}
Where _txtPassword - TextBox or PasswordBox control. So why I'm enforsed to Focus text control?
Actually, the selection is working.
You may feel that the text is not selected because it's not visually highlighted, but that's because the TextBox is not focused.
Try giving focus to your TextBox with the Tab key, you'll see the whole text highlighted.

How to select first/previous/next/last row in wpf DataGrid

I've got a WPF application which contains a standard DataGrid showing a lot of data.
Now I want to add buttons to select the next/first/previous/last row.
Is there a way to tell the DataGrid to change the selection for me?
I tried to use
private void SelectNext_Click(object sender, RoutedEventArgs e)
{
datagrid.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
but it does not work for me (does not change the selected item, sets just the cell focus to the first item instead).
private void SelectNext_Click(object sender, RoutedEventArgs e)
{
if (dataGrid.Items.Count - 1 > datagrid.SelectedIndex)
{
datagrid.SelectedIndex++;
}
}
private void SelectPrevious_Click(object sender, RoutedEventArgs e)
{
if (dataGrid.SelectedIndex > 0)
{
datagrid.SelectedIndex--;
}
}
private void SelectFirst_Click(object sender, RoutedEventArgs e)
{
dataGrid.SelectedIndex = 0;
}
private void SelectLast_Click(object sender, RoutedEventArgs e)
{
dataGrid.SelectedIndex = dataGrid.Items.Count - 1;
}
If you fill your datagrid with a datasource by databinding, you can do that
Example of button "next"
private void btnNext_Click(object sender, RoutedEventArgs e)
{
ICollectionView view = CollectionViewSource.GetDefaultView(yourDataSource);
view.MoveCurrentToNext();
}
The interface ICollectionView has all the methods you need for moving the current item
In XAML remember also to set
<DataGrid ..... IsSynchronizedWithCurrentItem="True" >

Problems with event bubbeling (ScrollViewer)

I have a problem with bubbeling events. I manage to bubble events in borders, grid, stackpanel, but not in a ScrollViewer
If you look at the example below you will notice that when you click the TextBlock the event is bubbeled up to the Grid. But when I include the ScrollViewer the event stops here and is not sent up to the Grid.
Does anyone now whay this happends and if it can be fixed? I really need to be able to bubble events through a ScrollViewer as I use it all the time.
<Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown">
<!--<ScrollViewer MouseLeftButtonDown="ScrollViewer_MouseLeftButtonDown">-->
<StackPanel Orientation="Vertical" MouseLeftButtonDown="StackPanel_MouseLeftButtonDown">
<TextBlock Text="Click me to bubble an event" MouseLeftButtonDown="TextBlock_MouseLeftButtonDown"/>
</StackPanel>
<!--</ScrollViewer>-->
</Grid>
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("LayoutRoot clicked");
}
private void ScrollViewer_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("ScrollViewer clicked");
e.Handled = false;
}
private void StackPanel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("StackPanel clicked");
e.Handled = false;
}
private void TextBlock_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Textblock clicked");
e.Handled = false;
}
}
use AddHandler(yourDelegate, True); syntax for adding event handlers, which will ignore Handled flag set by other controls in the visual tree.
I had this problem and the fix posted by user572559 fixed my issue. For those that need it, below is what I did (modified for posting):
_scrollViewer = new ScrollViewer();
_scrollViewer.AddHandler(
ScrollViewer.MouseLeftButtonDownEvent,
new MouseButtonEventHandler(OnMouseLeftButtonDown),
true);
_scrollViewer.AddHandler(
ScrollViewer.MouseLeftButtonUpEvent,
new MouseButtonEventHandler(OnMouseLeftButtonUp),
true);
...
void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
...
}
void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
...
}
Also note that if you hare handling these you may be handling MouseMove as well. MouseMove worked for me without needing to do this, and it also does not seem to be supported in this fashion (not a bubbling event).
You can prevent e.Handled on MouseButtonEventArgs by overriding ScrollViewer like this
public sealed class ClickScrollViewer : ScrollViewer
{
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
this.Focus();
}
}

Keeping keyboard focus on a single control while still beeing able to use a ListBox

Working on a TouchScreen application which also has a keyboard attached, I have the following problem:
The WPF window has a TextBox, which should receive ALL keyboard input. There are also Buttons and a ListBox, which are solely used by the TouchScreen(=Mouse).
A very simple example looks like this:
<Window x:Class="KeyboardFocusTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1">
<StackPanel>
<TextBox Text="{Binding Input, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PreviewLostKeyboardFocus="TextBox_PreviewLostKeyboardFocus"/>
<Button Click="Button_Click">Add</Button>
<ListBox ItemsSource="{Binding Strings}" />
</StackPanel>
</Window>
To keep the TextBox always focused, I just do:
private void TextBox_PreviewLostKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
{
e.Handled = true;
}
So far so good - the problem now is, that I can't select items from the ListBox anymore. This only seems to work, if the ListBox has the keyboard focus. But if I loose the keyboard focus on the TextBox, I can't enter text anymore without clicking it first.
Any ideas, comments suggestions are welcome!
There might be a more elegant solution for this, but you could always handle the PreviewKeyDown event at the Window level, and pass focus to the TextBox if it doesn't already have it, instead of preventing it from losing focus in the first place. That way, the ListBox can use focus as is normal, but as soon as a key is pressed focus jumps right to the TextBox. In addition, you can filter out keys that you don't want to switch focus - the arrow keys come to mind, which could then be used to move up and down in the ListBox.
Adding an event handler like the following should do the trick:
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (!textBox.IsFocused)
{
textBox.Focus();
}
}
Based on Nicholas' suggestion (thx!), here's a markup extension, which is used like:
<TextBox Helpers:KeyboardFocusAttractor.IsAttracted="true" />
It seems to work, and ANTS didn't show any memory leaks. But when it comes to WPF and especially events and bindings, you never know, so use with care!
public static class KeyboardFocusAttractor
{
public static readonly DependencyProperty IsAttracted = DependencyProperty.RegisterAttached("IsAttracted",
typeof (bool), typeof (KeyboardFocusAttractor), new PropertyMetadata(false, OnIsAttracted));
private static void OnIsAttracted(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var isAttracted = (bool) e.NewValue;
var controlWithInputFocus = d as Control;
if (controlWithInputFocus != null)
{
if (isAttracted)
{
new KeyboardInputFocusEventManager(controlWithInputFocus);
}
}
}
public static void SetIsAttracted(DependencyObject dp, bool value)
{
dp.SetValue(IsAttracted, value);
}
public static bool GetIsAttracted(DependencyObject dp)
{
return (bool) dp.GetValue(IsAttracted);
}
private class KeyboardInputFocusEventManager
{
private readonly Control _control;
private Window _window;
public KeyboardInputFocusEventManager(Control control)
{
_control = control;
_control.Loaded += ControlLoaded;
_control.IsVisibleChanged += ControlIsVisibleChanged;
_control.Unloaded += ControlUnloaded;
}
private void ControlLoaded(object sender, RoutedEventArgs e)
{
_window = Window.GetWindow(_control);
if (_window != null)
{
_control.Unloaded += ControlUnloaded;
_control.IsVisibleChanged += ControlIsVisibleChanged;
if (_control.IsVisible)
{
_window.PreviewKeyDown += ParentWindowPreviewKeyDown;
}
}
}
private void ControlUnloaded(object sender, RoutedEventArgs e)
{
_control.Unloaded -= ControlUnloaded;
_control.IsVisibleChanged -= ControlIsVisibleChanged;
}
private void ControlIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (_window != null)
{
_window.PreviewKeyDown -= ParentWindowPreviewKeyDown;
}
if (_control.IsVisible)
{
_window = Window.GetWindow(_control);
if (_window != null)
{
_window.PreviewKeyDown += ParentWindowPreviewKeyDown;
}
}
}
private void ParentWindowPreviewKeyDown(object sender, KeyEventArgs e)
{
Keyboard.Focus(_control);
}
}
}

Interpret enter as tab WPF

I want to interpret Enter key as Tab key in whole my WPF application, that is, everywhere in my application when user press Enter I want to focus the next focusable control,except when button is focused. Is there any way to do that in application life circle? Can anyone give me an example?
Thanks a lot!
You can use my EnterKeyTraversal attached property code if you like. Add it to the top-level container on a WPF window and everything inside will treat enter as tab:
<StackPanel my:EnterKeyTraversal.IsEnabled="True">
...
</StackPanel>
Based on Richard Aguirre's answer, which is better than the selected answer for ease of use, imho, you can make this more generic by simply changing the Grid to a UIElement.
To change it in whole project you need to do this
In App.xaml.cs:
protected override void OnStartup(StartupEventArgs e)
{
EventManager.RegisterClassHandler(typeof(UIElement), UIElement.PreviewKeyDownEvent, new KeyEventHandler(Grid_PreviewKeyDown));
base.OnStartup(e);
}
private void Grid_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
var uie = e.OriginalSource as UIElement;
if (e.Key == Key.Enter)
{
e.Handled = true;
uie.MoveFocus(
new TraversalRequest(
FocusNavigationDirection.Next));
}
}
Compile.
And done it. Now you can use enter like tab.
Note: This work for elements in the grid
I got around woodyiii's issue by adding a FrameworkElement.Tag (whose value is IgnoreEnterKeyTraversal) to certain elements (buttons, comboboxes, or anything I want to ignore the enter key traversal) in my XAML. I then looked for this tag & value in the attached property. Like so:
if (e.Key == Key.Enter)
{
if (ue.Tag != null && ue.Tag.ToString() == "IgnoreEnterKeyTraversal")
{
//ignore
}
else
{
e.Handled = true;
ue.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
woodyiii, There is a function in the UIElement called PredictFocus() which by its name know its function, then you can check if that element is enabled or not so as to move the focus to it or not...
Here is Matt Hamilton's code, if anyone is wondering since his site is down apparently:
public class EnterKeyTraversal
{
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
static void ue_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
var ue = e.OriginalSource as FrameworkElement;
if (e.Key == Key.Enter)
{
e.Handled = true;
ue.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
private static void ue_Unloaded(object sender, RoutedEventArgs e)
{
var ue = sender as FrameworkElement;
if (ue == null) return;
ue.Unloaded -= ue_Unloaded;
ue.PreviewKeyDown -= ue_PreviewKeyDown;
}
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool),
typeof(EnterKeyTraversal), new UIPropertyMetadata(false, IsEnabledChanged));
static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ue = d as FrameworkElement;
if (ue == null) return;
if ((bool)e.NewValue)
{
ue.Unloaded += ue_Unloaded;
ue.PreviewKeyDown += ue_PreviewKeyDown;
}
else
{
ue.PreviewKeyDown -= ue_PreviewKeyDown;
}
}
}
Another, a more on/off implementation approach would be to use behaviors:
public class TextBoxEnterFocusesNextBehavior :
Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewKeyDown += AssociatedObjectOnPreviewKeyDown;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewKeyDown -= AssociatedObjectOnPreviewKeyDown;
base.OnDetaching();
}
private void AssociatedObjectOnPreviewKeyDown(object sender, KeyEventArgs args)
{
if (args.Key != Key.Enter) { return; }
args.Handled = true;
AssociatedObject.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
Usage example:
<UserControl xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:behaviors="clr-namespace:Your.Namespace.To.Behaviors"
...>
<DockPanel>
<TextBox x:Name="TextBoxWithBehavior"
DockPanel.Dock="Top">
<b:Interaction.Behaviors>
<behaviors:TextBoxEnterFocusesNextBehavior />
</b:Interaction.Behaviors>
</TextBox>
<TextBox x:Name="TextBoxWithoutBehavior"
DockPanel.Dock="Top" />
<TextBox x:Name="AnotherTextBoxWithBehavior"
DockPanel.Dock="Top">
<b:Interaction.Behaviors>
<behaviors:TextBoxEnterFocusesNextBehavior />
</b:Interaction.Behaviors>
</TextBox>
</DockPanel>
</UserControl>
My solution:
public class MoveToNext : TriggerAction<DependencyObject>
{
protected override void Invoke(object parameter)
{
if (parameter is RoutedEventArgs routedEventArgs && routedEventArgs.OriginalSource is FrameworkElement element)
{
routedEventArgs.Handled = true;
element.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
}
Usage:
<StackPanel>
<i:Interaction.Triggers>
<i:KeyTrigger Key="Return">
<util:MoveToNext/>
</i:KeyTrigger>
</i:Interaction.Triggers>
<!-- put your controls here -->
</StackPanel>
If you want the behavior to be attached to only one control instead of all controls within a layouter, simply add the <i:Interaction.Triggers block to that specific control.

Resources