User-friendly way to display a Visual in a WPF application? - wpf

Looking for a simple, elegant way to display any given Visual to the user. The only way I can think of off my head is to slap it in a brush and paint it on a Rectangle that's in a ScrollViewer. Not exactly the best option.

You could create a wrapper that inherits from FrameworkElement that would either host your Visual or a generic wrapper that will host any object deriving from Visual.
Take a look at the example in Visual.AddVisual or, if you want to host more than one visual, take a look at the (partial) example in Using DrawingVisual Objects

I don't see a way how you could do that since a Visual has neither a position nor a size. Perhaps stick to FrameworkElement and create a style for it?

My (current) answer is to slap it in an XpsDocument and display it in a DocumentViewer. I could, I suppose, do it a little less complex, but I already have the infrastructure to do it this way. Its not 100%, but it works.
First, a behavior so that I can bind to DocumentViewer.Document (its a friggen POCO, urgh):
public sealed class XpsDocumentBinding
{
#region Document
/// <summary>
/// The <see cref="DependencyProperty"/> for <see cref="Document"/>.
/// </summary>
public static readonly DependencyProperty DocumentProperty =
DependencyProperty.RegisterAttached(
"Document",
typeof(XpsDocument), //
typeof(XpsDocumentBinding),
new UIPropertyMetadata(null, OnDocumentChanged));
/// <summary>
/// Gets the value of the <see cref="DocumentProperty">Document attached property</see> on the given <paramref name="target"/>.
/// </summary>
/// <param name="target">The <see cref="DependencyObject">target</see> on which the property is set.</param>
public static XpsDocument GetDocument(DependencyObject target)
{
return (XpsDocument)target.GetValue(DocumentProperty);
}
/// <summary>
/// Sets the <paramref name="value"/> of the <see cref="DocumentProperty">Document attached property</see> on the given <paramref name="target"/>.
/// </summary>
/// <param name="dependencyObject">The <see cref="DependencyObject">target</see> on which the property is to be set.</param>
/// <param name="value">The value to set.</param>
public static void SetDocument(DependencyObject target, XpsDocument value)
{
target.SetValue(DocumentProperty, value);
}
/// <summary>
/// Called when Document changes.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
private static void OnDocumentChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var viewer = sender as DocumentViewer;
if (viewer == null)
throw new InvalidOperationException(
"This behavior is only valid on DocumetViewers.");
var doc = e.NewValue as XpsDocument;
if (doc == null)
return;
viewer.Document = doc.GetFixedDocumentSequence();
}
#endregion
}
Then in my model I expose my visual as an XpsDocument
var pack = PackageStore.GetPackage(_uri);
if (pack != null)
return new XpsDocument(pack, CompressionOption.SuperFast, _uri.AbsoluteUri);
MemoryStream ms = new MemoryStream(2048);
Package p = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);
PackageStore.AddPackage(_uri, p);
XpsDocument doc = new XpsDocument(p, CompressionOption.SuperFast, _uri.AbsoluteUri);
var writer = XpsDocument.CreateXpsDocumentWriter(doc);
var collator = writer.CreateVisualsCollator();
// write the visuals using our collator
collator.BeginBatchWrite();
collator.Write(Visual);
collator.EndBatchWrite();
p.Flush();
return doc;
Just add a DocumentViewer, bind the result of the conversion method to it via the behavior, and there it is. I'm sure there's a shortcut in here somewhere...

Related

WPF MVVM Dependency Properties

