How to attach PreviewKeyDown event to Dependencyobject on instantiation? - c#

I want to attach PreviewKeyDown event to my dependencyobject on instatiation.
Code:
public class PriceFieldExtension : DependencyObject
{
public static decimal GetPriceInputField(DependencyObject obj)
{
return (decimal)obj.GetValue(PriceInputFieldProperty);
}
public static void SetPriceInputField(DependencyObject obj, decimal value)
{
obj.SetValue(PriceInputFieldProperty, value);
}
public static readonly DependencyProperty PriceInputFieldProperty =
DependencyProperty.RegisterAttached("PriceInputField", typeof (decimal), typeof (PriceFieldExtension), new FrameworkPropertyMetadata(0.00M, new PropertyChangedCallback(OnIsTextPropertyChanged)));
private static void OnIsTextPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
TextBox targetTextbox = d as TextBox;
if (targetTextbox != null)
{
targetTextbox.PreviewKeyDown += targetTextbox_PreviewKeyDown;
}
}
static void targetTextbox_PreviewKeyDown(object sender, KeyEventArgs e)
{
e.Handled = (e.Key == Key.Decimal);
}
}
Right now i have to change something in the textbox before event is bound to the dependency object, but how can i do it on instantiation?
Ground problem is that i want textbox only to accept decimals, but here is a catch:
When i type in the textbox TextChanged event fire like this:
0 fire
0, dont fire
0,0 fire
0,00 fire
0,,00 dont fire
Xaml:
<TextBox Text="{Binding InputPrice, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, StringFormat=F2}" Style="{StaticResource DefaultTextBox}" classes:PriceFieldExtension.PriceInputField="{Binding InputPrice, StringFormat=F2, Converter={StaticResource StringToDecimalConverter}}" TextAlignment="Right" Margin="0,6,0,0" Height="45">
</TextBox>
if i change InputPrice property to be string, TextChanged event will fire every time.
I want to avoid this inconsistency by catching "," key press. Maybe there are better solutions?

How about using a masked text box:
https://marlongrech.wordpress.com/2007/10/28/masked-textbox/

Related

Bind KeyBinding Command to WPF DatePicker (MVVM)

I've been searching high and low for a way to bind the Return key to a DatePicker control in a MVVM way, but to no avail.
My current XAML Markup:
<DatePicker x:Name="DateFilter" SelectedDate="{Binding SearchDate, UpdateSourceTrigger=PropertyChanged}">
<DatePicker.InputBindings>
<KeyBinding Key="Return" Command="{Binding SearchCommand}"></KeyBinding>
</DatePicker.InputBindings>
</DatePicker>
The closest thing I found is rewriting the entire control template, but this both requires a lot of markup and screws up the original control appearance.
Right now, I've hacked a solution together by forcefully adding the DatePicker InputBindings to the underlying DatePickerTextBox from the form codebehind, but it's awful as it requires writing code behind and uses reflection:
Form CodeBehind (bound to the Loaded event of the view):
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
var fiTextBox = typeof(DatePicker)
.GetField("_textBox", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
if (fiTextBox?.GetValue(DateFilter) is System.Windows.Controls.Primitives.DatePickerTextBox textBox)
{
textBox.InputBindings.AddRange(DateFilter.InputBindings);
}
}
Is there a better way to do this?
(Note: I cannot bind the execution of the command to the change of the bound SearchDate property as the operation is quite expensive and I don't want it to fire every time the user picks a new date. However, I need the property to immediately refresh as the CanExecute of the command is also tied to said Date not being null.)
You could use a reusable attached behaviour:
public static class ReturnKeyBehavior
{
public static ICommand GetCommand(UIElement UIElement) =>
(ICommand)UIElement.GetValue(CommandProperty);
public static void SetCommand(UIElement UIElement, ICommand value) =>
UIElement.SetValue(CommandProperty, value);
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(ReturnKeyBehavior),
new UIPropertyMetadata(null, OnCommandChanged));
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement uie = (UIElement)d;
ICommand oldCommand = e.OldValue as ICommand;
if (oldCommand != null)
uie.RemoveHandler(UIElement.PreviewKeyDownEvent, (KeyEventHandler)OnMouseLeftButtonDown);
ICommand newCommand = e.NewValue as ICommand;
if (newCommand != null)
uie.AddHandler(UIElement.PreviewKeyDownEvent, (KeyEventHandler)OnMouseLeftButtonDown, true);
}
private static void OnMouseLeftButtonDown(object sender, KeyEventArgs e)
{
if(e.Key == Key.Enter)
{
UIElement uie = (UIElement)sender;
ICommand command = GetCommand(uie);
if (command != null)
command.Execute(null);
}
}
}
...that can be attached to any UIElement in XAML:
<DatePicker local:ReturnKeyBehavior.Command="{Binding ListViewItemMouseLeftButtonDownCommand}" />
Then you don't have to deal with any keys in the view model.
I'd probably use an interaction trigger, or whatever else your framework uses to convert events to commands, and then trap PreviewKeyDown:
<DatePicker x:Name="DateFilter" SelectedDate="{Binding SearchDate, UpdateSourceTrigger=PropertyChanged}"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd ="http://www.galasoft.ch/mvvmlight">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewKeyDown">
<cmd:EventToCommand Command="{Binding KeyDownCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DatePicker>
And then in your view model:
private ICommand _KeyDownCommand;
public ICommand KeyDownCommand => this._KeyDownCommand ?? (this._KeyDownCommand = new RelayCommand<KeyEventArgs>(OnKeyDown));
private void OnKeyDown(KeyEventArgs args)
{
if (args.Key == Key.Return)
{
// do something
}
}

