Set Focus in VisualStatemanager - c#

I write a custom control and want to set the focus on a control in case a VisualState goes active.
The control is a kind of ComboBox with a search box in the drop down popup. When I open it, the Opened Visual State goes active and the search box should be focused. Besides a dependency property bool IsDropDownOpen will be true.
PS: It's an Windows 10 UWP project.

Sorry, but you can set focus only programmatically, not by visual state :(

Not my favorite solution, but a workaround without accessing the TextBox from code behind.
I implemented an attached property which sets the focus to a control in case the property is set to true.
public class FocusHelper : DependencyObject
{
#region Attached Properties
public static readonly DependencyProperty IsFocusedProperty = DependencyProperty.RegisterAttached("IsFocused", typeof(bool), typeof(FocusHelper), new PropertyMetadata(default(bool), OnIsFocusedChanged));
public static bool GetIsFocused(DependencyObject obj)
{
return (bool)obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value)
{
obj.SetValue(IsFocusedProperty, value);
}
#endregion
#region Methods
public static void OnIsFocusedChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
{
var ctrl = s as Control;
if (ctrl == null)
{
throw new ArgumentException();
}
if ((bool)e.NewValue)
{
ctrl.Focus(FocusState.Keyboard);
}
}
#endregion
}
So I can bind this property to my IsDropDownOpen property. So every time I open the drop down, the TextBox will get the focus.
<TextBox helper:FocusHelper.IsFocused="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}" ...

Related

WPF - GetBindingExpression in PropertyChangedCallback of DependencyProperty

I need to be able to access the binding expression for the Text property of a TextBox from within a DependencyProperty on a TextBox. the value of my DependencyProperty is set in XAML. I'm calling GetBindingExpression in the PropertyChangedCallback method of my DependencyProperty, but I'm too early at this point because GetBindingExpression always returns null here, yet after the window fully loads it definitely returns a value (I tested using a button on screen to change the value of my DependencyProperty).
Clearly I have a load order issue here where my DependencyProperty's value is set before the Text property is bound to my view model. My question is, is there some event I can hook into to identify when the binding of the Text property is complete? Preferably without modifying the XAML of my TextBox as I have hundreds of them in the solution.
public class Foobar
{
public static readonly DependencyProperty TestProperty =
DependencyProperty.RegisterAttached(
"Test", typeof(bool), typeof(Foobar),
new UIPropertyMetadata(false, Foobar.TestChanged));
private static void TestChanged(DependencyObject o, DependencyPropertyChangedEventArgs args)
{
var textBox = (TextBox)o;
var expr = textBox.GetBindingExpression(TextBox.TextProperty);
//expr is always null here, but after the window loads it has a value
}
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
public static bool GetTest(DependencyObject obj)
{
return (bool)obj.GetValue(Foobar.TestProperty);
}
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
public static void SetTest(DependencyObject obj, bool value)
{
obj.SetValue(Foobar.TestProperty, value);
}
}
Try listen to LayoutUpdated event. I think its called layout updated event. Else google it. Its a crazy little event which will be fired everytime no matter what you do ex. when loading, drawing, even when you move your mouse!
Take a look at this pseudo code:
private static void TestChanged(DependencyObject o, DependencyPropertyChangedEventArgs args)
{
var textBox = (TextBox)o;
// start listening
textBox.LayoutUpdated += SomeMethod();
}
private static void SomeMethod(...)
{
// this will be called very very often
var expr = textBox.GetBindingExpression(TextBox.TextProperty);
if(expr != null)
{
// finally you got the value so stop listening
textBox.LayoutUpdated -= SomeMethod();

WPF MVVM - Update Dropdown When Clicked

I have a dropdown (ComboBox) that displays all the com ports available on a machine. Now, ports come and go when you connect and disconnect devices.
For performance reasons I don't want to keep calling System.IO.Ports.SerialPort.GetPortNames(), but rather just call that when the user clicks on the Combobox? Is this possible? Is there an MVVM approach to this problem?
Use InvokeCommandAction.
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
DropDownOpenedCommand is an ICommand property on your ViewModel.
<ComboBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="DropDownOpened">
<i:InvokeCommandAction Command="{Binding DropDownOpenedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
Edit: obviously DropDownOpened not SelectionChanged, as Patrice commented.
You can use something like MVVMLight's EventToCommand to accomplish this. Basically, the event of clicking the combo would be hooked to your MVVM command binding, which would then fire the method that calls GetPortNames().
Here are some alternatives:
MVVM Light: Adding EventToCommand in XAML without Blend, easier way or snippet? (check the accepted answer)
http://www.danharman.net/2011/08/05/binding-wpf-events-to-mvvm-viewmodel-commands/ (Prism)
What I would recommend is scrapping the 'only update on clicks' idea, and just use binding and notifications for this (unless for some reason you think there will be so many Connect/Disconnect events it will slow your system). The simplest version of that would be a dependency property.
Provide an IObservableList<Port> property as a dependency property on your ViewModel like this:
/// <summary>
/// Gets or sets...
/// </summary>
public IObservableList<Port> Ports
{
get { return (IObservableList<Port>)GetValue(PortsProperty); }
set { SetValue(PortsProperty, value); }
}
public static readonly DependencyProperty PortsProperty = DependencyProperty.Register("Ports", typeof(IObservableList<Port>), typeof(MyViewModelClass), new PropertyMetadata(new ObservableList<Port>));
Now you may add/remove items to/from that list whenever you connect or disconnect devices, just do not replace the list. This will force the list to send off a ListChangedEvent for each action on the list, and the ComboBox (or any other bound UI) will react to those events.
This should be performant enough for you, as this will only cause the UI ComboBox to update whenever an event goes through.
I took a stab at routing events to a command:
XAML:
<ComboBox
ItemsSource="{Binding Items}"
local:ControlBehavior.Event="SelectionChanged"
local:ControlBehavior.Command="{Binding Update}" />
Code:
using System;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
namespace StackOverflow
{
public class ControlBehavior
{
public static DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(ControlBehavior));
public static DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(ControlBehavior));
public static DependencyProperty EventProperty = DependencyProperty.RegisterAttached("Event", typeof(string), typeof(ControlBehavior), new PropertyMetadata(PropertyChangedCallback));
public static void EventHandler(object sender, EventArgs e)
{
var s = (sender as DependencyObject);
if (s != null)
{
var c = (ICommand)s.GetValue(CommandProperty);
var p = s.GetValue(CommandParameterProperty);
if (c != null && c.CanExecute(s))
c.Execute(s);
}
}
public static void PropertyChangedCallback(DependencyObject o, DependencyPropertyChangedEventArgs a)
{
if (a.Property == EventProperty)
{
EventInfo ev = o.GetType().GetEvent((string)a.NewValue);
if (ev != null)
{
var del = Delegate.CreateDelegate(ev.EventHandlerType, typeof(ControlBehavior).GetMethod("EventHandler"));
ev.AddEventHandler(o, del);
}
}
}
public string GetEvent(UIElement element)
{
return (string)element.GetValue(EventProperty);
}
public static void SetEvent(UIElement element, string value)
{
element.SetValue(EventProperty, value);
}
public ICommand GetCommand(UIElement element)
{
return (ICommand)element.GetValue(CommandProperty);
}
public static void SetCommand(UIElement element, ICommand value)
{
element.SetValue(CommandProperty, value);
}
public object GetCommandParameter(UIElement element)
{
return element.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(UIElement element, object value)
{
element.SetValue(CommandParameterProperty, value);
}
}
}

Scrolling in any place of application should cause scrolling in last used combobox

I want to add to my application, that when I use the mousewheel in any place of my application it should case a scrolling in the last active or last used combobox.
Can you help me? How should I proceed.
Thank you very much.
Here are two ways I attempted to solve the problem
Approach 1: Attached properties
I have created a class MouseExtension with an Attached Property ScrollAnywhere which will enable the bahavior on any element of your application. You may choose to have different scrollable region as well, so different combobox can be scrolled when mouse is in their respective regions. you may have a sub region which have this behavior for it's child control only. possibilities are unlimited.
Also this approach makes it injectable
example
<Window x:Class="CSharpWPF.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:CSharpWPF"
Title="MainWindow"
l:MouseExtension.ScrollAnywhere="true">
...
</Window>
I have set the property l:MouseExtension.ScrollAnywhere="true" in order to enable the behavior
code
namespace CSharpWPF
{
class MouseExtension : DependencyObject
{
public static bool GetScrollAnywhere(DependencyObject obj)
{
return (bool)obj.GetValue(ScrollAnywhereProperty);
}
public static void SetScrollAnywhere(DependencyObject obj, bool value)
{
obj.SetValue(ScrollAnywhereProperty, value);
}
// Using a DependencyProperty as the backing store for ScrollAnywhere. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ScrollAnywhereProperty =
DependencyProperty.RegisterAttached("ScrollAnywhere", typeof(bool), typeof(MouseExtension), new PropertyMetadata(false, OnScrollAnywhere));
private static void OnScrollAnywhere(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement element = d as UIElement;
if ((bool)e.NewValue)
element.PreviewMouseWheel += element_PreviewMouseWheel;
else
element.PreviewMouseWheel -= element_PreviewMouseWheel;
}
static void element_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
IInputElement element = FocusManager.GetFocusedElement(sender as DependencyObject);
if (element != null && e.Source != element)
{
MouseWheelEventArgs args = new MouseWheelEventArgs(Mouse.PrimaryDevice, e.Timestamp, e.Delta) { RoutedEvent = UIElement.MouseWheelEvent };
element.RaiseEvent(args);
e.Handled = true;
}
}
}
}
this property is also settable via styles or programmatically
eg
<Style x:key="MyScrollStyle">
<Setter Property="l:MouseExtension.ScrollAnywhere" Value="true"/>
</Style>
or
MouseExtension.SetScrollAnywhere(element, true);
or
element.SetValue(MouseExtension.ScrollAnywhereProperty, true);
Approach 2: Override method
this is rather simple approach if you can access the code behind of window or user control.
simply paste the code below in the class and rest will be handled
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
IInputElement element = FocusManager.GetFocusedElement(this);
if (element != null && e.Source != element)
{
MouseWheelEventArgs args = new MouseWheelEventArgs(Mouse.PrimaryDevice, e.Timestamp, e.Delta) { RoutedEvent = UIElement.MouseWheelEvent };
element.RaiseEvent(args);
e.Handled = true;
}
base.OnMouseWheel(e);
}
Conclusion
You may choose the method which you prefer. I would suggest to go for attached property approach as that make this behavior plug and play. E.g. You can turn on or off the behavior via a check box. You can store the user preference in settings and apply to the property.
Attached properties allows you to extend the behavior, may you need another behavior after this.

