How to override keybinding in a control in wpf? - wpf

I have the following code which uses Microsoft's WPFToolkit AutoCompleteBox. I have tried adding an input binding inside it
xmlns:tk="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit"
<tk:AutoCompleteBox IsTextCompletionEnabled="True" FilterMode="Contains" ItemsSource="{Binding DistinctItemNames, Mode=OneWay}"
SelectedItem="{Binding SelectedItemName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
x:Name="searchBox" Width="300" Height="23" VerticalContentAlignment="Center" >
<tk:AutoCompleteBox.InputBindings>
<KeyBinding Key="Return" Command="{Binding ShowSelectedItemsCommand}"/>
<tk:AutoCompleteBox.InputBindings>
</tk:AutoCompleteBox>
However, it doesn't work. I expect that the control itself handles the 'Enter' or 'Return' key so how can I override its default function?
I have also put that keybinding directly under the 'UserControl.InputBindings' and it also did not work. I hate to use Code Behind to handle the command logic.

You could try to handle the PreviewKeyDown event, either directly in the code-behind of the view:
private void AutoCompleteBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
AutoCompleteBox box = sender as AutoCompleteBox;
dynamic viewModel = box.DataContext;
viewModel.ShowSelectedItemsCommand.Execute(null);
}
}
...or by wrapping it in an attached behaviour: https://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF.
Neither approach breaks the MVVM pattern. In the first case you are just invoking the exact same view model command from the exact same view. But if you really "hate to use code-behind" for some strange reason, then create an attached behaviour.

Try having a look at this post: ReactiveCommand pass Command Parameter.
It uses Reactivity to accomplish the same thing you want.
Then, you can process the key received via KeyCode, and check if it is the key you want.

Related

WPF Button Command, why logic code in ViewModel?

I have read some thread about how to work on WPF ListView command binding.
Passing a parameter using RelayCommand defined in the ViewModel
Binding Button click to a method
Button Command in WPF MVVM Model
How to bind buttons in ListView DataTemplate to Commands in ViewModel?
All of them suggest write the logic code inside ViewModel class, for example:
public RelayCommand ACommandWithAParameter
{
get
{
if (_aCommandWithAParameter == null)
{
_aCommandWithAParameter = new RelayCommand(
param => this.CommandWithAParameter("Apple")
);
}
return _aCommandWithAParameter;
}
}
public void CommandWithAParameter(String aParameter)
{
String theParameter = aParameter;
}
It is good practice or anyway so I can move the CommandWithAParameter() out of the ViewModel?
In principle, MVVM application should be able to run to its full potential without creating the views. That's impossible, if some parts of your logic are in View classes.
On top of that, ICommand has CanExecute, which will autamagically disable buttons, menu items etc. if the command should not be run.
I understand why with basic RelayCommand implementation it can be hard to see the benefits, but take a look at ReactiveCommand samples.
ReactiveCommand handles async work very well, even disabling the button for the time work is done and enabling it afterwards.
Short example: you have a login form. You want to disable the login button if the username and password are empty.
Using commands, you just set CanExecute to false and it's done.
Using events, you have manualy disable/enable the button, remember that it has to be done in Dispatcher thread and so on - it gets very messy if you have 5 buttons depending on different properties.
As for ListView, commands are also usefull - you can bind current item as command parameter:
<ListView ItemsSource="{Binding MyObjects}">
<ListView.ItemTemplate>
<DataTemplate>
<DockPanel>
<!-- change the context to parent ViewModel and pass current element to the command -->
<Button DockPanel.Dock="Right" Command="{Binding ElementName=Root, Path=ViewModel.Delete}" CommandParameter="{Binding}">Delete</Button>
<TextBlock Text="{Binding Name}"/>
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

AutoCompleteBox with TextChanged event not selecting properly

