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
}
}
Related
I wanted to create a textbox that can search for files and also keeps track of previously used files. So I made a user control with a DependecyProperty that should give me the current text of the textbox and a button. But everytime I try to bind to the DependencyProperty, the property that binds to it remains empty. In short, the control looks like this:
<UserControl
<!-- ... -->
x:Name="PTB">
<AutoSuggestBox x:Name="SearchBox"
Text="{Binding ElementName=PTB, Path=FilePath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Command="{Binding PickFileCommand}" />
</UserControl
I have this simple ViewModel for the user control
public string FilePath
{
get => _filePath;
set => SetProperty(ref _filePath, value);
}
public async Task PickFile()
{
// ...
}
and this code-behind for the user control
public readonly static DependencyProperty FilePathProperty =
DependencyProperty.Register("FilePath", typeof(string), typeof(PathTextBox), new PropertyMetadata("", new PropertyChangedCallback(OnTextChanged)));
public string FilePath
{
get => (string)GetValue(FilePathProperty);
set => SetValue(FilePathProperty, value);
}
private static void OnTextChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
if (dependencyObject is PathTextBox ptb && e.NewValue is string s)
{
ptb.SearchBox.Text = s;
ptb.FilePath = s;
}
}
And when I try to use it like this in my MainPage.xaml:
<customcontrols:PathTextBox x:Name="SearchBox"
KeyUp="SearchBox_KeyUp"
FilePath="{Binding ScriptFilePath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
and MainPage.xaml.cs
private async void SearchBox_KeyUp(object sender, KeyRoutedEventArgs e)
{
if (e.Key == VirtualKey.Enter)
{
await ViewModel.OpenSqlFile(ViewModel.ScriptFilePath);
}
}
then ViewModel.ScriptFilePath remains empty, even though I did bind to it. I tried a couple of different things with x:Bind etc., but I couldn't find a way to cleanly implement it in MVVM. I'm using the CommunityToolkit.Mvvm library, if that helps. Any ideas?
From your code, I assume that you have the ViewModel in MainPage.xaml.cs. Then you need to add ViewModel to you binding code.
<customcontrols:PathTextBox
x:Name="SearchBox"
KeyUp="SearchBox_KeyUp"
FilePath="{Binding ViewModel.ScriptFilePath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
or even better, use x:Bind ViewModel.ScriptFilePath.
I am trying to test this at a simple level where I have the following TasksDatagridView.xaml:
<UserControl x:Class="Example.Views.TasksDatagridView" ...>
<UserControl.Resources>
<local:CompleteConverter x:Key="completeConverter" />
<local:Tasks x:Key="tasks" />
<CollectionViewSource x:Key="cvsTasks" Source="{Binding Path=tasks}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="ProjectName"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</UserControl.Resources>
<Grid>
<DataGrid x:Name="myDG" AutoGenerateColumns="True" ItemsSource="{Binding Source={StaticResource cvsTasks}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="AutoGeneratingColumn">
<i:InvokeCommandAction Command="{Binding GenColumns}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
</Grid>
</UserControl>
In my TasksDatagridView.xaml.cs I tried both setting the datacontext first this.DataContext = new ViewModels.TaskDgVm() and then InitializeComponent() and vice versa.
In my main window MainWindow.xaml I reference the control like such:
<Window x:Name="MainWindow" x:Class="Example.Views.MyMainWindowView" ...>
<Grid>
<local:TasksDatagridView x:Name="tview" />
</Grid>
</Window>
This is a derived example that shows the point so please excuse mispelling. So I am having two issues:
In the MainWindow.xaml line where i reference the control: <local:TasksDatagridView x:Name="tview" /> it says it threw a system.exception, yet the code still compiles and runs fine.
AutoGeneratingColumn is not being fired.
Really I am trying to figure out #2 and why this specific event is not firing. Right now I have a simple print in the execute method and when replacing the event name with a simple click or loaded event for the datagrid the command works fine and just about any other event gets fired, which tells me its not something in my viewmodel or delegate command class. Any thoughts on why the autogenerate column event is not working with command? Note I have made sure the event name is not misspelled.
Edit:
After posting question I found a related question here: MVVM - WPF DataGrid - AutoGeneratingColumn Event
However they use mvvm-light toolkit where I am using the expression blend interactivity library. Although the same answer may apply to both questions, they are indeed two separate toolkits.
So based on this thread MVVM - WPF DataGrid - AutoGeneratingColumn Event I believe the visual tree is not getting constructed during some of these events.
But there is an alternative provided that solves the problem while avoiding code behind:
public class AutoGeneratingColumnEventToCommandBehaviour
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(AutoGeneratingColumnEventToCommandBehaviour),
new PropertyMetadata(
null,
CommandPropertyChanged));
public static void SetCommand(DependencyObject o, ICommand value)
{
o.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject o)
{
return o.GetValue(CommandProperty) as ICommand;
}
private static void CommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var dataGrid = d as DataGrid;
if (dataGrid != null)
{
if (e.OldValue != null)
{
dataGrid.AutoGeneratingColumn -= OnAutoGeneratingColumn;
}
if (e.NewValue != null)
{
dataGrid.AutoGeneratingColumn += OnAutoGeneratingColumn;
}
}
}
private static void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var dependencyObject = sender as DependencyObject;
if (dependencyObject != null)
{
var command = dependencyObject.GetValue(CommandProperty) as ICommand;
if (command != null && command.CanExecute(e))
{
command.Execute(e);
}
}
}
}
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/
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.
I am a bit of a novice when it comes to MVVM and C# in general, but I do not understand why I am getting the following xaml parse exception: AG_E_PARSER_BAD_TYPE
The exception occurs when attempting to parse my event trigger:
<applicationspace:AnViewBase
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:c="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP7">
...and inside my grid:
<Button Name="LoginButton"
Content="Login"
Height="72"
HorizontalAlignment="Left"
Margin="150,229,0,0"
VerticalAlignment="Top"
Width="160">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<c:EventToCommand Command="{Binding LoginCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
The exception occurs at the i:EventTrigger EventName="Click" line.
Does anyone have any insight as to why this is happening? I have seen this used before, and am simply too inexperienced to discern why it isn't working for me.
I am obliged for any help, and thank you for your time.
I did not resolve this issue, but created a work around... I thought it might be helpful to some, so here it is:
I extended the button class by adding a command property to my new "BindableButton"
public class BindableButton : Button
{
public BindableButton()
{
Click += (sender, e) =>
{
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
};
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(BindableButton), new PropertyMetadata(null, CommandChanged));
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(BindableButton), new PropertyMetadata(null));
private static void CommandChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
BindableButton button = source as BindableButton;
button.RegisterCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
}
private void RegisterCommand(ICommand oldCommand, ICommand newCommand)
{
if (oldCommand != null)
oldCommand.CanExecuteChanged -= HandleCanExecuteChanged;
if (newCommand != null)
newCommand.CanExecuteChanged += HandleCanExecuteChanged;
HandleCanExecuteChanged(newCommand, EventArgs.Empty);
}
// Disable button if the command cannot execute
private void HandleCanExecuteChanged(object sender, EventArgs e)
{
if (Command != null)
IsEnabled = Command.CanExecute(CommandParameter);
}
}
Following this, I just bind a command in my xaml:
<b:BindableButton x:Name="LoginButton" Command="{Binding LoginCommand}"></b:BindableButton>