How to put a user control in a static layer ontop of all other controls?

I'm developing an autocomplete user control for WPF using XAML and C#.
I would like to have the pop-up for the suggestions to appear above all controls. Currently my pop up is a ListView . That causes problems since whenever I decide to show it the UI must find a place for it and to do so moves all the controls which are below it further down.
How can I avoid this? I assume I must put it in a layer which is above all of the other controls?
I have written "auto-complete" style controls before by using the WPF Popup control, combined with a textbox. If you use Popup it should appear, as you say, in a layer over the top of everything else. Just use Placement of Bottom to align it to the bottom of the textbox.
Here is an example that I wrote a while ago. Basically it is a text box which, as you type pops up a suggestions popup, and as you type more it refines the options down. You could fairly easily change it to support multi-word auto-complete style code editing situations if you wanted that:
XAML:
<Grid>
<TextBox x:Name="textBox"
Text="{Binding Text, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:IntelliSenseUserControl}}}"
KeyUp="textBox_KeyUp"/>
<Popup x:Name="popup"
Placement="Bottom"
PlacementTarget="{Binding ElementName=textBox}"
IsOpen="False"
Width="200"
Height="300">
<ListView x:Name="listView"
ItemsSource="{Binding FilteredItemsSource, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:IntelliSenseUserControl}}}"
SelectionChanged="ListView_Selected"/>
</Popup>
</Grid>
Code-behind:
public partial class IntelliSenseUserControl : UserControl, INotifyPropertyChanged
{
public IntelliSenseUserControl()
{
InitializeComponent();
DependencyPropertyDescriptor prop = DependencyPropertyDescriptor.FromProperty(ItemsSourceProperty, typeof(IntelliSenseUserControl));
prop.AddValueChanged(this, ItemsSourceChanged);
}
private void ItemsSourceChanged(object sender, EventArgs e)
{
FilteredItemsSource = new ListCollectionView((IList)ItemsSource);
FilteredItemsSource.Filter = (arg) => { return arg == null || string.IsNullOrEmpty(textBox.Text) || arg.ToString().Contains(textBox.Text.Trim()); };
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(IntelliSenseUserControl), new FrameworkPropertyMetadata(null) { BindsTwoWayByDefault = true });
public object ItemsSource
{
get { return (object)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(object), typeof(IntelliSenseUserControl), new PropertyMetadata(null));
#region Notified Property - FilteredItemsSource (ListCollectionView)
public ListCollectionView FilteredItemsSource
{
get { return filteredItemsSource; }
set { filteredItemsSource = value; RaisePropertyChanged("FilteredItemsSource"); }
}
private ListCollectionView filteredItemsSource;
#endregion
private void textBox_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return || e.Key == Key.Enter)
{
popup.IsOpen = false;
}
else
{
popup.IsOpen = true;
FilteredItemsSource.Refresh();
}
}
private void UserControl_LostFocus(object sender, RoutedEventArgs e)
{
popup.IsOpen = false;
}
private void ListView_Selected(object sender, RoutedEventArgs e)
{
if (listView.SelectedItem != null)
{
Text = listView.SelectedItem.ToString().Trim();
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
If your Window's content container is a Grid, you can simply do something like
<ListBox Grid.RowSpawn="99" Grid.ColumnSpan="99"/>
to "simulate" an absolute position. You then just have to set its position with Margin, HorizontalAlignment and VerticalAlignment so it lays around the desired control.

Focus on a textbox in autocompletebox?

this is my xaml:
<toolkit:AutoCompleteBox Name="signalNameEditor"
ItemsSource="{Binding MySource}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
IsTextCompletionEnabled="True"
FilterMode="StartsWith"
ValueMemberPath="Label"
MinimumPrefixLength="3"
MinimumPopulateDelay="800"
Style="{StaticResource autoCompleteBoxStyle}">
<toolkit:AutoCompleteBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Name="textBlock" Text="{Binding Label}"/>
</StackPanel>
</DataTemplate>
</toolkit:AutoCompleteBox.ItemTemplate>
</toolkit:AutoCompleteBox>
So, how could i get textblock element in my view? I tried this:
var textBlock = signalNameEditor.FindName("textBlock");
but it is wrong. So could you help me with this or redirect me to a proper solution. Thanks in advance.
Thanks for all aswers, that worked
var textBlock = ((StackPanel)signalNameEditor.ItemTemplate.LoadContent()).FindName("textBlock") as TextBlock;
but unfortunately I didn't get the result, that I expected. The question is how to get focus on textbox in autocompletebox, so that when focus is on autocompletebox I could write something there without double clicking.
I thought that I could do something inside my view
public void SetFocus
{
var textBlock = ((StackPanel)signalNameEditor
.ItemTemplate
.LoadContent())
.FindName("textBlock") as TextBlock;
textBlock.Focus();
}
I know that there are a lot of howto examples for setting focus like this one
autocompletebox focus in wpf
but I can't make it work for me. Is there a solution, that I could get without writing AutoCompleteFocusableBox class?
The solution was simplier. Actually i need to set focus on a textbox in a autocompletebox. For this purpose I used style defined as a regular style http://msdn.microsoft.com/ru-ru/library/dd728668(v=vs.95).aspx
After it in my view I could use the following:
public void SetFocus()
{
var textbox = this.editor.Template.FindName("Text", editor) as TextBox;
textbox.Focus();
}
You can Write extension and set custom property for textbox to make it focusable
For example you can write extension class as below
public static class FocusBehavior
{
#region Constants
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached("IsFocused", typeof (bool?),
typeof (FocusBehavior), new FrameworkPropertyMetadata(IsFocusedChanged));
#endregion
#region Public Methods
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 Event Handlers
private static void IsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uie = (UIElement) d;
if ((bool) e.NewValue)
uie.Dispatcher.BeginInvoke(DispatcherPriority.Input, new ThreadStart(() => Keyboard.Focus(uie)));
}
#endregion Event Handlers
}
Then in xaml as below:
<UserControl xmlns:behaviours="clr-namespace:Example.Views.Behaviours">
<TextBox TextWrapping="Wrap" Text="TextBox" behaviours:FocusBehavior.IsFocused={Binding IsFocused}/>
I hope that answeres your question

Need help with caliburn Message.Attach when TextBox gets focus

I have a TextBox that I am setting the focus on using an attached property bound to a property of the view model. The attached property calls "UIElement.Focus()" to set the focus. The problem is when the TextBox receives focus in this manner the "GotFocus" event doesn't fire. I am using Caliburn.Micro's Message.Attach to handle the event. Any ideas?
Here is the TextBox.
<TextBox x:Name="Test"
Grid.Column="0"
Text="{Binding Test, Converter={StaticResource TestToStringConverter}}"
AttachedProperties:FocusExtension.IsFocused="{Binding IsTestFocused}"
cal:Message.Attach="[Event GotFocus] = [Action OnGotFocus($eventargs)]; />
Here is the Attached Property (found on SO).
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));
private static void OnIsFocusedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uie = (UIElement)d;
if ((bool)e.NewValue)
{
uie.Focus();
}
}
}
I have tried this myself, and am able to replicate the issue. I'm not quite sure why this happens, it may have something to do with the user control's (i.e. the views) lifecycle. One option could be to extend your attached property so that it invokes a verb on your view model at the point at which it calls uie.Focus().
The name of the verb could be a dependency property on your FocusExtension attached property, and could be set in the view.