Hi I'm using an AutoCompleteBox like this
<!-- XAML Code -->
<sdk:AutoCompleteBox Grid.Row="2"
FilterMode="None"
ItemsSource="{Binding Customers}"
SelectedItem="{Binding Path=SelectedCustomer, Mode=TwoWay}"
Text="{Binding CustomerSearchString, Mode=TwoWay}"
ValueMemberBinding="{Binding Path=FullName}"
ValueMemberPath="FullName"
TextChanged="{ext:Invoke MethodName=Search, Source={Binding}}"/>
C# part:
// Search Method in the viewmodel
public void Search()
{
var customerOperation = _context.Load(_context.GetCustomerByNameQuery(CustomerSearchString));
customerOperation.Completed += (s, e) => Customers = new List<Customer>(customerOperation.Entities);
}
In my app to quick-search for customers for an fast and uncomplicated method to search. I get it to display everything correctly in the dropdown, and when I select with the mouse it works perfectly.
But when I press ArrowDown, you see the text come up for a split-second but then it reverts and puts the cursor back in the textbox instead of selecting the first entry down. I tried using the TextInput event, but that one won't fire.
How can I avoid this behaviour?
SOLUTION:
The problem was, that the TextChanged event got fired when the user selected an entry, creating some sort of race condition like behaviour where the Text got reset. The solution was to use the KeyUp event (don't use KeyDown, because the Text property won't be updated yet). This event doesn't get triggered when the user selects something, solving the problem.
Final code (ViewModel unchanged):
<!-- XAML Code -->
<sdk:AutoCompleteBox Grid.Row="2"
FilterMode="None"
ItemsSource="{Binding Customers}"
SelectedItem="{Binding Path=SelectedCustomer, Mode=TwoWay}"
Text="{Binding CustomerSearchString, Mode=TwoWay}"
ValueMemberBinding="{Binding Path=FullName}"
ValueMemberPath="FullName"
KeyUp="{ext:Invoke MethodName=Search, Source={Binding}}"/>
Thanks everyone!
Add a handler like this in code:
KeyEventHandler eventHandler = MyAutoCompleteBox_KeyDown;
MyAutoCompleteBox.AddHandler(KeyDownEvent, eventHandler, true);
I'm not understanding why you are using a TextChanged event...? What is that for? If you take that out, does it work? I use an autocomplete box in my project and I don't need a search method... all I do is just supply a list of objects to the autocompletebox and the it searches that list when the user types. I can select either by mouse or by up/down arrows. The only thing that I can think of is that each time you try to use the up/down arrow the text changes and fires off the search function and closes the selection option drop down...

XAML reference control and properties in x:Array