I have the following situation:
I have a User Control with just a single Grid inside.
The Grid has its first column as a checkbox column, which is bound to the IsSelected property of CustomerModel
ItemsSource for the Grid is bound to List< CustomerModel>
When the user checks any of the CheckBoxes the corresponding IsSelected property of CustomerModel is getting updated
Query:
I added a dependency property to the UserControl named "SelectedCustomerItems", and I want it to return a List< CustomerModel> (Only for IsSelected = true)
This UserControl is placed on another Window
The Dependency Property "SelectedCustomerItems" is bound to "SelectedCustomers" property inside the WindowViewModel
But I am not getting the SelectedCustomer Items through this dependency property. Breakpoint is not hitting in Get{} Please suggest....
Implement your DPs this way:
#region SomeProperty
/// <summary>
/// The <see cref="DependencyProperty"/> for <see cref="SomeProperty"/>.
/// </summary>
public static readonly DependencyProperty SomePropertyProperty =
DependencyProperty.Register(
SomePropertyPropertyName,
typeof(object),
typeof(SomeType),
// other types here may be appropriate for your situ
new FrameworkPropertyMetadata(null, OnSomePropertyPropertyChanged));
/// <summary>
/// Called when the value of <see cref="SomePropertyProperty"/> changes on a given instance of <see cref="SomeType"/>.
/// </summary>
/// <param name="d">The instance on which the property changed.</param>
/// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
private static void OnSomePropertyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as SomeType).OnSomePropertyChanged(e.OldValue as object, e.NewValue as object);
}
/// <summary>
/// Called when <see cref="SomeProperty"/> changes.
/// </summary>
/// <param name="oldValue">The old value</param>
/// <param name="newValue">The new value</param>
private void OnSomePropertyChanged(object oldValue, object newValue)
{
}
/// <summary>
/// The name of the <see cref="SomeProperty"/> <see cref="DependencyProperty"/>.
/// </summary>
public const string SomePropertyPropertyName = "SomeProperty";
/// <summary>
///
/// </summary>
public object SomeProperty
{
get { return (object)GetValue(SomePropertyProperty); }
set { SetValue(SomePropertyProperty, value); }
}
#endregion
You must understand that a DependencyProperty isn't just a property with a bunch of junk added, its a hook into the WPF Binding system. This is a vast, complex system that lives below the sea level upon which your DP daintily floats. It behaves in ways that you will not expect, unless you actually learn it.
You are experiencing the first revelation all of us had with DPs: Bindings do NOT access DependencyProperty values via the property accessors (i.e., get and set methods). These property accessors are convenience methods for you to use from code only. You could dispense with them and use DependencyObject.GetValue and DependencyObject.SetValue, which are the actual hooks into the system (see the implementation of the getters/setters in my example above).
If you want to listen for change notification, you should do what I have done above in my example. You can add a change notification listener when registering your DependencyProperty. You can also override the "Metadata" of inherited DependencyProperties (I do this all the time for DataContext) in order to add change notification (some use the DependencyPropertyDescriptor for this, but I've found them to be lacking).
But, whatever you do, do NOT add code to the get and set methods of your DependencyProperties! They won't be executed by binding operations.
For more information about how DependencyProperties work, I highly suggest reading this great overview on MSDN.

WPF/Silverlight: Printing of Visuals - Drawbacks

I read once or twice that printing visuals from WPF/Silverlight has some drawbacks so people usually tend to use FlowDocument or FixedDocument for printing.
I want to print some graphically intense dashboards and printing the visual directly seems to be the easiest way to go. I don't have to care about pagination since every dashboard I want to print is supposed to fit on one page.
Are there still drawbacks I must consider before choosing this way of printing?
You can print Visual objects by hosting them in a FrameworkElement and then adding the FrameworkElement to a FixedDocument as content of a FixedPage. The Visual host looks like this:
/// <summary>
/// Implements a FrameworkElement host for a Visual
/// </summary>
public class VisualHost : FrameworkElement
{
Visual _visual;
/// <summary>
/// Gets the number of visual children (always 1)
/// </summary>
protected override int VisualChildrenCount
{
get { return 1; }
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="visual">The visual to host</param>
public VisualHost(Visual visual)
{
_visual = visual;
AddVisualChild(_visual);
AddLogicalChild(_visual);
}
/// <summary>
/// Get the specified visual child (always
/// </summary>
/// <param name="index">Index of visual (should always be 0)</param>
/// <returns>The visual</returns>
protected override Visual GetVisualChild(int index)
{
if (index != 0)
throw new ArgumentOutOfRangeException("index out of range");
return _visual;
}
}
Then you can add them and print them like this:
// Start the fixed document
FixedDocument fixedDoc = new FixedDocument();
Point margin = new Point(96/2, 96/2); // Half inch margins
// Add the visuals
foreach (Visual nextVisual in visualCollection)
{
// Host the visual
VisualHost host = new VisualHost(nextVisual);
Canvas canvas = new Canvas();
Canvas.SetLeft(host, margin.X);
Canvas.SetTop(host, margin.Y);
canvas.Children.Add(host);
// Make a FixedPage out of the canvas and add it to the document
PageContent pageContent = new PageContent();
FixedPage fixedPage = new FixedPage();
fixedPage.Children.Add(canvas);
((System.Windows.Markup.IAddChild)pageContent).AddChild(fixedPage);
fixedDoc.Pages.Add(pageContent);
}
// Write the finished FixedDocument to the print queue
XpsDocumentWriter xpsDocumentWriter = PrintQueue.CreateXpsDocumentWriter(queue);
xpsDocumentWriter.Write(fixedDoc);

Bindable richTextBox still hanging in memory {WPF, Caliburn.Micro}

I use in WFP Caliburn.Micro Framework.
I need bindable richTextbox for Document property. I found many ways how do it bindable richTextBox.
But I have one problem. From parent window I open child window. Child window consist bindable richTextBox user control.
After I close child window and use memory profiler view class with bindabelrichTextBox control and view model class is still hanging in memory. -> this cause memory leaks.
If I use richTextBox from .NET Framework or richTextBox from Extended WPF Toolkit it doesn’t cause this memory leak problem.
I can’t identified problem in bindable richTextBox class.
Here is ist class for bindable richTextBox:
Base class can be from .NET or Extended toolkit.
/// <summary>
/// Represents a bindable rich editing control which operates on System.Windows.Documents.FlowDocument
/// objects.
/// </summary>
public class BindableRichTextBox : RichTextBox
{
/// <summary>
/// Identifies the <see cref="Document"/> dependency property.
/// </summary>
public static readonly DependencyProperty DocumentProperty = DependencyProperty.Register("Document",
typeof(FlowDocument), typeof(BindableRichTextBox));
/// <summary>
/// Initializes a new instance of the <see cref="BindableRichTextBox"/> class.
/// </summary>
public BindableRichTextBox()
: base()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BindableRichTextBox"/> class.
/// </summary>
/// <param title="document">A <see cref="T:System.Windows.Documents.FlowDocument"></see> to be added as the initial contents of the new <see cref="T:System.Windows.Controls.BindableRichTextBox"></see>.</param>
public BindableRichTextBox(FlowDocument document)
: base(document)
{
}
/// <summary>
/// Raises the <see cref="E:System.Windows.FrameworkElement.Initialized"></see> event. This method is invoked whenever <see cref="P:System.Windows.FrameworkElement.IsInitialized"></see> is set to true internally.
/// </summary>
/// <param title="e">The <see cref="T:System.Windows.RoutedEventArgs"></see> that contains the event data.</param>
protected override void OnInitialized(EventArgs e)
{
// Hook up to get notified when DocumentProperty changes.
DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(DocumentProperty, typeof(BindableRichTextBox));
descriptor.AddValueChanged(this, delegate
{
// If the underlying value of the dependency property changes,
// update the underlying document, also.
base.Document = (FlowDocument)GetValue(DocumentProperty);
});
// By default, we support updates to the source when focus is lost (or, if the LostFocus
// trigger is specified explicity. We don't support the PropertyChanged trigger right now.
this.LostFocus += new RoutedEventHandler(BindableRichTextBox_LostFocus);
base.OnInitialized(e);
}
/// <summary>
/// Handles the LostFocus event of the BindableRichTextBox control.
/// </summary>
/// <param title="sender">The source of the event.</param>
/// <param title="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
void BindableRichTextBox_LostFocus(object sender, RoutedEventArgs e)
{
// If we have a binding that is set for LostFocus or Default (which we are specifying as default)
// then update the source.
Binding binding = BindingOperations.GetBinding(this, DocumentProperty);
if (binding.UpdateSourceTrigger == UpdateSourceTrigger.Default ||
binding.UpdateSourceTrigger == UpdateSourceTrigger.LostFocus)
{
BindingOperations.GetBindingExpression(this, DocumentProperty).UpdateSource();
}
}
/// <summary>
/// Gets or sets the <see cref="T:System.Windows.Documents.FlowDocument"></see> that represents the contents of the <see cref="T:System.Windows.Controls.BindableRichTextBox"></see>.
/// </summary>
/// <value></value>
/// <returns>A <see cref="T:System.Windows.Documents.FlowDocument"></see> object that represents the contents of the <see cref="T:System.Windows.Controls.BindableRichTextBox"></see>.By default, this property is set to an empty <see cref="T:System.Windows.Documents.FlowDocument"></see>. Specifically, the empty <see cref="T:System.Windows.Documents.FlowDocument"></see> contains a single <see cref="T:System.Windows.Documents.Paragraph"></see>, which contains a single <see cref="T:System.Windows.Documents.Run"></see> which contains no text.</returns>
/// <exception cref="T:System.ArgumentException">Raised if an attempt is made to set this property to a <see cref="T:System.Windows.Documents.FlowDocument"></see> that represents the contents of another <see cref="T:System.Windows.Controls.RichTextBox"></see>.</exception>
/// <exception cref="T:System.ArgumentNullException">Raised if an attempt is made to set this property to null.</exception>
/// <exception cref="T:System.InvalidOperationException">Raised if this property is set while a change block has been activated.</exception>
public new FlowDocument Document
{
get { return (FlowDocument)GetValue(DocumentProperty); }
set { SetValue(DocumentProperty, value); }
}
}
Thank fro help and advice.
Qucik example:
Child window with .NET richTextBox
<Window x:Class="WpfApplication2.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<RichTextBox Background="Green"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
FontSize="13"
Margin="4,4,4,4"
Grid.Row="0"/>
</Grid>
</Window>
This window I open from parent window:
var w = new Window1();
w.Show();
Then close this window, check with memory profiler and it memory doesn’t exist any object of window1 - richTextBox. It’s Ok.
But then I try bindable richTextBox:
Child window 2:
<Window x:Class="WpfApplication2.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:WpfApplication2.Controls"
Title="Window2" Height="300" Width="300">
<Grid>
<Controls:BindableRichTextBox Background="Red"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
FontSize="13"
Margin="4,4,4,4"
Grid.Row="0" />
</Grid>
</Window>
Open child window 2, close this child window and in memory are still alive object of this child window also bindable richTextBox object.
I suspect that since instances of DependencyPropertyDescriptor are likely to be cached at application level, the references to ValueChanged delegate (the anonymous delegate in OnInitialized method) might leak instances of the BindableRichTextBox.
In the code shown there isn't, indeed, any call to DependencyPropertyDescriptor.RemoveValueChanged to remove the handler.
You might consider using the DependencyProperty.Register overload, which supports a PropertyMetadata parameter; this allows you to specify a proper PropertyChangedCallback for the property (see http://msdn.microsoft.com/en-us/library/cc903933(v=VS.95).aspx#metadata):
public static readonly DependencyProperty DocumentProperty =
DependencyProperty.Register("Document",
typeof(FlowDocument), typeof(BindableRichTextBox),
new PropertyMetadata(null,
new PropertyChangedCallback(OnDocumentChanged)
)
);
public static void OnDocumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((BindableRichTextBox)d).SetBaseDocument((FlowDocument)e.NewValue);
}
public new FlowDocument Document
{
get { return (FlowDocument)GetValue(DocumentProperty); }
set { SetValue(DocumentProperty, value); }
}
private SetBaseDocument(FlowDocument document) {
base.Document = (FlowDocument)GetValue(DocumentProperty);
}
Maybe is better to create user control with richTextBox.
Richtextbox wpf binding

WindowsFormsHost and DependencyProperty

I've got a Windows Forms control that I'm attempting to wrap as a WPF control using the WindowsFormsHost class; I would like to bind the legacy control to a view-model. Specifically, the control exposes a grid property, GridVisible, that I would like to bind a view-model to. I'm using a private, static backing field and a static, read-only property to represent the dependency property (functionally the same as a static, public field, but less mess). When I attempt to set the control's GridVisible property via XAML, it's not updating. Ideas? What am I doing incorrectly?
DrawingHost Class
/// <summary>
/// Provides encapsulation of a drawing control.
/// </summary>
public class DrawingHost : WindowsFormsHost
{
#region Data Members
/// <summary>
/// Holds the disposal flag.
/// </summary>
private bool disposed;
/// <summary>
/// Holds the grid visible property.
/// </summary>
private static readonly DependencyProperty gridVisibleProperty =
DependencyProperty.Register("GridVisible", typeof(bool),
typeof(DrawingHost), new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
/// <summary>
/// Holds the pad.
/// </summary>
private readonly DrawingPad pad = new DrawingPad();
#endregion
#region Properties
/// <summary>
/// Get or set whether the grid is visible.
/// </summary>
public bool GridVisible
{
get { return (bool)GetValue(GridVisibleProperty); }
set { SetValue(GridVisibleProperty, pad.GridVisible = value); }
}
/// <summary>
/// Get the grid visible property.
/// </summary>
public static DependencyProperty GridVisibleProperty
{
get { return gridVisibleProperty; }
}
#endregion
/// <summary>
/// Default-construct a drawing host.
/// </summary>
public DrawingHost()
{
this.Child = this.pad;
}
/// <summary>
/// Dispose of the drawing host.
/// </summary>
/// <param name="disposing">The disposal invocation flag.</param>
protected override void Dispose(bool disposing)
{
if (disposing && !disposed)
{
if (pad != null)
{
pad.Dispose();
}
disposed = true;
}
base.Dispose(disposing);
}
}
XAML
<UserControl x:Class="Drawing.DrawingView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:local="clr-namespace:Drawing">
<local:DrawingHost GridVisible="True"/></UserControl>
One of the first rules of dependency properties is to never include any logic in the get and set except the GetValue and SetValue calls. This is because when they are used in XAML, they do not actually go through the get and set accessors. They are inlined with the GetValue and SetValue calls. So none of your code will be executed.
The appropriate way to do this is set up a call-back using the PropertyMetadata parameter in the DependencyProperty.Register method. Then in the call-back you can then execute any extra code.

Keyboard events in a WPF MVVM application?

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.

Resources