Focus on textBox on the page load with MVVM

I can't set keyboard focus on textbox control when my UI form loads. I am using MVVM pattern and when I tried solution on the following link but this didn't help. When the form loads there is a caret in my textbox but it's not flashing, and I can't write text in it. The same thing happens when I use FocusManager.focused element in XAML. I also tried with Keyboard.Focus(MytextBox) but the same thing happened... Please help anybody, I am stuck 2 days with this...
This is class where i made dependency property IsFocused and used it for binding with isFocused property in my viewmodel:
public static class Attached
{
public static DependencyProperty IsFocusedProperty = DependencyProperty.RegisterAttached("IsFocused", typeof(bool), typeof(Attached), new UIPropertyMetadata(false, OnIsFocusedChanged));
public static bool GetIsFocused(DependencyObject dependencyObject)
{
return (bool)dependencyObject.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject dependencyObject, bool value)
{
dependencyObject.SetValue(IsFocusedProperty, value);
}
public static void OnIsFocusedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
TextBox textBox = dependencyObject as TextBox;
bool newValue = (bool)dependencyPropertyChangedEventArgs.NewValue;
bool oldValue = (bool)dependencyPropertyChangedEventArgs.OldValue;
Keyboard.Focus(textBox);
if (newValue && !oldValue && !textBox.IsFocused) textBox.Focus();
}
}
This is my XAML:
<TextBox a:Attached.IsFocused="{Binding IsFocused, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}.../>"
For this u can use your code behind file. In the page loaded event you should do
MyTextBox.Focus();
In the Window-Element you can write
FocusManager.FocusedElement="{Binding ElementName=textBox}"
You maybe get a problem with the loaded-timing, meaning you set focus but it gets stolen afterwards. You can try to defer that by using a dispatcher:
myView.Dispatcher.BeginInvoke(new Action(() =>
{
textBox.Focus();
}), DispatcherPriority.ContextIdle);

