Modify WPF dependency property when pressing key - wpf

I have a WPF UserControl with a certain dependency property DepProp.
I would like this property to be modified when I press Shift or Alt, and to return to the previous value when releasing the keys.
What I want is similar to a trigger, but I don't know if it's possible to set the condition to be something like "Shift key is pressed".
I know that it's possible to specify KeyBindings for the control, as far as I understood they can execute a command when a key is pressed, but don't restore the previous vlaue when the key is released.
Any idea on how to do this?

You could create an attached behavior that you can affix to some "scope" element (e.g., your UserControl) that will maintain an attached read-only property that gets inherited down the tree. Then you can simply add a Trigger on the attached property.
public sealed class AltShiftHotKeyBehavior : Behavior<FrameworkElement>
{
private const ModifierKeys AltShift = ModifierKeys.Alt | ModifierKeys.Shift;
private static readonly DependencyPropertyKey IsAltShiftPressedPropertyKey =
DependencyProperty.RegisterAttachedReadOnly(
"IsAltShiftPressed",
typeof(bool),
typeof(AltShiftHotKeyBehavior),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.Inherits));
public static readonly DependencyProperty IsAltShiftPressedProperty =
IsAltShiftPressedPropertyKey.DependencyProperty;
public static bool GetIsAltShiftPressed(DependencyObject element)
{
return (bool)element.GetValue(IsAltShiftPressedProperty);
}
protected override void OnAttached()
{
base.OnAttached();
var element = this.AssociatedObject;
element.AddHandler(
FrameworkElement.LoadedEvent,
(RoutedEventHandler)OnLoaded,
handledEventsToo: true);
element.AddHandler(
FrameworkElement.UnloadedEvent,
(RoutedEventHandler)OnUnloaded,
handledEventsToo: true);
element.AddHandler(
UIElement.PreviewKeyDownEvent,
(KeyEventHandler)OnKey,
handledEventsToo: true);
element.AddHandler(
UIElement.PreviewKeyUpEvent,
(KeyEventHandler)OnKey,
handledEventsToo: true);
element.AddHandler(
UIElement.LostKeyboardFocusEvent,
(KeyboardFocusChangedEventHandler)OnLostKeyboardFocus,
handledEventsToo: true);
var window = element as Window;
if (window != null)
{
window.Activated += OnWindowActivated;
window.Deactivated += OnWindowDeactivated;
}
CheckToggledState();
}
protected override void OnDetaching()
{
ClearToggledState();
base.OnDetaching();
var element = this.AssociatedObject;
element.RemoveHandler(
FrameworkElement.LoadedEvent,
(RoutedEventHandler)OnLoaded);
element.RemoveHandler(
FrameworkElement.UnloadedEvent,
(RoutedEventHandler)OnUnloaded);
element.RemoveHandler(
UIElement.PreviewKeyDownEvent,
(KeyEventHandler)OnKey);
element.RemoveHandler(
UIElement.PreviewKeyUpEvent,
(KeyEventHandler)OnKey);
element.RemoveHandler(
UIElement.LostKeyboardFocusEvent,
(KeyboardFocusChangedEventHandler)OnLostKeyboardFocus);
var window = element as Window;
if (window != null)
{
window.Activated -= OnWindowActivated;
window.Deactivated -= OnWindowDeactivated;
}
}
private void CheckToggledState()
{
var element = this.AssociatedObject;
if (element.IsLoaded &&
element.IsKeyboardFocusWithin &&
Keyboard.PrimaryDevice.Modifiers == AltShift)
{
element.SetValue(IsAltShiftPressedPropertyKey, true);
}
else
{
element.ClearValue(IsAltShiftPressedPropertyKey);
}
}
private void ClearToggledState()
{
this.AssociatedObject.ClearValue(IsAltShiftPressedPropertyKey);
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
CheckToggledState();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
ClearToggledState();
}
private void OnWindowActivated(object sender, EventArgs e)
{
CheckToggledState();
}
private void OnWindowDeactivated(object sender, EventArgs e)
{
ClearToggledState();
}
private void OnLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
CheckToggledState();
}
private void OnKey(object sender, KeyEventArgs e)
{
CheckToggledState();
}
}

Related

WPF Dispatchertimer tick increments,