WPF Expander Validation

Does anyone know of a way to change the style of an expander if a IDataError validation occurs in a control held within the expander. E.g.
<Expander Header="Details">
<TextBox Text="{Binding Brand.DESCRIPTION,
UpdateSourceTrigger=LostFocus,
ValidatesOnDataErrors=True}"/>
</Expander>
So if the textbox has an error the style of my expander will change (go red maybe).
I'm looking to make this as generic as possible so without binding to each control within the expander manually if possible.
You could make use of the Attached Event Validation.Error (which is raised everytime a validation error is added or removed) through an Attached Behavior. To make this work you need to add NotifyOnValidationError=True to the bindings.
This Attached Behavior, ChildValidation, subscribes to the Validation.Error event for the Expander which is bubbled up if NotifyOnValidationError is set to True on the bindings. Since several Controls may be located within the Expander it also need to keep track of the count of Validation Errors that's currently active to determine if a Red Border should be displayed or not. It could look like this
Xaml
<Expander Header="Details"
behaviors:ChildValidationBehavior.ChildValidation="True">
<TextBox Text="{Binding Brand.DESCRIPTION,
UpdateSourceTrigger=LostFocus,
ValidatesOnDataErrors=True,
NotifyOnValidationError=True}"/>
</Expander>
ChildValidationBehavior
public static class ChildValidationBehavior
{
private static readonly DependencyProperty ErrorCountProperty =
DependencyProperty.RegisterAttached("ErrorCount",
typeof(int),
typeof(ChildValidationBehavior));
private static void SetErrorCount(DependencyObject element, int value)
{
element.SetValue(ErrorCountProperty, value);
}
private static int GetErrorCount(DependencyObject element)
{
return (int)element.GetValue(ErrorCountProperty);
}
public static readonly DependencyProperty ChildValidationProperty =
DependencyProperty.RegisterAttached("ChildValidation",
typeof(bool),
typeof(ChildValidationBehavior),
new UIPropertyMetadata(false, OnChildValidationPropertyChanged));
public static bool GetChildValidation(DependencyObject obj)
{
return (bool)obj.GetValue(ChildValidationProperty);
}
public static void SetChildValidation(DependencyObject obj, bool value)
{
obj.SetValue(ChildValidationProperty, value);
}
private static void OnChildValidationPropertyChanged(DependencyObject dpo,
DependencyPropertyChangedEventArgs e)
{
Control control = dpo as Control;
if (control != null)
{
if ((bool)e.NewValue == true)
{
SetErrorCount(control, 0);
Validation.AddErrorHandler(control, Validation_Error);
}
else
{
Validation.RemoveErrorHandler(control, Validation_Error);
}
}
}
private static void Validation_Error(object sender, ValidationErrorEventArgs e)
{
Control control = sender as Control;
if (e.Action == ValidationErrorEventAction.Added)
{
SetErrorCount(control, GetErrorCount(control)+1);
}
else
{
SetErrorCount(control, GetErrorCount(control)-1);
}
int errorCount = GetErrorCount(control);
if (errorCount > 0)
{
control.BorderBrush = Brushes.Red;
}
else
{
control.ClearValue(Control.BorderBrushProperty);
}
}
}

Categories