Get the value of an attached property rather than set it

I have some code to set the focused property of a text box, but what i'm actually after is finding out if the text box currently has the keyboard focus, I need to determine this from my view model
public static class FocusExtension
{
public static bool GetIsFocused(DependencyObject obj)
{
return (bool)obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value)
{
obj.SetValue(IsFocusedProperty, value);
}
public static readonly DependencyProperty IsFocusedProperty = DependencyProperty.RegisterAttached
(
"IsFocused",
typeof(bool),
typeof(FocusExtension),
new UIPropertyMetadata(false, OnIsFocusedPropertyChanged)
);
public static void OnIsFocusedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uie = (UIElement)d;
if ((bool)e.NewValue)
{
uie.Focus();
}
}
}
And the xaml is
<TextBox Text="{Binding Path=ClientCode}" c:FocusExtension.IsFocused="{Binding IsClientCodeFocused}" />
source of code
have you seen the FocusManager? you can get/set focus using this object.
Edit
Based on the comments below, here's an example of an attached property that hooks up an event and updates the source of a binding. I'll add comments where I know you'll need to make modifications. Hopefully it will point you in the right direction
public class TextBoxHelper
{
// I excluded the generic stuff, but the property is called
// EnterUpdatesSource and it makes a TextBox update it's source
// whenever the Enter key is pressed
// Property Changed Event - You have this in your class above
private static void EnterUpdatesTextSourcePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
UIElement sender = obj as UIElement;
if (obj != null)
{
// In my case, the True/False value just determined a behavior,
// so toggling true/false added/removed an event.
// Since you want your events to be on at all times, you'll either
// want to have two AttachedProperties (one to tell the control
// that it should be tracking the current focused state, and
// another for binding the actual focused state), or you'll want
// to find a way to only add the EventHandler when the
// AttachedProperty is first added and not toggle it on/off as focus
// changes or add it repeatedly whenever this value is set to true
// You can use the GotFocus and LostFocus Events
if ((bool)e.NewValue == true)
{
sender.PreviewKeyDown += new KeyEventHandler(OnPreviewKeyDownUpdateSourceIfEnter);
}
else
{
sender.PreviewKeyDown -= OnPreviewKeyDownUpdateSourceIfEnter;
}
}
}
// This is the EventHandler
static void OnPreviewKeyDownUpdateSourceIfEnter(object sender, KeyEventArgs e)
{
// You won't need this
if (e.Key == Key.Enter)
{
// or this
if (GetEnterUpdatesTextSource((DependencyObject)sender))
{
// But you'll want to take this bit and modify it so it actually
// provides a value to the Source based on UIElement.IsFocused
UIElement obj = sender as UIElement;
// If you go with two AttachedProperties, this binding should
// point to the property that contains the IsFocused value
BindingExpression textBinding = BindingOperations.GetBindingExpression(
obj, TextBox.TextProperty);
// I know you can specify a value for a binding source, but
// I can't remember the exact syntax for it right now
if (textBinding != null)
textBinding.UpdateSource();
}
}
}
There might be a better way of accomplishing what you're trying to do, but if not then I hope this provides a good starting point :)
in your OnIsFocusedPropertyChanged handler, you need to get a reference to the control that it is being set on and subscribe to its FocusChanged event, where you can re-set the dependency pproperty. Make sure in your XAML you set the binding mode to TwoWay

Categories