I am making a countdown clock using dispatchertimer and timespan in WPF.
I have a button to start the countdown and a button to stop the countdown. When I hit the start button again it has doubled the interval from one second to two seconds. I display the counting in a textbox. What is wrong?
The code is simple:
public partial class MainWindow : Window
{
private DispatcherTimer DPtimerA;
private TimeSpan timeA;
public MainWindow()
{
InitializeComponent();
DPtimerA = new DispatcherTimer();
DPtimerA.Interval = new TimeSpan(0, 0, 1);
timeA = TimeSpan.FromSeconds(21);
}
private void DPtimerA_Tick(object sender, EventArgs e)
{
timeA = timeA.Add(TimeSpan.FromSeconds(-1));
txtClockA.Text = timeA.ToString("c");
if (timeA == TimeSpan.Zero) DPtimerA.Stop();
}
private void btnStartA_Click(object sender, RoutedEventArgs e)
{
DPtimerA.Tick += DPtimerA_Tick;
DPtimerA.Start();
}
private void btnStopA_Click(object sender, RoutedEventArgs e)
{
DPtimerA.Stop();
}
}
}
Change it like this:
private void btnStartA_Click(object sender, RoutedEventArgs e)
{
DPtimerA.Tick -= DPtimerA_Tick;
DPtimerA.Tick += DPtimerA_Tick;
DPtimerA.Start();
}
Make DispatcherTimer a readonly field that you initialize and hook up an event handler to once:
public partial class MainWindow : Window
{
private readonly DispatcherTimer DPtimerA =
new DispatcherTimer() { Interval = new TimeSpan(0, 0, 1) };
private TimeSpan timeA = TimeSpan.FromSeconds(21);
public MainWindow()
{
InitializeComponent();
DPtimerA.Tick += DPtimerA_Tick;
}
private void DPtimerA_Tick(object sender, EventArgs e)
{
timeA = timeA.Add(TimeSpan.FromSeconds(-1));
txtClockA.Text = timeA.ToString("c");
if (timeA == TimeSpan.Zero)
DPtimerA.Stop();
}
private void btnStartA_Click(object sender, RoutedEventArgs e)
{
DPtimerA.Start();
}
private void btnStopA_Click(object sender, RoutedEventArgs e)
{
DPtimerA.Stop();
}
}

Programatically change state of GridViewCheckBoxColumn row with updating binding source for Telerik's GridView

I'm making a custom behavior for Telerik's RadGridView.
When this behavior is attached and its PropertyName is set to same property as specified by
DataMemberBinding value of some of the GridViewCheckBoxColumn of the grid, then toggling the checkbox in that column will apply same checkbox state to all selected rows (but only to the same column).
That happens in the ApplyToAllSelected method, namely in gvcb.SetCurrentValue(GridViewCheckBox.IsCheckedProperty, isChecked); line. The visuals are working as expected, and all checkbox values are updated on screen.
The Problem is that the binding source is not updated for those rows. Only for the one where click happened. GridViewCheckBox.IsChecked dependency property does not seem to be bound directly to the datacontext's property, so gvcb.GetBindingExpression(GridViewCheckBox.IsChecked) returns null.
The Question: how to update source after setting checkbox state?
public sealed class CheckAllSelectedBehavior : Behavior<RadGridView>
{
public event EventHandler Toggled;
public string PropertyName { get; set; }
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.PreparingCellForEdit += this.AssociatedObject_PreparedCellForEdit;
this.AssociatedObject.CellEditEnded += this.AssociatedObject_CellEditEnded;
}
protected override void OnDetaching()
{
this.AssociatedObject.PreparingCellForEdit -= this.AssociatedObject_PreparedCellForEdit;
this.AssociatedObject.CellEditEnded -= this.AssociatedObject_CellEditEnded;
base.OnDetaching();
}
private void AssociatedObject_CellEditEnded(object sender, GridViewCellEditEndedEventArgs e)
{
if (e.Cell.Column.UniqueName == this.PropertyName && e.EditingElement is CheckBox cb)
{
cb.Checked -= this.Cb_Checked;
cb.Unchecked -= this.Cb_Unchecked;
}
}
private void AssociatedObject_PreparedCellForEdit(object sender, GridViewPreparingCellForEditEventArgs e)
{
if (e.Column.UniqueName == this.PropertyName && e.EditingElement is CheckBox cb)
{
cb.Checked += this.Cb_Checked;
cb.Unchecked += this.Cb_Unchecked;
}
}
private void Cb_Unchecked(object sender, System.Windows.RoutedEventArgs e)
{
this.ApplyToAllSelected(false);
}
private void Cb_Checked(object sender, System.Windows.RoutedEventArgs e)
{
this.ApplyToAllSelected(true);
}
private void ApplyToAllSelected(bool isChecked)
{
foreach (var item in this.AssociatedObject.SelectedItems)
{
var row = this.AssociatedObject.GetRowForItem(item);
var cell = row.GetCellFromPropertyName(this.PropertyName);
if (cell.Content is GridViewCheckBox gvcb)
{
gvcb.SetCurrentValue(GridViewCheckBox.IsCheckedProperty, isChecked);
}
}
this.Toggled?.Invoke(this, EventArgs.Empty);
}
}
Using reflection to set the value on viewmodel property seems to work. Modify the ApplyToAllSelected method like follows:
private void ApplyToAllSelected(bool isChecked)
{
foreach (var item in this.AssociatedObject.SelectedItems)
{
this.SetProperty(item, isChecked);
}
this.Toggled?.Invoke(this, EventArgs.Empty);
}
private void SetProperty(object target, bool isChecked)
{
var prop = target.GetType().GetProperty(
this.PropertyName,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty);
prop.SetValue(target, isChecked);
}

