I have a datagrid bound to an observable collection of objects. What I want to do is have a button that will execute a method of the object representing the row of the button that was clicked. So what I have now is something like this:
<DataGridTemplateColumn Header="Command">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Name="cmdCommand" Click="{Binding Command}"
Content="Command"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Which doesn't work and reports the following error:
Click="{Binding Command}" is not valid. '{Binding Command}' is not a valid event handler method name. Only instance methods on the generated or code-behind class are valid.
I've looked at command binding but that looks like it would just end up going to a single external command instead of to the object bound to the row. I have it working using an event handler on the code behind and then routing it to the item bound to the selected row (since the row gets selected when the button is clicked) but that seems like poor way of handing this and I assume I'm just missing something here.
I do this all the time. Here's a look at an example and how you would implement it.
Change your XAML to use the Command property of the button instead of the Click event. I am using the name SaveCommand since it is easier to follow then something named Command.
<Button Command="{Binding Path=SaveCommand}" />
Your CustomClass that the Button is bound to now needs to have a property called SaveCommand of type ICommand. It needs to point to the method on the CustomClass that you want to run when the command is executed.
public MyCustomClass
{
private ICommand _saveCommand;
public ICommand SaveCommand
{
get
{
if (_saveCommand == null)
{
_saveCommand = new RelayCommand(
param => this.SaveObject(),
param => this.CanSave()
);
}
return _saveCommand;
}
}
private bool CanSave()
{
// Verify command can be executed here
}
private void SaveObject()
{
// Save command execution logic
}
}
The above code uses a RelayCommand which accepts two parameters: the method to execute, and a true/false value of if the command can execute or not. The RelayCommand class is a separate .cs file with the code shown below. I got it from Josh Smith :)
/// <summary>
/// A command whose sole purpose is to
/// relay its functionality to other
/// objects by invoking delegates. The
/// default return value for the CanExecute
/// method is 'true'.
/// </summary>
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameters)
{
return _canExecute == null ? true : _canExecute(parameters);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameters)
{
_execute(parameters);
}
#endregion // ICommand Members
}
You have various possibilies. The most simple and the most ugly is:
XAML
<Button Name="cmdCommand" Click="Button_Clicked" Content="Command"/>
Code Behind
private void Button_Clicked(object sender, RoutedEventArgs e) {
FrameworkElement fe=sender as FrameworkElement;
((YourClass)fe.DataContext).DoYourCommand();
}
Another solution (better) is to provide a ICommand-property on your YourClass. This command will have already a reference to your YourClass-object and therefore can execute an action on this class.
XAML
<Button Name="cmdCommand" Command="{Binding YourICommandReturningProperty}" Content="Command"/>
Because during writing this answer, a lot of other answers were posted, I stop writing more. If you are interested in one of the ways I showed or if you think I have made a mistake, make a comment.
Here is the VB.Net rendition of Rachel's answer above.
Obviously the XAML binding is the same...
<Button Command="{Binding Path=SaveCommand}" />
Your Custom Class would look like this...
''' <summary>
''' Retrieves an new or existing RelayCommand.
''' </summary>
''' <returns>[RelayCommand]</returns>
Public ReadOnly Property SaveCommand() As ICommand
Get
If _saveCommand Is Nothing Then
_saveCommand = New RelayCommand(Function(param) SaveObject(), Function(param) CanSave())
End If
Return _saveCommand
End Get
End Property
Private _saveCommand As ICommand
''' <summary>
''' Returns Boolean flag indicating if command can be executed.
''' </summary>
''' <returns>[Boolean]</returns>
Private Function CanSave() As Boolean
' Verify command can be executed here.
Return True
End Function
''' <summary>
''' Code to be run when the command is executed.
''' </summary>
''' <remarks>Converted to a Function in VB.net to avoid the "Expression does not produce a value" error.</remarks>
''' <returns>[Nothing]</returns>
Private Function SaveObject()
' Save command execution logic.
Return Nothing
End Function
And finally the RelayCommand class is as follows...
Public Class RelayCommand : Implements ICommand
ReadOnly _execute As Action(Of Object)
ReadOnly _canExecute As Predicate(Of Object)
Private Event ICommand_CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
''' <summary>
''' Creates a new command that can always execute.
''' </summary>
''' <param name="execute">The execution logic.</param>
Public Sub New(execute As Action(Of Object))
Me.New(execute, Nothing)
End Sub
''' <summary>
''' Creates a new command.
''' </summary>
''' <param name="execute">The execution logic.</param>
''' <param name="canExecute">The execution status logic.</param>
Public Sub New(execute As Action(Of Object), canExecute As Predicate(Of Object))
If execute Is Nothing Then
Throw New ArgumentNullException("execute")
End If
_execute = execute
_canExecute = canExecute
End Sub
<DebuggerStepThrough>
Public Function CanExecute(parameters As Object) As Boolean Implements ICommand.CanExecute
Return If(_canExecute Is Nothing, True, _canExecute(parameters))
End Function
Public Custom Event CanExecuteChanged As EventHandler
AddHandler(ByVal value As EventHandler)
AddHandler CommandManager.RequerySuggested, value
End AddHandler
RemoveHandler(ByVal value As EventHandler)
RemoveHandler CommandManager.RequerySuggested, value
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)
If (_canExecute IsNot Nothing) Then
_canExecute.Invoke(sender)
End If
End RaiseEvent
End Event
Public Sub Execute(parameters As Object) Implements ICommand.Execute
_execute(parameters)
End Sub
End Class
Hope that helps any VB.Net developers!
Click is an event. In your code behind, you need to have a corresponding event handler to whatever you have in the XAML. In this case, you would need to have the following:
private void Command(object sender, RoutedEventArgs e)
{
}
Commands are different. If you need to wire up a command, you'd use the Commmand property of the button and you would either use some pre-built Commands or wire up your own via the CommandManager class (I think).
On Xamarin Forms, the ugliest and most straightforward version:
Xaml:
<Button Margin="0,10,0,0"
Text="Access galery"
Clicked="OpenGalery"
BackgroundColor="{StaticResource Primary}"
TextColor="White" />
then: in .cs
private async void OpenGalery(object sender, EventArgs e)
{
//do your bidding
}
Related
I am trying for MVVM pattern basic level and got struck at ICommand CanExecute changed. I have XAML binding as follows:
<ListBox ItemsSource="{Binding Contact.Addresses}" x:Name="AddressCollections" Height="152" SelectedValue="{Binding SelectedAddress}"
DockPanel.Dock="Top" />
<Button Content="Add" Command="{Binding AddAddressCommand}" DockPanel.Dock="Top" />
<Button Content="Remove" Command="{Binding DeleteAddressCommand}" DockPanel.Dock="Bottom" />
Commands:
Public Class DeleteCommand
Implements ICommand
Private method As Object
Private methodname As String
Public Sub New(ByVal Controlname As String, ByVal mee As Object)
methodname = Controlname
method = mee
End Sub
Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
Select Case methodname
Case "Address"
Return TryCast(method, ModelView.Contacts.ContactMV).CanDeleteAddress()
Case "Numbers"
Return TryCast(method, ModelView.Contacts.ContactMV).CanDeleteNumbers
Case Else : Return False
End Select
End Function
Public Event CanExecuteChanged(sender As Object, e As EventArgs) Implements ICommand.CanExecuteChanged
Public Sub Execute(parameter As Object) Implements ICommand.Execute
Select Case methodname
Case "Address"
TryCast(method, ModelView.Contacts.ContactMV).DeleteAddress()
Case "Numbers"
TryCast(method, ModelView.Contacts.ContactMV).DeleteNumbers()
Case Else
End Select
End Sub
End Class
My ModelView:
Public Class ContactMV
Property Contact As Model.Contacts.ContactMod
Property AddAddressCommand As New Commands.AddCommand("Address", Me)
Property DeleteAddressCommand As New Commands.DeleteCommand("Address", Me)
Property SelectedAddress As Model.Contacts.AddressModel
Public Sub AddAddress()
If Contact.Addresses.Count = 0 Then
Contact.Addresses.Add(New Model.Contacts.AddressModel(Contact.Primary.ID, True))
Else
Contact.Addresses.Add(New Model.Contacts.AddressModel(Contact.Primary.ID, False))
End If
End Sub
Public Sub DeleteAddress()
If IsNothing(SelectedAddress) = False Then
Try
Contact.Addresses.Remove(SelectedAddress)
Catch ex As Exception
MsgBox("Address not found")
End Try
End If
End Sub
Public Function CanDeleteAddress()
If IsNothing(SelectedAddress) Then
Return False
Else
Return Contact.Addresses.Contains(SelectedAddress)
End If
End Function
End Class
The problem is that the Canexecutechanged is firing only at start, I actually want to get the delete button enabled only when something in the listbox is selected, and I want to get it done by MVVM - ICommand binding method. Could you please explain where i went wrong or miss understood the ICommand implementation.
Thank you.
Updated Relay iCommand code I use:
Public Class RelayCommand
Implements ICommand
''' <summary>
''' A command whose sole purpose is to relay its functionality to other objects by invoking delegates. The default return value for the CanExecute method is 'true'.
''' </summary>
''' <remarks></remarks>
#Region "Declarations"
Private ReadOnly _CanExecute As Func(Of Boolean)
Private ReadOnly _Execute As Action
#End Region
#Region "Constructors"
Public Sub New(ByVal execute As Action)
Me.New(execute, Nothing)
End Sub
Public Sub New(ByVal execute As Action, ByVal canExecute As Func(Of Boolean))
If execute Is Nothing Then
Throw New ArgumentNullException("execute")
End If
_Execute = execute
_CanExecute = canExecute
End Sub
#End Region
#Region "ICommand"
Public Custom Event CanExecuteChanged As EventHandler Implements System.Windows.Input.ICommand.CanExecuteChanged
AddHandler(ByVal value As EventHandler)
If _CanExecute IsNot Nothing Then
AddHandler CommandManager.RequerySuggested, value
End If
End AddHandler
RemoveHandler(ByVal value As EventHandler)
If _CanExecute IsNot Nothing Then
RemoveHandler CommandManager.RequerySuggested, value
End If
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
'This is the RaiseEvent block
'CommandManager.InvalidateRequerySuggested()
End RaiseEvent
End Event
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements System.Windows.Input.ICommand.CanExecute
If _CanExecute Is Nothing Then
Return True
Else
Return _CanExecute.Invoke
End If
End Function
Public Sub Execute(ByVal parameter As Object) Implements System.Windows.Input.ICommand.Execute
_Execute.Invoke()
End Sub
#End Region
End Class
Most of the code is a copy, but I understood the working by below comments.
As Raul OtaƱo has pointed out, you can raise the CanExecuteChanged. However, not all MVVM frameworks provide a RaiseCanExecuteChanged method. It's also worth noting that the actual event CanExecuteChanged must be called on the UI thread. So, if you're expecting a callback from some thread in your model, you need to invoke it back to the UI thread, like this:
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
{
Application.Current.Dispatcher.Invoke((Action)(() => { CanExecuteChanged(this, EventArgs.Empty); }));
}
}
I would very much recommend against calling CommandManager.InvalidateRequerySuggested() because although this works functionally, and is ok for small applications, it is indiscriminate and will potentially re-query every command! In a large system with lots of commands, this can be very very slow!
You must have in your ICommand implementation some method like RaiseCanExecuteChanged that fires the event CanExecuteChanged. Then every time that the selected item in the list changed, in your view model you execute the RaiseCanExecuteChanged from the command you want. Any way I suggest you to use any generic command, like the RelayCommand of GalaSoft MVVMLite library, or any implementation of DelegateCommand.
Hope this helps...
I'm tasked with creating something of a 3D interface for an application. After looking through my options I decided Viewport2DVisual3D was the easiest to understand and use with my game design background (I am comfortable with matrix transformations, etc).
So far I've got a nice list of buttons displaying in a semicircular 'stage' sort of pattern onscreen. This is a good start. Now, what I want to do is allow the carousel to rotate with user input. For now, I'd have a button rotate into center view when clicked, but eventually I'll use manipulation data to allow the screen to be swiped about.
The issue I'm having is binding the Transform property of each V2DV3D to the data backing each Button control. I am not sure how to do this in code-behind, and it needs to be code-behind in order to programmatically build the transforms.
As it is, I assign the value this way (vv is a Viewport2DVisual3D object):
vv.SetValue(Viewport2DVisual3D.TransformProperty, item.Transform);
Where item is a CarouselItem which implements INotifyPropertyChanged:
public class CarouselItem : INotifyPropertyChanged
{
[...]
public Transform3DGroup Transform
{
get { return _transform; }
set { _transform = value; OnPropertyChanged("Transform"); }
}
[...]
private void Recalc()
{
Transform3D rotate = new RotateTransform3D(new
AxisAngleRotation3D(CarouselBrowser.Up, angle));
Transform3D translate = new TranslateTransform3D(0, 0,
CarouselBrowser.Radius);
Transform3D translate2 = new TranslateTransform3D(
CarouselBrowser.Focus);
Transform3DGroup tGroup = new Transform3DGroup();
tGroup.Children.Add(translate);
tGroup.Children.Add(rotate);
tGroup.Children.Add(translate2);
Transform = tGroup;
}
[...]
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
[...]
}
(some code omitted; Recalc is called when angle is changed, and the new value is different than the old)
So the initial 'binding' works fine, the buttons take their initial transformations, but any further modifications to Transform result in no change. The event handler for the Transform property change event has no subscribers. How should I change the binding/relationship with CarouselItem and V2DV3D to get them to link? There doesn't seem to be any sort of DataContext or similar property of V2DV3D I could bind the whole object to.
I am trying to implement a search (Ctrl+F) feature on a XamDataGrid. How can I programmatically invoke record filtering on the grid that searches across content in multiple columns and displays only columns that match the search?
The record filtering in the DataPresenter is just that - a means of the filtering the records based on some specified criteria. Normally that criteria is provided via one of the built in ui's - either using the LabelIcons which is just a drop down list of the filtered values or via the FilterRecord which is a dedicated special record that is displayed with cells for each column to allow choosing/entering an operator and value.
That being said the record filtering can be manipulated in code if you wish. The FieldLayout exposes a RecordFilters collection where a RecordFilter provides the conditions (i.e. the match criteria) and the field for which the match should be performed. It is also exposed off the RecordManager but this is really to facilitate filtering in hierarchical situations where the filter criteria is different for each "island" of child records.
Since you want to search multiple fields for the same criteria, you would need to create a RecordFilter for each Field in the FieldLayout's Fields collection (or whatever subset of the Fields to which you want this to be applied). Since you want to do a text search you probably want to use a ComparisonCondition where the ComparisonOperator you use is Contains and the value would be the text to search for. Now since you want a record to match if the value is found in any of the fields (for which you have created a RecordFilter) you will also need to set the FieldLayoutSettings' RecordFiltersLogicalOperator property to Or (by default this resolves to And since one typically wants to match a record when all the criteria matches the entered values).
So to that end the following is a basic attached behavior (in this case a property named FilterText that you would set on the DataPresenter to be filtered). This behavior/property will manipulate the RecordFilters of the DefaultFieldLayout considering the text value of the FilterText property.
public static class DataPresenterHelpers
{
#region FilterText
/// <summary>
/// FilterText Attached Dependency Property
/// </summary>
public static readonly DependencyProperty FilterTextProperty =
DependencyProperty.RegisterAttached("FilterText", typeof(string), typeof(DataPresenterHelpers),
new FrameworkPropertyMetadata((string)null,
new PropertyChangedCallback(OnFilterTextChanged)));
/// <summary>
/// Gets the text to be used to filter the DataPresenter on which the property was set.
/// </summary>
public static string GetFilterText(DependencyObject d)
{
return (string)d.GetValue(FilterTextProperty);
}
/// <summary>
/// Sets the filter text on the DataPresenter that should be used to manipulate the RecordFilters of the specified DataPresenter
/// </summary>
public static void SetFilterText(DependencyObject d, string value)
{
d.SetValue(FilterTextProperty, value);
}
/// <summary>
/// Handles changes to the FilterText property.
/// </summary>
private static void OnFilterTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var dp = d as DataPresenterBase;
if (dp.DefaultFieldLayout != null)
{
dp.DefaultFieldLayout.RecordFilters.Clear();
dp.DefaultFieldLayout.Settings.RecordFiltersLogicalOperator = LogicalOperator.Or;
foreach (var field in dp.DefaultFieldLayout.Fields)
{
var filter = new RecordFilter();
filter.Field = field;
filter.Conditions.Add(new ComparisonCondition(ComparisonOperator.Contains, e.NewValue));
dp.DefaultFieldLayout.RecordFilters.Add(filter);
}
}
}
#endregion //FilterText
}
You can then do something like the following to hook up the value of a TextBox to this attached property. Note, you would need to define an xmlns mapping for local to be whatever clr namespace you put the above class into.
<TextBox DockPanel.Dock="Top" x:Name="txtFilter" />
<igDP:XamDataGrid
x:Name="grid"
BindToSampleData="True"
local:DataPresenterHelpers.FilterText="{Binding ElementName=txtFilter, Path=Text}">
</igDP:XamDataGrid>
Now the other part of your question was about only showing the columns which contained matching values. This isn't really possible as part of the record filters itself since record filtering is about filtering out records based on some specified criteria and has no relation to hiding/showing columns/fields. That being said perhaps one way to help the end user understand where the matching text resides would be to highlight that text within the cells.
To provide such functionality I defined a derived TextBlock called HighlightTextBlock. It exposes several properties:
RawText - This is the source text that will be displayed. It cannot use the Text property because this element will manipulate the Inlines of the TextBlock and that will set the Text property which would break bindings in the case of one way bindings or push back values to the source in the case of two way bindings.
FilterText - This is used to indicate the text that is to be sought within the RawText.
FilterTextComparisonType - This is used to indicate the string comparison for the match (i.e. case sensitive, etc.).
FilterTextForeground - The foreground to be used to highlight the matching text.
FilterTextBackground - The background to be used to highlight the matching text.
Here is the code for the class:
public class HighlightTextBlock
: TextBlock
{
#region Member Variables
private DispatcherOperation _pendingUpdate;
#endregion //Member Variables
#region Constructor
static HighlightTextBlock()
{
}
/// <summary>
/// Initializes a new <see cref="HighlightTextBlock"/>
/// </summary>
public HighlightTextBlock()
{
}
#endregion //Constructor
#region Base class overrides
#region OnInitialized
protected override void OnInitialized(EventArgs e)
{
if (_pendingUpdate != null)
this.UpdateInlines(null);
base.OnInitialized(e);
}
#endregion //OnInitialized
#endregion //Base class overrides
#region Properties
#region FilterText
/// <summary>
/// Identifies the <see cref="FilterText"/> dependency property
/// </summary>
public static readonly DependencyProperty FilterTextProperty = DependencyProperty.Register("FilterText",
typeof(string), typeof(HighlightTextBlock), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnCriteriaChanged)));
/// <summary>
/// Returns or sets the text that should be highlighted
/// </summary>
/// <seealso cref="FilterTextProperty"/>
[Description("Returns or sets the text that should be highlighted")]
[Category("Behavior")]
[Bindable(true)]
public string FilterText
{
get
{
return (string)this.GetValue(HighlightTextBlock.FilterTextProperty);
}
set
{
this.SetValue(HighlightTextBlock.FilterTextProperty, value);
}
}
#endregion //FilterText
#region FilterTextBackground
/// <summary>
/// Identifies the <see cref="FilterTextBackground"/> dependency property
/// </summary>
public static readonly DependencyProperty FilterTextBackgroundProperty = DependencyProperty.Register("FilterTextBackground",
typeof(Brush), typeof(HighlightTextBlock), new FrameworkPropertyMetadata(Brushes.Yellow, new PropertyChangedCallback(OnCriteriaChanged)));
/// <summary>
/// Returns or sets the background of the matching text.
/// </summary>
/// <seealso cref="FilterTextBackgroundProperty"/>
[Description("Returns or sets the background of the matching text.")]
[Category("Behavior")]
[Bindable(true)]
public Brush FilterTextBackground
{
get
{
return (Brush)this.GetValue(HighlightTextBlock.FilterTextBackgroundProperty);
}
set
{
this.SetValue(HighlightTextBlock.FilterTextBackgroundProperty, value);
}
}
#endregion //FilterTextBackground
#region FilterTextComparisonType
/// <summary>
/// Identifies the <see cref="FilterTextComparisonType"/> dependency property
/// </summary>
public static readonly DependencyProperty FilterTextComparisonTypeProperty = DependencyProperty.Register("FilterTextComparisonType",
typeof(StringComparison), typeof(HighlightTextBlock), new FrameworkPropertyMetadata(StringComparison.CurrentCultureIgnoreCase, new PropertyChangedCallback(OnCriteriaChanged)));
/// <summary>
/// Returns or sets the StringComparison when locating the FilterText within the RawText.
/// </summary>
/// <seealso cref="FilterTextComparisonTypeProperty"/>
[Description("Returns or sets the StringComparison when locating the FilterText within the RawText.")]
[Category("Behavior")]
[Bindable(true)]
public StringComparison FilterTextComparisonType
{
get
{
return (StringComparison)this.GetValue(HighlightTextBlock.FilterTextComparisonTypeProperty);
}
set
{
this.SetValue(HighlightTextBlock.FilterTextComparisonTypeProperty, value);
}
}
#endregion //FilterTextComparisonType
#region FilterTextForeground
/// <summary>
/// Identifies the <see cref="FilterTextForeground"/> dependency property
/// </summary>
public static readonly DependencyProperty FilterTextForegroundProperty = DependencyProperty.Register("FilterTextForeground",
typeof(Brush), typeof(HighlightTextBlock), new FrameworkPropertyMetadata(Brushes.Black, new PropertyChangedCallback(OnCriteriaChanged)));
/// <summary>
/// Returns or sets the brushed used for the foreground of the matching text.
/// </summary>
/// <seealso cref="FilterTextForegroundProperty"/>
[Description("Returns or sets the brushed used for the foreground of the matching text.")]
[Category("Behavior")]
[Bindable(true)]
public Brush FilterTextForeground
{
get
{
return (Brush)this.GetValue(HighlightTextBlock.FilterTextForegroundProperty);
}
set
{
this.SetValue(HighlightTextBlock.FilterTextForegroundProperty, value);
}
}
#endregion //FilterTextForeground
#region RawText
/// <summary>
/// Identifies the <see cref="RawText"/> dependency property
/// </summary>
public static readonly DependencyProperty RawTextProperty = DependencyProperty.Register("RawText",
typeof(string), typeof(HighlightTextBlock), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnCriteriaChanged)));
/// <summary>
/// Returns or sets the base string that will be displayed by the element.
/// </summary>
/// <seealso cref="RawTextProperty"/>
[Description("Returns or sets the base string that will be displayed by the element.")]
[Category("Behavior")]
[Bindable(true)]
public string RawText
{
get
{
return (string)this.GetValue(HighlightTextBlock.RawTextProperty);
}
set
{
this.SetValue(HighlightTextBlock.RawTextProperty, value);
}
}
#endregion //RawText
#endregion //Properties
#region Methods
#region OnCriteriaChanged
private static void OnCriteriaChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as HighlightTextBlock;
if (instance._pendingUpdate == null)
{
instance._pendingUpdate = instance.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new SendOrPostCallback(instance.UpdateInlines), new object[] { null });
}
}
#endregion //OnCriteriaChanged
#region UpdateInlines
private void UpdateInlines(object param)
{
_pendingUpdate = null;
string filterText = this.FilterText;
string text = this.RawText;
var inlines = this.Inlines;
try
{
inlines.Clear();
if (string.IsNullOrEmpty(filterText))
{
inlines.Add(text);
return;
}
var foreground = this.FilterTextForeground;
var background = this.FilterTextBackground;
var comparison = this.FilterTextComparisonType;
var newInlines = new List<Inline>();
int filterTextLen = filterText.Length;
int start = 0;
do
{
int end = text.IndexOf(filterText, start, comparison);
string substr = text.Substring(start, (end < 0 ? text.Length : end) - start);
newInlines.Add(new Run(substr));
if (end < 0)
break;
var run = new Run(text.Substring(end, filterTextLen));
// note we could bind and not rebuild when the background/foreground
// changes but that doesn't seem likely to happen and would add more
// overhead than just referencing the value directly
if (null != foreground)
run.Foreground = foreground;
if (null != background)
run.Background = background;
newInlines.Add(run);
start = end + filterTextLen;
} while (true);
inlines.AddRange(newInlines);
}
finally
{
if (_pendingUpdate != null)
{
_pendingUpdate.Abort();
_pendingUpdate = null;
}
}
}
#endregion //UpdateInlines
#endregion //Methods
}
So then you could change the templates for the editors you are using to use this in their render template. e.g.
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:igDP="http://infragistics.com/DataPresenter"
xmlns:igEditors="http://infragistics.com/Editors"
xmlns:local="clr-namespace:WpfApplication6"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<TextBox DockPanel.Dock="Top" x:Name="txtFilter" />
<igDP:XamDataGrid
x:Name="grid"
BindToSampleData="True"
local:DataPresenterHelpers.FilterText="{Binding ElementName=txtFilter, Path=Text}">
<igDP:XamDataGrid.Resources>
<Style TargetType="igEditors:XamTextEditor">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="igEditors:XamTextEditor">
<Border x:Name="MainBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
>
<local:HighlightTextBlock
Margin="{TemplateBinding Padding}"
FilterText="{Binding Path=Host.DataPresenter.(local:DataPresenterHelpers.FilterText), RelativeSource={RelativeSource TemplatedParent}}"
RawText="{TemplateBinding DisplayText}"
TextWrapping="{TemplateBinding TextWrapping}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
TextAlignment="{TemplateBinding TextAlignmentResolved}"
/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</igDP:XamDataGrid.Resources>
</igDP:XamDataGrid>
</DockPanel>
The way that I would approach this problem would be to add code to the search button click event (or some other trigger mechanism) and perform the search directly in code, using linq or record by record comparison, whatever works best in your situation (it would help to know how the columns and rows are populated).
Once you have the set of records and/or columns that have the appropriate results, hide the columns that do not have hits. To streamline the process, I would create a collection of columns that should be hidden, then have a method that processes each column. The method would search through the column data and at the sign of the first match, would remove the column from the list of columns to be hidden and stop processing records in the column.
At the end of the process, your list would contain the list of columns to hide, which you could then use to perform the actual hiding.
This mechanism could also be extended to hide rows that don't have any matches, assuming that there are multiple rows. I would support this by hiding each of the rows that don't have a match while processing the first column and then unhiding them for subsequent columns where a match was found.
Thank you for sharing. I'll share too.
I used this example to handle a more simple case.
I just wanted to set up a filter on one column.
Here it is:
Imports Infragistics.Windows.DataPresenter
Imports Infragistics.Windows.Controls
Public Class DataPresenterFilter
Public Shared Function GetTitleFilter(ByVal element As DependencyObject) As String
If element Is Nothing Then
Throw New ArgumentNullException("element")
End If
Return element.GetValue(TitleFilter)
End Function
Public Shared Sub SetTitleFilter(ByVal element As DependencyObject,
ByVal value As String)
If element Is Nothing Then
Throw New ArgumentNullException("element")
End If
element.SetValue(TitleFilter, value)
End Sub
Public Shared ReadOnly TitleFilter As _
DependencyProperty = DependencyProperty.RegisterAttached("TitleFilter", _
GetType(String), GetType(DataPresenterFilter), _
New FrameworkPropertyMetadata(String.Empty,
New PropertyChangedCallback(AddressOf OnTitleFilterChanged)))
Private Shared Sub OnTitleFilterChanged(d As DependencyObject,
e As DependencyPropertyChangedEventArgs)
Dim dp As DataPresenterBase = CType(d, DataPresenterBase)
If (Not dp.DefaultFieldLayout Is Nothing) Then
Dim Filter As RecordFilter = New RecordFilter()
Filter.FieldName = "Title"
Filter.Conditions.Add(
New ComparisonCondition(ComparisonOperator.Contains, e.NewValue))
dp.DefaultFieldLayout.RecordFilters.Remove(
dp.DefaultFieldLayout.RecordFilters.Item("Title"))
dp.DefaultFieldLayout.RecordFilters.Add(Filter)
End If
End Sub
End Class
And the XAML:
xmlns:Inv="clr-namespace:InventoryApp"
<TextBox Height="23" Margin="0,2,0,2" x:Name="tbTitle" />
<igDP:XamDataPresenter Name="xamDataPresenterPublicationCollection"
DataSource="{Binding Source={StaticResource PublicationCollectionViewSource},
UpdateSourceTrigger=PropertyChanged}" IsSynchronizedWithCurrentItem="True"
ActiveDataItem="{Binding Path=PublicationModel, Mode=OneWay}"
Inv:DataPresenterFilter.TitleFilter="{Binding ElementName=tbTitle, Path=Text}"/>
This works perfectly for me.
I was looking for a very basic solution, so I'll share what has worked for me. Here is my C# code-behind code to apply a filter to a column:
grdConnectionManagerEntries.FieldLayouts[0].RecordFilters.Clear(); // Clear any existing filters before applying this one.
grdConnectionManagerEntries.FieldLayouts[0].RecordFilters.Add(new RecordFilter(new Field("Name")));
grdConnectionManagerEntries.FieldLayouts[0].RecordFilters[0].Conditions.Add(new Infragistics.Windows.Controls.ComparisonCondition(Infragistics.Windows.Controls.ComparisonOperator.Contains, connectionName));
Here grdConnectionManagerEntries is my XamDataGrid, I have a Field (i.e. column) called Name, and I'm adding a Contains filter to it that will filter by the given connectionName.
For some reason the filter text only shows up in the XamDataGrid's UI when I use a Contains comparison operator; I'm guessing because that's the default filter operator on that column. So there's something that I'm not doing quite right, but since I wanted to use the Contains operator anyways it has worked for my purposes.
The basic idea behind a Cancel button is to enable closing your window with an Escape Keypress.
You can set the IsCancel property on
the Cancel button to true, causing the
Cancel button to automatically close
the dialog without handling the Click
event.
Source: Programming WPF (Griffith, Sells)
So this should work
<Window>
<Button Name="btnCancel" IsCancel="True">_Close</Button>
</Window>
However the behavior I expect isn't working out for me. The parent window is the main application window specified by the Application.StartupUri property. What works is
<Button Name="btnCancel" IsCancel=True" Click="CloseWindow">_Close</Button>
private void CloseWindow(object sender, RoutedEventArgs)
{
this.Close();
}
Is the behavior of IsCancel different based on whether the Window is a normal window or a Dialog? Does IsCancel work as advertised only if ShowDialog has been called?
Is an explicit Click handler required for the button (with IsCancel set to true) to close a window on an Escape press?
Yes, it only works on dialogs as a normal window has no concept of "cancelling", it's the same as DialogResult.Cancel returning from ShowDialog in WinForms.
If you wanted to close a Window with escape you could add a handler to PreviewKeyDown on the window, pickup on whether it is Key.Escape and close the form:
public MainWindow()
{
InitializeComponent();
this.PreviewKeyDown += new KeyEventHandler(CloseOnEscape);
}
private void CloseOnEscape(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
Close();
}
We can take Steve's answer one step further and create an attached property that provides the "escape on close" functionality for any window. Write the property once and use it in any window. Just add the following to the window XAML:
yournamespace:WindowService.EscapeClosesWindow="True"
Here's the code for the property:
using System.Windows;
using System.Windows.Input;
/// <summary>
/// Attached behavior that keeps the window on the screen
/// </summary>
public static class WindowService
{
/// <summary>
/// KeepOnScreen Attached Dependency Property
/// </summary>
public static readonly DependencyProperty EscapeClosesWindowProperty = DependencyProperty.RegisterAttached(
"EscapeClosesWindow",
typeof(bool),
typeof(WindowService),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnEscapeClosesWindowChanged)));
/// <summary>
/// Gets the EscapeClosesWindow property. This dependency property
/// indicates whether or not the escape key closes the window.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> to get the property from</param>
/// <returns>The value of the EscapeClosesWindow property</returns>
public static bool GetEscapeClosesWindow(DependencyObject d)
{
return (bool)d.GetValue(EscapeClosesWindowProperty);
}
/// <summary>
/// Sets the EscapeClosesWindow property. This dependency property
/// indicates whether or not the escape key closes the window.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> to set the property on</param>
/// <param name="value">value of the property</param>
public static void SetEscapeClosesWindow(DependencyObject d, bool value)
{
d.SetValue(EscapeClosesWindowProperty, value);
}
/// <summary>
/// Handles changes to the EscapeClosesWindow property.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> that fired the event</param>
/// <param name="e">A <see cref="DependencyPropertyChangedEventArgs"/> that contains the event data.</param>
private static void OnEscapeClosesWindowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Window target = (Window)d;
if (target != null)
{
target.PreviewKeyDown += new System.Windows.Input.KeyEventHandler(Window_PreviewKeyDown);
}
}
/// <summary>
/// Handle the PreviewKeyDown event on the window
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">A <see cref="KeyEventArgs"/> that contains the event data.</param>
private static void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
Window target = (Window)sender;
// If this is the escape key, close the window
if (e.Key == Key.Escape)
target.Close();
}
}
This isn't quite right is it...
MSDN says this: When you set the IsCancel property of a button to true, you create a Button that is registered with the AccessKeyManager. The button is then activated when a user presses the ESC key.
So you do need a handler in your code behind
And you don't need any attached properties or anything like that
Yes this is right.In windows Application in WPF AcceptButton and Cancel Button is there. But one thing is that if you are setting your control visibility as false, then it won't work as expected.For that you need to make as visibility as true in WPF. For example: (it is not working for Cancel button because here visibility is false)
<Button x:Name="btnClose" Content="Close" IsCancel="True" Click="btnClose_Click" Visibility="Hidden"></Button>
So, you need make it:
<Button x:Name="btnClose" Content="Close" IsCancel="True" Click="btnClose_Click"></Button>
Then you have write btnClose_Click in code behind file:
private void btnClose_Click (object sender, RoutedEventArgs e)
{ this.Close(); }
How can I handle the Keyboard.KeyDown event without using code-behind? We are trying to use the MVVM pattern and avoid writing an event handler in code-behind file.
To bring an updated answer, the .net 4.0 framework enables you to do this nicely by letting you bind a KeyBinding Command to a command in a viewmodel.
So... If you wanted to listen for the Enter key, you'd do something like this:
<TextBox AcceptsReturn="False">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding SearchCommand}"
CommandParameter="{Binding Path=Text, RelativeSource={RelativeSource AncestorType={x:Type TextBox}}}" />
</TextBox.InputBindings>
</TextBox>
WOW - there's like a thousand answers and here I'm going to add another one..
The really obvious thing in a 'why-didn't-I-realise-this-forehead-slap' kind of way is that the code-behind and the ViewModel sit in the same room so-to-speak, so there is no reason why they're not allowed to have a conversation.
If you think about it, the XAML is already intimately coupled to the ViewModel's API, so you might just as well go and make a dependency on it from the code behind.
The other obvious rules to obey or ignore still applies (interfaces, null checks <-- especially if you use Blend...)
I always make a property in the code-behind like this:
private ViewModelClass ViewModel { get { return DataContext as ViewModelClass; } }
This is the client-code. The null check is for helping control hosting as like in blend.
void someEventHandler(object sender, KeyDownEventArgs e)
{
if (ViewModel == null) return;
/* ... */
ViewModel.HandleKeyDown(e);
}
Handle your event in the code behind like you want to (UI events are UI-centric so it's OK) and then have a method on the ViewModelClass that can respond to that event. The concerns are still seperated.
ViewModelClass
{
public void HandleKeyDown(KeyEventArgs e) { /* ... */ }
}
All these other attached properties and voodoo is very cool and the techniques are really useful for some other things, but here you might get away with something simpler...
A little late, but here goes.
Microsoft's WPF Team recently released an early version of their WPF MVVM Toolkit
. In it, you'll find a class called CommandReference that can handle things like keybindings. Look at their WPF MVVM template to see how it works.
I do this by using an attached behaviour with 3 dependency properties; one is the command to execute, one is the parameter to pass to the command and the other is the key which will cause the command to execute. Here's the code:
public static class CreateKeyDownCommandBinding
{
/// <summary>
/// Command to execute.
/// </summary>
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command",
typeof(CommandModelBase),
typeof(CreateKeyDownCommandBinding),
new PropertyMetadata(new PropertyChangedCallback(OnCommandInvalidated)));
/// <summary>
/// Parameter to be passed to the command.
/// </summary>
public static readonly DependencyProperty ParameterProperty =
DependencyProperty.RegisterAttached("Parameter",
typeof(object),
typeof(CreateKeyDownCommandBinding),
new PropertyMetadata(new PropertyChangedCallback(OnParameterInvalidated)));
/// <summary>
/// The key to be used as a trigger to execute the command.
/// </summary>
public static readonly DependencyProperty KeyProperty =
DependencyProperty.RegisterAttached("Key",
typeof(Key),
typeof(CreateKeyDownCommandBinding));
/// <summary>
/// Get the command to execute.
/// </summary>
/// <param name="sender"></param>
/// <returns></returns>
public static CommandModelBase GetCommand(DependencyObject sender)
{
return (CommandModelBase)sender.GetValue(CommandProperty);
}
/// <summary>
/// Set the command to execute.
/// </summary>
/// <param name="sender"></param>
/// <param name="command"></param>
public static void SetCommand(DependencyObject sender, CommandModelBase command)
{
sender.SetValue(CommandProperty, command);
}
/// <summary>
/// Get the parameter to pass to the command.
/// </summary>
/// <param name="sender"></param>
/// <returns></returns>
public static object GetParameter(DependencyObject sender)
{
return sender.GetValue(ParameterProperty);
}
/// <summary>
/// Set the parameter to pass to the command.
/// </summary>
/// <param name="sender"></param>
/// <param name="parameter"></param>
public static void SetParameter(DependencyObject sender, object parameter)
{
sender.SetValue(ParameterProperty, parameter);
}
/// <summary>
/// Get the key to trigger the command.
/// </summary>
/// <param name="sender"></param>
/// <returns></returns>
public static Key GetKey(DependencyObject sender)
{
return (Key)sender.GetValue(KeyProperty);
}
/// <summary>
/// Set the key which triggers the command.
/// </summary>
/// <param name="sender"></param>
/// <param name="key"></param>
public static void SetKey(DependencyObject sender, Key key)
{
sender.SetValue(KeyProperty, key);
}
/// <summary>
/// When the command property is being set attach a listener for the
/// key down event. When the command is being unset (when the
/// UIElement is unloaded for instance) remove the listener.
/// </summary>
/// <param name="dependencyObject"></param>
/// <param name="e"></param>
static void OnCommandInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
UIElement element = (UIElement)dependencyObject;
if (e.OldValue == null && e.NewValue != null)
{
element.AddHandler(UIElement.KeyDownEvent,
new KeyEventHandler(OnKeyDown), true);
}
if (e.OldValue != null && e.NewValue == null)
{
element.RemoveHandler(UIElement.KeyDownEvent,
new KeyEventHandler(OnKeyDown));
}
}
/// <summary>
/// When the parameter property is set update the command binding to
/// include it.
/// </summary>
/// <param name="dependencyObject"></param>
/// <param name="e"></param>
static void OnParameterInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
UIElement element = (UIElement)dependencyObject;
element.CommandBindings.Clear();
// Setup the binding
CommandModelBase commandModel = e.NewValue as CommandModelBase;
if (commandModel != null)
{
element.CommandBindings.Add(new CommandBinding(commandModel.Command,
commandModel.OnExecute, commandModel.OnQueryEnabled));
}
}
/// <summary>
/// When the trigger key is pressed on the element, check whether
/// the command should execute and then execute it.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
static void OnKeyDown(object sender, KeyEventArgs e)
{
UIElement element = sender as UIElement;
Key triggerKey = (Key)element.GetValue(KeyProperty);
if (e.Key != triggerKey)
{
return;
}
CommandModelBase cmdModel = (CommandModelBase)element.GetValue(CommandProperty);
object parameter = element.GetValue(ParameterProperty);
if (cmdModel.CanExecute(parameter))
{
cmdModel.Execute(parameter);
}
e.Handled = true;
}
}
To use this from xaml you can do something like this:
<TextBox framework:CreateKeyDownCommandBinding.Command="{Binding MyCommand}">
<framework:CreateKeyDownCommandBinding.Key>Enter</framework:CreateKeyDownCommandBinding.Key>
</TextBox>
Edit: CommandModelBase is a base class I use for all commands. It's based on the CommandModel class from Dan Crevier's article on MVVM (here). Here's the source for the slightly modified version I use with CreateKeyDownCommandBinding:
public abstract class CommandModelBase : ICommand
{
RoutedCommand routedCommand_;
/// <summary>
/// Expose a command that can be bound to from XAML.
/// </summary>
public RoutedCommand Command
{
get { return routedCommand_; }
}
/// <summary>
/// Initialise the command.
/// </summary>
public CommandModelBase()
{
routedCommand_ = new RoutedCommand();
}
/// <summary>
/// Default implementation always allows the command to execute.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void OnQueryEnabled(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = CanExecute(e.Parameter);
e.Handled = true;
}
/// <summary>
/// Subclasses must provide the execution logic.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void OnExecute(object sender, ExecutedRoutedEventArgs e)
{
Execute(e.Parameter);
}
#region ICommand Members
public virtual bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public abstract void Execute(object parameter);
#endregion
}
Comments and suggestions for improvements would be very welcome.
Similar to karlipoppins answer, but I found it didn't work without the following additions/changes:
<TextBox Text="{Binding UploadNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding FindUploadCommand}" />
</TextBox.InputBindings>
</TextBox>
I looked into that issue a few months ago, and I wrote a markup extension that does the trick. It can be used like a regular binding :
<Window.InputBindings>
<KeyBinding Key="E" Modifiers="Control" Command="{input:CommandBinding EditCommand}"/>
</Window.InputBindings>
The full source code for this extension can be found here :
http://www.thomaslevesque.com/2009/03/17/wpf-using-inputbindings-with-the-mvvm-pattern/
Please be aware that this workaround is probably not very "clean", because it uses some private classes and fields through reflection...
Short answer is you can't handle straight keyboard input events without code-behind, but you can handle InputBindings with MVVM (I can show you a relevant example if this is what you need).
Can you provide more information on what you want to do in the handler?
Code-behind isn't to be avoided entirely with MVVM. It's simply to be used for strictly UI-related tasks. A cardinal example would be having some type of 'data entry form' that, when loaded, needs to set focus to the first input element (text box, combobox, whatever). You would commonly assign that element an x:Name attribute, then hook up the Window/Page/UserControl's 'Loaded' event to set focus to that element. This is perfectly ok by the pattern because the task is UI-centric and has nothing to do with the data it represents.
I know this question is very old, but I came by this because this type of functionality was just made easier to implement in Silverlight (5). So maybe others will come by here too.
I wrote this simple solution after I could not find what I was looking for. Turned out it was rather simple. It should work in both Silverlight 5 and WPF.
public class KeyToCommandExtension : IMarkupExtension<Delegate>
{
public string Command { get; set; }
public Key Key { get; set; }
private void KeyEvent(object sender, KeyEventArgs e)
{
if (Key != Key.None && e.Key != Key) return;
var target = (FrameworkElement)sender;
if (target.DataContext == null) return;
var property = target.DataContext.GetType().GetProperty(Command, BindingFlags.Public | BindingFlags.Instance, null, typeof(ICommand), new Type[0], null);
if (property == null) return;
var command = (ICommand)property.GetValue(target.DataContext, null);
if (command != null && command.CanExecute(Key))
command.Execute(Key);
}
public Delegate ProvideValue(IServiceProvider serviceProvider)
{
if (string.IsNullOrEmpty(Command))
throw new InvalidOperationException("Command not set");
var targetProvider = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
if (!(targetProvider.TargetObject is FrameworkElement))
throw new InvalidOperationException("Target object must be FrameworkElement");
if (!(targetProvider.TargetProperty is EventInfo))
throw new InvalidOperationException("Target property must be event");
return Delegate.CreateDelegate(typeof(KeyEventHandler), this, "KeyEvent");
}
Usage:
<TextBox KeyUp="{MarkupExtensions:KeyToCommand Command=LoginCommand, Key=Enter}"/>
Notice that Command is a string and not an bindable ICommand. I know this is not as flexible, but it is cleaner when used, and what you need 99% of the time. Though it should not be a problem to change.