<RichTextBox x:Name="OrigText" Margin="0,0,8,0" d:LayoutOverrides="Width"/>
<Button x:Name="OrigFileBrowse" Command="{Binding BrowseCommand}" CommandParameter="{Binding ElementName=OrigText, Path=Document}" HorizontalAlignment="Center" Margin="0,0,8,2.442" Width="75" Content="Browse" Grid.Row="1" d:LayoutOverrides="Height"/>
<RichTextBox x:Name="ModifiedText" Grid.Column="1" Margin="8,0,0,0"/>
<Button x:Name="ModifiedFileBrowse" Command="{Binding BrowseCommand}" CommandParameter="{Binding ElementName=ModifiedText, Path=Document}" HorizontalAlignment="Center" Width="75" Content="Browse" Grid.Row="1" Grid.Column="1" Margin="0,0,0,2.442" d:LayoutOverrides="Height"/>
<Button x:Name="Compare" Command="{Binding CompareCommand}" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Content="Compare" Grid.Row="2" Grid.ColumnSpan="2">
<Button.CommandParameter>
<x:Array Type="RichTextBox">
<local:CompareTextView/>
</x:Array>
</Button.CommandParameter>
</Button>
Trying to get 2 items to be passed when the Compare button is clicked as it will then execute a compare command. Attempted to make use of MultiBinding however that is firing on instantiation and therefore the converter then fires accordingly. It does NOT fire when I click compare and the compare command is executed.
With that not working, I am attempting to now reference the controls within XAML to pass within an ArrayExtension. Not sure of the syntax or if it is even possible as I know you cannot bind within the ArrayExtension. The above fails since it can not construct a new CompareTextView view, which has no default constructor since I am making use of Prism...
Pretty frustrating, hopefully someone can help me out...
EDIT:
Want to clear some things up. The issue is not that I want CanExecute called again. The issue is that at instantiation of the controls, the converter is called and executed and the values are returned...but where they go I have no clue? The converter is never called again. If I could get the initial references to the FlowDocument this would all be a moot point...but it doesn't return things anywhere per se...since this is a command...if that makes sense...when making use of MultiBinding.
<Button x:Name="Compare" Command="{Binding CompareCommand}" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Content="Compare" Grid.Row="2" Grid.ColumnSpan="2">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource FlowDocumentConverter}">
<Binding ElementName="OrigText" Path="Document"/>
<Binding ElementName="ModifiedText" Path="Document"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
UPDATE:
Tried what refereejoe mentions here, scroll down a little bit to see his posting. While CanExecute continually fires, this does nothing to resolve the issue. In addition I switched the MultiBinding to be a single item, it is coming back null. Again when the converter fires on instantiation the FlowDocument references are there...
ANSWER:
Abe's mention that it was being cached led me to try something else. Since I knew that the FlowDocument references were available when the converter was called I knew they were there. Something was getting fouled up. The key piece appears to be in the converter itself. I was simply returning the object[]. Then when the command fired the arg was indeed an object[] but the two items were null. I created a class called Docs, which had two properties, one for each FlowDocument reference. When the converter fired I set the properties appropriately and then returned the Docs object. Now when I initiated the compare command, the Docs object was the args and it had the reference to the FlowDocuments just as I needed! Not sure if this is by design, but the fact that the items get lost when using the object[] doesn't make sense to me.
The proper way to do this is indeed with a MultiBinding on the CommandParameter. You won't see it call your CanExecute method unless WPF is informed that the method could return a different value than it had already cached (via the CanExecuteChanged event).
Since you are relying on the parameter passed in to determine this, we have to raise the event when the parameter changes. Since we can't really determine that in the command, we can use another technique: tell WPF to poll our command anytime it polls UICommands. This is done by implementing your ICommand like so:
public class MyCommand : ICommand
{
public void Execute(object parameter) { /* do stuff */ }
public bool CanExecute(object parameter { /* determine if we can do stuff */ }
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
Obviously, this prevents you from using the Prism DelegateCommand, but this will respond to changes in the command parameters.
UPDATE
Another thing to consider is that the Document property on the RichTextBox isn't actually changing. Instead, when you type into it, the content of the FlowDocument changes. Since the property instances don't change, the converter won't get fired again, and the originally converted value will get stored in the CommandParameter property.
One of the ways to force the converter to be called again is to add a Binding to the MultiBinding that is bound to a property that will change every time the text of the RichTextBox changes.
A somewhat hacky solution would be to use the IsKeyboardFocusWithin property, as that will mimic the default binding behavior of TextBox.Text (i.e. when the TextBox loses focus, the Binding updates):
<MultiBinding Converter="{StaticResource FlowDocumentConverter}">
<Binding ElementName="OrigText" Path="Document" />
<Binding ElementName="ModifiedText" Path="Document" />
<Binding ElementName="OrigText" Path="IsKeyboardFocusWithin" />
<Binding ElementName="ModifiedText" Path="IsKeyboardFocusWithin" />
</MultiBinding>
Obviously, in your converter, you will need to ignore these additional values, as they aren't relevant to your conversion.

Select the Initial Text in a Silverlight TextBox

I am trying to figure out the best way to select all the text in a TextBox the first time the control is loaded. I am using the MVVM pattern, so I am using two-way binding for the Text property of the TextBox to a string on my ViewModel. I am using this TextBox to "rename" something that already has a name, so I would like to select the old name when the control loads so it can easily be deleted and renamed. The initial text (old name) is populated by setting it in my ViewModel, and it is then reflected in the TextBox after the data binding completes.
What I would really like to do is something like this:
<TextBox x:Name="NameTextBox" Text="{Binding NameViewModelProperty, Mode=TwoWay}" SelectedText="{Binding NameViewModelProperty, Mode=OneTime}" />
Basically just use the entire text as the SelectedText with OneTime binding. However, that does not work since the SelectedText is not a DependencyProperty.
I am not completely against adding the selection code in the code-behind of my view, but my problem in that case is determining when the initial text binding has completed. The TextBox always starts empty, so it can not be done in the constructor. The TextChanged event only seems to fire when a user enters new text, not when the text is changed from the initial binding of the ViewModel.
Any ideas are greatly appreciated!
Dan,
I wrote a very simple derived class, TextBoxEx, that offers this functionality. The TextBoxEx class derives from TextBox, and can be referenced in XAML for any and all of your TextBox’s. There are no methods to call. It just listens for Focus events and selects it own text. Very simple.
Usage is as follows:
In XAML, reference the assembly where you implement the TextBoxEx class listed below, and add as many TextBoxEx elements as you need. The example below uses data binding to display a username.
<UserControl x:Class="MyApp.MainPage"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:ClassLibrary;assembly=ClassLibrary"
>
.
.
.
<c:TextBoxEx x:Name="NameTextBox" Text="{Binding NameViewModelProperty, Mode=TwoWay}" Width="120" />
This code below works with Silverlight 3.
using System.Windows;
using System.Windows.Controls;
namespace ClassLibrary
{
// This TextBox derived class selects all text when it receives focus
public class TextBoxEx : TextBox
{
public TextBoxEx()
{
base.GotFocus += OnGotFocus;
}
private void OnGotFocus(object sender, RoutedEventArgs e)
{
base.SelectAll();
}
}
}
Good luck.
I'm leaving Jim's solution as the answer, since calling SelectAll() on the GotFocus event of the TextBox did the trick.
I actually ended up making a Blend TriggerAction and an EventTrigger to do this instead of subclassing the TextBox or doing it in code-behind. It was really simple to do and nice to be able to keep the behavior logic encapsulated and just add it declaratively in XAML to an existing TextBox.
Just posting this in case anyone else comes across this thread and is interested:
XAML:
<TextBox x:Name="NameTextBox" Text="{Binding NameViewModelProperty, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<local:SelectAllAction/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
C#
public class SelectAllAction : TriggerAction<TextBox>
{
protected override void Invoke(object parameter)
{
if (this.AssociatedObject != null)
{
this.AssociatedObject.SelectAll();
}
}
}
Just wanna add a link I found pertaining to this - here is a fantastic discussion (read comments) on Behaviours vs subclassing vvs attached properties...

Enable button based on TextBox value (WPF)

This is MVVM application. There is a window and related view model class.
There is TextBox, Button and ListBox on form. Button is bound to DelegateCommand that has CanExecute function. Idea is that user enters some data in text box, presses button and data is appended to list box.
I would like to enable command (and button) when user enters correct data in TextBox. Things work like this now:
CanExecute() method contains code that checks if data in property bound to text box is correct.
Text box is bound to property in view model
UpdateSourceTrigger is set to PropertyChanged and property in view model is updated after each key user presses.
Problem is that CanExecute() does not fire when user enters data in text box. It doesn't fire even when text box lose focus.
How could I make this work?
Edit:
Re Yanko's comment:
Delegate command is implemented in MVVM toolkit template and when you create new MVVM project, there is Delegate command in solution. As much as I saw in Prism videos this should be the same class (or at least very similar).
Here is XAML snippet:
...
<UserControl.Resources>
<views:CommandReference x:Key="AddObjectCommandReference"
Command="{Binding AddObjectCommand}" />
</UserControl.Resources>
...
<TextBox Text="{Binding ObjectName, UpdateSourceTrigger=PropertyChanged}"> </TextBox>
<Button Command="{StaticResource AddObjectCommandReference}">Add</Button>
...
View model:
// Property bound to textbox
public string ObjectName
{
get { return objectName; }
set {
objectName = value;
OnPropertyChanged("ObjectName");
}
}
// Command bound to button
public ICommand AddObjectCommand
{
get
{
if (addObjectCommand == null)
{
addObjectCommand = new DelegateCommand(AddObject, CanAddObject);
}
return addObjectCommand;
}
}
private void AddObject()
{
if (ObjectName == null || ObjectName.Length == 0)
return;
objectNames.AddSourceFile(ObjectName);
OnPropertyChanged("ObjectNames"); // refresh listbox
}
private bool CanAddObject()
{
return ObjectName != null && ObjectName.Length > 0;
}
As I wrote in the first part of question, following things work:
property setter for ObjectName is triggered on every keypress in textbox
if I put return true; in CanAddObject(), command is active (button to)
It looks to me that binding is correct.
Thing that I don't know is how to make CanExecute() fire in setter of ObjectName property from above code.
Re Ben's and Abe's answers:
CanExecuteChanged() is event handler and compiler complains:
The event
'System.Windows.Input.ICommand.CanExecuteChanged'
can only appear on the left hand side
of += or -=
there are only two more members of ICommand: Execute() and CanExecute()
Do you have some example that shows how can I make command call CanExecute().
I found command manager helper class in DelegateCommand.cs and I'll look into it, maybe there is some mechanism that could help.
Anyway, idea that in order to activate command based on user input, one needs to "nudge" command object in property setter code looks clumsy. It will introduce dependencies and one of big points of MVVM is reducing them.
Edit 2:
I tried to activate CanExecute by calling addObjectCommand.RaiseCanExecuteChanged() to ObjectName property setter from above code. This does not help either. CanExecute() is fired few times when form is initialized, but after that it never gets executed again. This is the code:
// Property bound to textbox
public string ObjectName
{
get { return objectName; }
set {
objectName = value;
addObjectCommand.RaiseCanExecuteChanged();
OnPropertyChanged("ObjectName");
}
}
Edit 3: Solution
As Yanko Yankov and JerKimball wrote, problem is static resource. When I changed button binding like Yanko suggested:
<Button Command="{Binding AddObjectCommand}">Add</Button>
things started to work immediately. I don't even need RaiseCanExecuteChanged(). Now CanExecute fires automatically.
Why did I use static resource in first place?
Original code was from WPF MVVM toolkit manual. Example in that manual defines commands as static resource and then binds it to menu item. Difference is that instead of string property in my example, MVVM manual works with ObservableCollection.
Edit 4: Final explanation
I finally got it. All I needed to do was to read comment in CommandReference class. It says:
/// <summary>
/// This class facilitates associating a key binding in XAML markup to a command
/// defined in a View Model by exposing a Command dependency property.
/// The class derives from Freezable to work around a limitation in WPF when
/// databinding from XAML.
/// </summary>
So, CommandReference is used for KeyBinding, it is not for binding in visual elements. In above code, command references defined in resources would work for KeyBinding, which I don't have on this user control.
Of course, sample code that came with WPF MVVM toolkit were correct, but I misread it and used CommandReference in visual elements binding.
This WPF MVVM really is tricky sometimes.
Things look much clearer now with the edits, thanks! This might be a stupid question (I'm somewhat tired of a long day's work), but why don't you bind to the command directly, instead of through a static resource?
<Button Command="{Binding AddObjectCommand}">Add</Button>
Since you are using the DelegateCommand, you can call it's RaiseCanExecuteChanged method when your text property changes. I'm not sure what you are trying to accomplish with your CommandReference resource, but typically you just bind the commands directly to the button element's Command property:
<TextBox Text="{Binding ObjectName, UpdateSourceTrigger=ValueChanged}" />
<Button Command="{Binding AddObjectCommand}" Content="Add" />
This would be the relevant portion of your view model:
public string ObjectName
{
get { return objectName; }
set
{
if (value == objectName) return;
value = objectName;
AddObjectCommand.RaiseCanExecuteChanged();
OnPropertyChanged("ObjectName");
}
}
Try raising CanExecuteChanged when your property changes. The command binding is really distinct from the property binding and buttons bound to commands are alerted to a change in status by the CanExecuteChanged event.
In your case, you could fire a check when you do the PropertyChanged on the bound property that would evaluate it and set the command's internal CanExecute flag and then raise CanExecuteChanged. More of a "push" into the ICommand object than a "pull".
Echoing Abe here, but the "right" path to take here is using:
public void RaiseCanExecuteChanged();
exposed on DelegateCommand. As far as dependencies go, I don't think you're really doing anything "bad" by raising this when the property that the command depends on changes within the ViewModel. In that case, the coupling is more or less contained wholly within the ViewModel.
So, taking your above example, in your setter for "ObjectName", you would call RaiseCanExecuteChanged on the command "AddObjectCommand".
I know this is an old question but I personally think it's easier to bind the textbox Length to button's IsEnabled property, e.g.:
<TextBox Name="txtbox" Width="100" Height="30"/>
<Button Content="SomeButton " Width="100" Height="30"
IsEnabled="{Binding ElementName=txtbox, Path=Text.Length, Mode=OneWay}"></Button>
If ElementName binding does not work, use:
<Entry x:Name="Number1" Text="{Binding Number1Text}" Keyboard="Numeric"></Entry>
<Entry x:Name="Number2" Text="{Binding Number2Text}" Keyboard="Numeric"></Entry>
<Button Text="Calculate" x:Name="btnCalculate" Command="{Binding CalculateCommand}" IsEnabled="{Binding Source={x:Reference Number1, Number2}, Path=Text.Length, Mode=OneWay}"></Button>
or use:
<Entry x:Name="Number1" Text="{Binding Number1Text}" Placeholder="Number 1" Keyboard="Numeric"></Entry>
<Entry x:Name="Number2" Text="{Binding Number2Text}" Placeholder="Number 2" Keyboard="Numeric"></Entry>
<Button VerticalOptions="Center" Text="Calculate" x:Name="btnCalculate" Command="{Binding CalculateCommand}">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding Source={x:Reference Number1, Number2},
Path=Text.Length}"
Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Button.Triggers>

Resources