TextBox AttachedProperty to Select All text not working as expected?

I have an attached property called "SelectAllOnFocus". Values of true/false.
public static class TextBoxProps
{
private static void MyTextBoxKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
((TextBox)sender).Text = string.Empty;
}
}
public static void SetSelectAllOnFocus(DependencyObject dependencyObject, bool selectAllOnFocus)
{
if (!ReferenceEquals(null, dependencyObject))
{
dependencyObject.SetValue(SelectAllOnFocus, selectAllOnFocus);
}
}
public static bool GetSelectAllOnFocus(DependencyObject dependencyObject)
{
if (!ReferenceEquals(null, dependencyObject))
{
return (bool)dependencyObject.GetValue(SelectAllOnFocus);
}
else
{
return false;
}
}
private static void OnSelectAllOnFocus(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
bool selectAllOnFocus = (bool)e.NewValue == true;
var theTextBox = d as TextBox;
if (selectAllOnFocus && theTextBox != null)
{
theTextBox.PreviewMouseDown -= MyTextBoxMouseEnter; theTextBox.PreviewMouseDown += MyTextBoxMouseEnter;
}
}
private static void MyTextBoxMouseEnter(object sender, MouseEventArgs e)
{
((TextBox)sender).SelectAll();
e.Handled = false;
}
public static readonly DependencyProperty SelectAllOnFocus
= DependencyProperty.RegisterAttached("SelectAllOnFocus", typeof(bool), typeof(TextBoxEscapeProperty),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnSelectAllOnFocus)));
}
What happens is the following:
The PreviewMouseDown event gets triggered.
The MyTextBoxMouseEnter method gets called.
The SelectAll() Method gets called.
When I do a "watch" on ((TextBox)sender).SelectedText, the value is correct (meaning whatever is in the textbox is showing up as selectedText).
The textbox itself is unchanged. No text is selected.
This is part of a general WPF style. All textboxes in the application should receive this property and it's associated behavior.
I'm stumped. Any ideas?
Thanks
What happens if you call ((TextBox)sender).UpdateLayout(); immediately after the SelectAll command? Or maybe you need to set the Keyboard focus to the text box.
It might be a better option to use something like this, which works if the text box is being selected with the mouse or the keyboard. (You'll need to modify it to check your "SelectAllOnFocus" property)
In your App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
// Select the text in a TextBox when it receives focus.
EventManager.RegisterClassHandler(typeof(TextBox), TextBox.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(SelectivelyIgnoreMouseButton));
EventManager.RegisterClassHandler(typeof(TextBox), TextBox.GotKeyboardFocusEvent, new RoutedEventHandler(SelectAllText));
EventManager.RegisterClassHandler(typeof(TextBox), TextBox.MouseDoubleClickEvent, new RoutedEventHandler(SelectAllText));
base.OnStartup(e);
}
void SelectivelyIgnoreMouseButton(object sender, MouseButtonEventArgs e)
{
// Find the TextBox
DependencyObject parent = e.OriginalSource as UIElement;
while (parent != null && !(parent is TextBox))
parent = VisualTreeHelper.GetParent(parent);
if (parent != null)
{
var textBox = (TextBox)parent;
if (!textBox.IsKeyboardFocusWithin)
{
// If the text box is not yet focused, give it the focus and
// stop further processing of this click event.
textBox.Focus();
e.Handled = true;
}
}
}
void SelectAllText(object sender, RoutedEventArgs e)
{
var textBox = e.OriginalSource as TextBox;
if (textBox != null)
textBox.SelectAll();
}

WPF MVP command binding

Maybe someone can describe me how to properly organize commands binding in mvp design pattern? Now i have this solution:
I have commands class:
public class Commands
{
private static readonly RoutedUICommand OpenTaxGroupListCommand = new RoutedUICommand("OpenTaxGroupList","OpenTaxGroupList",typeof(Commands));
public static RoutedUICommand OpenTaxGroupList
{
get { return OpenTaxGroupListCommand; }
}
}
and in my application presenter:
public ApplicationPresenter(IShell view)
: base(view)
{
var openTaxGroupListBinding = new CommandBinding(Commands.OpenTaxGroupList, OpenTaxGroupList,
CanOpenTaxGroupList);
CommandManager.RegisterClassCommandBinding(typeof(Window), openTaxGroupListBinding);
}
private void CanOpenTaxGroupList(object sender, CanExecuteRoutedEventArgs e)
{
if (_taxGroupListPresenter != null)
{
if (View.TabExists(_taxGroupListPresenter))
e.CanExecute = false;
else e.CanExecute = true;
}
else e.CanExecute = true;
}
private void OpenTaxGroupList(object sender, ExecutedRoutedEventArgs e)
{
DisplayTaxGroups(e.Parameter as ITaxGroupListView);
}
Question: how can i separate command binding logic from ApplicationPresenter class?
UPDATE:
I found this solution:
public interface ICommandProvider
{
IEnumerable<RoutedUICommand> GetCommands();
}
In ApplicationPresenter:
public IEnumerable<RoutedUICommand> GetCommands()
{
return new Collection<RoutedUICommand>
{
new OpenTaxGroupListCommand(this, View,_taxGroupListPresenter)
};
}
and my RoutedUICommand:
public OpenTaxGroupListCommand(ApplicationPresenter presenter, IShell view, TaxGroupListPresenter taxGroupListPresenter)
{
_presenter = presenter;
_view = view;
_taxGroupListPresenter = taxGroupListPresenter;
var openTaxGroupListBinding = new CommandBinding(Commands.OpenTaxGroupList,Execute,CanExecute);
CommandManager.RegisterClassCommandBinding(typeof(Window), openTaxGroupListBinding);
}
private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void Execute(object sender, ExecutedRoutedEventArgs e)
{
_presenter.DisplayTaxGroups(e.Parameter as ITaxGroupListView, out _taxGroupListPresenter);
}
and View's xaml:
<Resources:MenuLinkButton x:Name="btnOpenTaxGroupList" Content="Nodokļa grupas" HorizontalAlignment="Left"
Style="{StaticResource menuLinkButton}" ImageSource="{StaticResource taxes16Image}" Command="WtpPresenters:Commands.OpenTaxGroupList"
CommandParameter="{StaticResource taxGroupListView}"/>
Is this normal solution?

DependencyObject.AssociatedObject is always null

I am trying to write a customer behavior to set some column widths to 0 if my "Visibility" (which is just a bool in this case) property is false... My problem is that when my on changed event fires it my AssociatedObject is always null.
Here is the relevant sample code, mybe someone can see where I am going wrong.
public static readonly DependencyProperty VisibilityProperty =
DependencyProperty.Register("Visibility", typeof(bool), typeof(HideRadGridViewColumnBehavior),
new PropertyMetadata(OnVisibilityPropertyChanged));
private static void OnVisibilityPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
{
if (((HideRadGridViewColumnBehavior)target).AssociatedObject == null)
MessageBox.Show("AssociatedObject is null");
}
Thanks for any help...
how are you attaching the behavior? and can you show some code of the behavior?
the AssociatedObject is set either after the call to Attach or through listing the behavior within <i:Interaction.Behaviors></i:Interaction.Behaviors>
Thanks Markus Hütter for idea. This is how I have implemented in PasswordBox Behaviour (see comments in code below):
<PasswordBox>
<i:Interaction.Behaviors>
<behaviours:PasswordBehavior Password="{Binding Password, Mode=TwoWay}" />
</i:Interaction.Behaviors>
</PasswordBox>
Behaviour:
public class PasswordBehavior : Behavior<PasswordBox>
{
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.Register("Password", typeof(string), typeof(PasswordBehavior), new PropertyMetadata(default(string)));
private object _value;
private bool _skipUpdate;
public string Password
{
get { return (string)GetValue(PasswordProperty); }
set { SetValue(PasswordProperty, value); }
}
protected override void OnAttached()
{
// in my case on OnAttached() called after OnPropertyChanged
base.OnAttached();
AssociatedObject.PasswordChanged += PasswordBox_PasswordChanged;
// using _value saved before in OnPropertyChanged
if (_value != null)
{
AssociatedObject.Password = _value as string;
}
}
protected override void OnDetaching()
{
AssociatedObject.PasswordChanged -= PasswordBox_PasswordChanged;
base.OnDetaching();
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
// in my case this called before OnAttached(), so that's why AssociatedObject is null on first call
base.OnPropertyChanged(e);
if (AssociatedObject == null)
{
// so, let'save the value and then reuse it when OnAttached() called
_value = e.NewValue as string;
return;
}
if (e.Property == PasswordProperty)
{
if (!_skipUpdate)
{
_skipUpdate = true;
AssociatedObject.Password = e.NewValue as string;
_skipUpdate = false;
}
}
}
private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
_skipUpdate = true;
Password = AssociatedObject.Password;
_skipUpdate = false;
}
}

Resources