WPF Data Binding with StringFormat when UpdateSourceTrigger is PropertyChanged - wpf

I want to have a textbox append a specific string once the user tabs out of the control, i.e. LostFocus, however I prefer the textbox to validate as the user types, so UpdateSourceTrigger is set to PropertyChanged.
Is there a way to get this to work in WPF?
Looked at this question which is similar but wondering if there is a cleaner solution?
My XAML is:
<TextBox Text="{Binding Path=MyBindingPath,
StringFormat='\{0} -HELLO',
TargetNullValue={x:Static sys:String.Empty},
ValidatesOnDataErrors=True,
NotifyOnValidationError=True,
UpdateSourceTrigger=PropertyChanged}"/>

You can set UpdateSourceTrigger to Explicit and in the TextChanged event handler of the TextBox you can call UpdateSource explicitly after performing the things you want.
//write the code you want to run first and then the following code
BindingExpression exp = this.textBox1.GetBindingExpression(TextBox.TextProperty);
exp.UpdateSource();

This seems to be the cleanest approach I have come up with.
This essentially sets events that will turn off the string formatting when the user has focus on the textbox, and restores the stringformat when the user leaves the textbox.
<TextBox Text="{Binding ValueToBind, UpdateSourceTrigger=PropertyChanged}" utilities:Formatting.StringFormat="N2" />
public class Formatting
{
public static readonly DependencyProperty StringFormatProperty = DependencyProperty.RegisterAttached("StringFormat", typeof(string), typeof(Formatting), new PropertyMetadata("", OnStringFormatPropertyChanged));
private static void OnStringFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is FrameworkElement element && e.OldValue != e.NewValue)
{
element.Loaded += (sender, args) =>
{
// Debug.Print("Loaded");
UpdateStringFormat(element.IsFocused ? null : (string)e.NewValue, element);
};
element.LostFocus += (sender, args) =>
{
// Debug.Print("Lost Focus");
UpdateStringFormat((string)e.NewValue, element);
};
element.GotFocus += (sender, args) =>
{
// Debug.Print("Got Focus");
UpdateStringFormat(null, element);
};
}
}
private static void UpdateStringFormat(string stringFormat, FrameworkElement element)
{
var bindingExpression = element.GetBindingExpression(TextBox.TextProperty);
if (bindingExpression != null && bindingExpression.ParentBinding != null)
{
Binding parentBinding = bindingExpression.ParentBinding;
if (parentBinding.StringFormat == stringFormat)
return;
// Debug.Print("Updating String Format");
bindingExpression.UpdateSource();
Binding newBinding = new Binding
{
Path = parentBinding.Path,
Mode = parentBinding.Mode,
UpdateSourceTrigger = parentBinding.UpdateSourceTrigger,
StringFormat = stringFormat
};
element.SetBinding(TextBox.TextProperty, newBinding);
}
}
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static void SetStringFormat(DependencyObject obj, string stringFormat)
{
obj.SetValue(StringFormatProperty, stringFormat);
}
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static string GetStringFormat(DependencyObject obj)
{
return (string)obj.GetValue(StringFormatProperty);
}
}

Related

Databinding TextBlock Runs in Silverlight / WP7

I'm using Silverlight on Windows Phone 7.
I want to display the first part of some text in a TextBlock in bold, and the rest in normal font. The complete text must wrap. I want the bolded part to contain text from one property in my ViewModel, and the plain text to contain text from a different property.
The TextBlock is defined in a DataTemplate associated with a LongListSelector.
My initial attempt was:
<TextBlock TextWrapping="Wrap">
<TextBlock.Inlines>
<Run Text="{Binding Property1}" FontWeight="Bold"/>
<Run Text="{Binding Property2}"/>
</TextBlock.Inlines>
</TextBlock>
This fails at runtime with the spectacularly unhelpful "AG_E_RUNTIME_MANAGED_UNKNOWN_ERROR". This is a known issue because the Run element is not a FrameworkElement and cannot be bound.
My next attempt was to put placeholders in place, and then update them in code:
<TextBlock Loaded="TextBlockLoaded" TextWrapping="Wrap">
<TextBlock.Inlines>
<Run FontWeight="Bold">Placeholder1</Run>
<Run>Placeholder2</Run>
</TextBlock.Inlines>
</TextBlock>
In the code-behind (yes I am desparate!):
private void TextBlockLoaded(object sender, RoutedEventArgs e)
{
var textBlock = (TextBlock)sender;
var viewModel = (ViewModel)textBlock.DataContext;
var prop1Run = (Run)textBlock.Inlines[0];
var prop2Run = (Run)textBlock.Inlines[1];
prop1Run.Text = viewModel.Property1;
prop2Run.Text = viewModel.Property2;
}
This seemed to work, but because I am using the LongListSelector, although items get recycled, the Loaded codebehind event handler doesn't re-initialize the Runs, so very quickly the wrong text is displayed...
I've looked at using the LongListSelector's Linked event (which I already use to free up images that I display in the list), but I can't see how I can use that to re-initialize the Runs' text properties.
Any help appreciated!
I finally found a solution that works for me.
As I mention in the comment, Paul Stovell's approach would not work.
Instead I used a similar approach to add an attached property to the TextBlock, bound to the TextBlock's DataContext, and attached properties on the runs, indicating which ViewModel properties they should be bound to:
<TextBlock TextWrapping="Wrap"
Views:BindableRuns.Target="{Binding}">
<TextBlock.Inlines>
<Run FontWeight="Bold" Views:BindableRuns.Target="Property1"/>
<Run Views:BindableRuns.Target="Property2"/>
</TextBlock.Inlines>
</TextBlock>
Then in my attached TextBox Target (datacontext) property's changed event, I update the Runs, and subscribe to be notified of changes to the TextBox Target properties. When a TextBox Target property changes, I updated any associated Run's text accordingly.
public static class BindableRuns
{
private static readonly Dictionary<INotifyPropertyChanged, PropertyChangedHandler>
Handlers = new Dictionary<INotifyPropertyChanged, PropertyChangedHandler>();
private static void TargetPropertyPropertyChanged(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
if(!(dependencyObject is TextBlock)) return;
var textBlock = (TextBlock)dependencyObject;
AddHandler(e.NewValue as INotifyPropertyChanged, textBlock);
RemoveHandler(e.OldValue as INotifyPropertyChanged);
InitializeRuns(textBlock, e.NewValue);
}
private static void AddHandler(INotifyPropertyChanged dataContext,
TextBlock textBlock)
{
if (dataContext == null) return;
var propertyChangedHandler = new PropertyChangedHandler(textBlock);
dataContext.PropertyChanged += propertyChangedHandler.PropertyChanged;
Handlers[dataContext] = propertyChangedHandler;
}
private static void RemoveHandler(INotifyPropertyChanged dataContext)
{
if (dataContext == null || !Handlers.ContainsKey(dataContext)) return;
dataContext.PropertyChanged -= Handlers[dataContext].PropertyChanged;
Handlers.Remove(dataContext);
}
private static void InitializeRuns(TextBlock textBlock, object dataContext)
{
if (dataContext == null) return;
var runs = from run in textBlock.Inlines.OfType<Run>()
let propertyName = (string)run.GetValue(TargetProperty)
where propertyName != null
select new { Run = run, PropertyName = propertyName };
foreach (var run in runs)
{
var property = dataContext.GetType().GetProperty(run.PropertyName);
run.Run.Text = (string)property.GetValue(dataContext, null);
}
}
private class PropertyChangedHandler
{
private readonly TextBlock _textBlock;
public PropertyChangedHandler(TextBlock textBlock)
{
_textBlock = textBlock;
}
public void PropertyChanged(object sender,
PropertyChangedEventArgs propertyChangedArgs)
{
var propertyName = propertyChangedArgs.PropertyName;
var run = _textBlock.Inlines.OfType<Run>()
.Where(r => (string) r.GetValue(TargetProperty) == propertyName)
.SingleOrDefault();
if(run == null) return;
var property = sender.GetType().GetProperty(propertyName);
run.Text = (string)property.GetValue(sender, null);
}
}
public static object GetTarget(DependencyObject obj)
{
return obj.GetValue(TargetProperty);
}
public static void SetTarget(DependencyObject obj,
object value)
{
obj.SetValue(TargetProperty, value);
}
public static readonly DependencyProperty TargetProperty =
DependencyProperty.RegisterAttached("Target",
typeof(object),
typeof(BindableRuns),
new PropertyMetadata(null,
TargetPropertyPropertyChanged));
}
I suggest you give the BindableRun a try. I've only used it in WPF, but I don't see why it wouldn't work in Silverlight.

Invoke Command When "ENTER" Key Is Pressed In XAML

I want to invoke a command when ENTER is pressed in a TextBox. Consider the following XAML:
<UserControl
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
...>
...
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<i:InvokeCommandAction Command="{Binding MyCommand}"
CommandParameter="{Binding Text}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
...
</UserControl>
and that MyCommand is as follows:
public ICommand MyCommand {
get { return new DelegateCommand<string>(MyCommandExecute); }
}
private void MyCommandExecute(string s) { ... }
With the above, my command is invoked for every key press. How can I restrict the command to only invoke when the ENTER key is pressed?
I understand that with Expression Blend I can use Conditions but those seem to be restricted to elements and can't consider event arguments.
I have also come across SLEX which offers its own InvokeCommandAction implementation that is built on top of the Systems.Windows.Interactivity implementation and can do what I need. Another consideration is to write my own trigger, but I'm hoping there's a way to do it without using external toolkits.
There is KeyTrigger in expression blend.
<UserControl
xmlns:i="clr-namespace:System.Windows.Interactivity;
assembly=System.Windows.Interactivity"
xmlns:iex="clr-namespace:Microsoft.Expression.Interactivity.Input;
assembly=Microsoft.Expression.Interactions" ...>
<TextBox>
<i:Interaction.Triggers>
<iex:KeyTrigger Key="Enter">
<i:InvokeCommandAction Command="{Binding PasswordLoginCommand}" />
</iex:KeyTrigger>
</i:Interaction.Triggers>
</TextBox>
</UserControl>
System.Windows.Interactivity and Microsoft.Expression.Interactions assemblies are available for WPF in the official Nuget package.
I like scottrudy's approach (to which I've given a +1) with the custom triggers approach as it stays true to my initial approach. I'm including a modified version of it below to use dependency properties instead of reflection info so that it's possible to bind directly to the ICommand. I'm also including an approach using attached properties to avoid using System.Windows.Interactivity if desired. The caveat to the latter approach is that you lose the feature of multiple invokations from an event, but you can apply it more generally.
Custom Triggers Approach
ExecuteCommandAction.cs
public class ExecuteCommandAction : TriggerAction<DependencyObject> {
#region Properties
public ICommand Command {
get { return (ICommand)base.GetValue(CommandProperty); }
set { base.SetValue(CommandProperty, value); }
}
public static ICommand GetCommand(DependencyObject obj) {
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value) {
obj.SetValue(CommandProperty, value);
}
// We use a DependencyProperty so we can bind commands directly rather
// than have to use reflection info to find them
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(ExecuteCommandAction), null);
#endregion Properties
protected override void Invoke(object parameter) {
ICommand command = Command ?? GetCommand(AssociatedObject);
if (command != null && command.CanExecute(parameter)) {
command.Execute(parameter);
}
}
}
TextBoxEnterKeyTrigger.cs
public class TextBoxEnterKeyTrigger : TriggerBase<UIElement> {
protected override void OnAttached() {
base.OnAttached();
TextBox textBox = this.AssociatedObject as TextBox;
if (textBox != null) {
this.AssociatedObject.KeyUp += new System.Windows.Input.KeyEventHandler(AssociatedObject_KeyUp);
}
else {
throw new InvalidOperationException("This behavior only works with TextBoxes");
}
}
protected override void OnDetaching() {
base.OnDetaching();
AssociatedObject.KeyUp -= new KeyEventHandler(AssociatedObject_KeyUp);
}
private void AssociatedObject_KeyUp(object sender, KeyEventArgs e) {
if (e.Key == Key.Enter) {
TextBox textBox = AssociatedObject as TextBox;
//This checks for an mvvm style binding and updates the source before invoking the actions.
BindingExpression expression = textBox.GetBindingExpression(TextBox.TextProperty);
if (expression != null)
expression.UpdateSource();
InvokeActions(textBox.Text);
}
}
}
MyUserControl.xaml
<UserControl
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:b="clr-namespace:MyNameSpace.Interactivity"
...
<TextBox>
<i:Interaction.Triggers>
<b:TextBoxEnterKeyTrigger>
<b:ExecuteCommandAction Command="{Binding MyCommand}" />
</b:TextBoxEnterKeyTrigger>
</i:Interaction.Triggers>
</TextBox>
...
</UserControl>
Attached Properties Approach
EnterKeyDown.cs
public sealed class EnterKeyDown {
#region Properties
#region Command
public static ICommand GetCommand(DependencyObject obj) {
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value) {
obj.SetValue(CommandProperty, value);
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(EnterKeyDown),
new PropertyMetadata(null, OnCommandChanged));
#endregion Command
#region CommandArgument
public static object GetCommandArgument(DependencyObject obj) {
return (object)obj.GetValue(CommandArgumentProperty);
}
public static void SetCommandArgument(DependencyObject obj, object value) {
obj.SetValue(CommandArgumentProperty, value);
}
public static readonly DependencyProperty CommandArgumentProperty =
DependencyProperty.RegisterAttached("CommandArgument", typeof(object), typeof(EnterKeyDown),
new PropertyMetadata(null, OnCommandArgumentChanged));
#endregion CommandArgument
#region HasCommandArgument
private static bool GetHasCommandArgument(DependencyObject obj) {
return (bool)obj.GetValue(HasCommandArgumentProperty);
}
private static void SetHasCommandArgument(DependencyObject obj, bool value) {
obj.SetValue(HasCommandArgumentProperty, value);
}
private static readonly DependencyProperty HasCommandArgumentProperty =
DependencyProperty.RegisterAttached("HasCommandArgument", typeof(bool), typeof(EnterKeyDown),
new PropertyMetadata(false));
#endregion HasCommandArgument
#endregion Propreties
#region Event Handling
private static void OnCommandArgumentChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
SetHasCommandArgument(o, true);
}
private static void OnCommandChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
FrameworkElement element = o as FrameworkElement;
if (element != null) {
if (e.NewValue == null) {
element.KeyDown -= new KeyEventHandler(FrameworkElement_KeyDown);
}
else if (e.OldValue == null) {
element.KeyDown += new KeyEventHandler(FrameworkElement_KeyDown);
}
}
}
private static void FrameworkElement_KeyDown(object sender, KeyEventArgs e) {
if (e.Key == Key.Enter) {
DependencyObject o = sender as DependencyObject;
ICommand command = GetCommand(sender as DependencyObject);
FrameworkElement element = e.OriginalSource as FrameworkElement;
if (element != null) {
// If the command argument has been explicitly set (even to NULL)
if (GetHasCommandArgument(o)) {
object commandArgument = GetCommandArgument(o);
// Execute the command
if (command.CanExecute(commandArgument)) {
command.Execute(commandArgument);
}
}
else if (command.CanExecute(element.DataContext)) {
command.Execute(element.DataContext);
}
}
}
}
#endregion
}
MyUserControl.xaml
<UserControl
...
xmlns:b="clr-namespace:MyNameSpace.Interactivity"
...
<TextBox b:EnterKeyDown.Command="{Binding AddNewDetailCommand}"
b:EnterKeyDown.CommandArgument="{Binding Path=Text,RelativeSource={RelativeSource Self}}" />
...
</UserControl>
I ran into this same issue yesterday and solved it using custom triggers. It may seem a bit much at first, but I found this general pattern is usable for doing a lot of the things that I used to accomplish using event handlers directly in a view (like double click events). The first step is to create a trigger action that can accept a parameter since we will need it later.
public class ExecuteCommandAction : TriggerAction<FrameworkElement>
{
public string Command { get; set; }
protected override void Invoke(object o)
{
if (Command != null)
{
object ctx = AssociatedObject.DataContext;
if (ctx != null)
{
var cmd = ctx.GetType().GetProperty(Command)
.GetValue(ctx, null) as ICommand;
if (cmd != null && cmd.CanExecute(o))
{
cmd.Execute(o);
}
}
}
}
}
The next step is to create the trigger. You could do some interesting things with base classes to make it more generic for capturing different types of key presses, but we'll keep it simple.
public class TextBoxEnterKeyTrigger: TriggerBase<UIElement>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.KeyUp += AssociatedObject_KeyUp;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.KeyUp -= AssociatedObject_KeyUp;
}
void AssociatedObject_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
TextBox textBox = AssociatedObject as TextBox;
object o = textBox == null ? null : textBox.Text;
if (o != null)
{
InvokeActions(o);
}
}
}
}
Keep in mind that even though you may have a data binding in place to your TextBox value, the property changed event won't fire because your textbox hasn't lost focus. For this reason I am passing the value of the TextBox.Text property to the command. The last step is to use this feature in your XAML. You need to be sure to include the Interactivity namespace as well as the namespace that contains your code from above.
<UserControl
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:common="clr-namespace:My.UI;assembly=My.UI">
<TextBox Text="{Binding Path=MyText, Mode=TwoWay}" IsEnabled="{Binding CanMyCommand}">
<i:Interaction.Triggers>
<common:TextBoxEnterKeyTrigger>
<common:ExecuteCommandAction Command=MyCommand" />
</common:TextBoxEnterKeyTrigger>
</i:Interaction.Triggers>
</TextBox>
</UserControl>
I used scottrudy's code in my application however, my textbox text is bound to some property in viewmodel class and this property is not getting updated by the time command is invoked after pressiong ENTER key because my textbox hasn't lost focus yet. So, to resolved this, i added the following code snippets just above InvokeActions(o) in AssociatedObject_KeyUp method and updated text property is getting updated in viewmodel class.
BindingExpression bindingExpression = (textBox).GetBindingExpression(TextBox.TextProperty);
bindingExpression.UpdateSource();
On top of my mind.. You can pass event args to command and than in ViewModel check if e.KeyPress = Keys.Enter.. this is not really code :) i dont have my VS on this computer.. this is rather an idea :)

WPF Datagrid: MVVM friendly way to bind SelectedCells to my ViewModel

I'm using the WPF datagrid, and have SelectionUnit="Cell" and SelectionMode="Extended". I'm also trying to adhere to the MVVM principals as much as I can.
I need my ViewModel to keep track of the current SelectedCells.
Life would be easy if I could just Bind its SelectedCells property to my ViewModel. Oddly enough, SelectedCells is only raised once - when we first select any cell in the grid.
MS explains it here: http://social.msdn.microsoft.com/Forums/en/wpf/thread/737117f4-6d20-4232-88cf-e52cc44d4431
Can anyone think of an MVVM-friendly approach to get around it?
Thanks!
I realized my last answer was for SelectedItems instead of SelectedCells, so I wrote a complete attached property class to do data binding for multiple SelectedCells which works as follows:
<controls:DataGrid ItemsSource="{StaticResource list}"
SelectionMode="Extended"
behaviors:DataGridSelectedCellsBehavior.SelectedCells="{Binding Path=SelectedGridCellCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
I have a working source code and a demo project of it here.
Attached property behavior Code :
public class DataGridSelectedCellsBehavior
{
// Source : https://archive.codeplex.com/?p=datagridthemesfromsl
// Credit to : T. Webster, https://stackoverflow.com/users/266457/t-webster
public static IList<DataGridCellInfo> GetSelectedCells(DependencyObject obj)
{
return (IList<DataGridCellInfo>)obj.GetValue(SelectedCellsProperty);
}
public static void SetSelectedCells(DependencyObject obj, IList<DataGridCellInfo> value)
{
obj.SetValue(SelectedCellsProperty, value);
}
public static readonly DependencyProperty SelectedCellsProperty = DependencyProperty.RegisterAttached("SelectedCells", typeof(IList<DataGridCellInfo>), typeof(DataGridSelectedCellsBehavior), new UIPropertyMetadata(null, OnSelectedCellsChanged));
static SelectedCellsChangedEventHandler GetSelectionChangedHandler(DependencyObject obj)
{
return (SelectedCellsChangedEventHandler)obj.GetValue(SelectionChangedHandlerProperty);
}
static void SetSelectionChangedHandler(DependencyObject obj, SelectedCellsChangedEventHandler value)
{
obj.SetValue(SelectionChangedHandlerProperty, value);
}
static readonly DependencyProperty SelectionChangedHandlerProperty = DependencyProperty.RegisterAttached("SelectedCellsChangedEventHandler", typeof(SelectedCellsChangedEventHandler), typeof(DataGridSelectedCellsBehavior), new UIPropertyMetadata(null));
//d is MultiSelector (d as ListBox not supported)
static void OnSelectedCellsChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
if (GetSelectionChangedHandler(d) != null)
return;
if (d is DataGrid)//DataGrid
{
DataGrid datagrid = d as DataGrid;
SelectedCellsChangedEventHandler selectionchanged = null;
foreach (var selected in GetSelectedCells(d) as IList<DataGridCellInfo>)
datagrid.SelectedCells.Add(selected);
selectionchanged = (sender, e) =>
{
SetSelectedCells(d, datagrid.SelectedCells);
};
SetSelectionChangedHandler(d, selectionchanged);
datagrid.SelectedCellsChanged += GetSelectionChangedHandler(d);
}
//else if (d is ListBox)
//{
// ListBox listbox = d as ListBox;
// SelectionChangedEventHandler selectionchanged = null;
// selectionchanged = (sender, e) =>
// {
// SetSelectedCells(d, listbox.SelectedCells);
// };
// SetSelectionChangedHandler(d, selectionchanged);
// listbox.SelectionChanged += GetSelectionChangedHandler(d);
//}
}
}
View Model Code :
class DemoViewModel : INotifyPropertyChanged
{
private IList<DataGridCellInfo> selectedGridCellCollection = new List<DataGridCellInfo>();
public IList<DataGridCellInfo> SelectedGridCellCollection
{
get { return selectedGridCellCollection; }
set
{
selectedGridCellCollection = value;
NotifyPropertyChanged();
}
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Do you need the SelectedCells constantly data-binded, or just when the user hits the OK/Accept button? If you only need it at the end of whatever process the user is in you can bind the SelectedCells to the CommandParameter property of a Button, for example. The SelectedCells is an IList, and you know enough to just do a cast to whatever object type the selection actually is. The other option is messier, you can use an attached property, keeping the event-handling out of your Views. This attached property would handle either a ListBox or in your case a DataGrid (MultiSelector).
public class Attach
{
public static IList GetSelectedItems(DependencyObject obj)
{
return (IList)obj.GetValue(SelectedItemsProperty);
}
public static void SetSelectedItems(DependencyObject obj, IList value)
{
obj.SetValue(SelectedItemsProperty, value);
}
/// <summary>
/// Attach this property to expose the read-only SelectedItems property of a MultiSelector for data binding.
/// </summary>
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.RegisterAttached("SelectedItems", typeof(IList), typeof(Attach), new UIPropertyMetadata(new List<object>() as IList, OnSelectedItemsChanged));
static SelectionChangedEventHandler GetSelectionChangedHandler(DependencyObject obj)
{
return (SelectionChangedEventHandler)obj.GetValue(SelectionChangedHandlerProperty);
}
static void SetSelectionChangedHandler(DependencyObject obj, SelectionChangedEventHandler value)
{
obj.SetValue(SelectionChangedHandlerProperty, value);
}
static readonly DependencyProperty SelectionChangedHandlerProperty =
DependencyProperty.RegisterAttached("SelectionChangedHandler", typeof(SelectionChangedEventHandler), typeof(Attach), new UIPropertyMetadata(null));
//d is MultiSelector (d as ListBox not supported)
static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
if (GetSelectionChangedHandler(d) != null)
return;
if (d is MultiSelector)//DataGrid
{
MultiSelector multiselector = d as MultiSelector;
SelectionChangedEventHandler selectionchanged = null;
foreach (var selected in GetSelectedItems(d) as IList)
multiselector.SelectedItems.Add(selected);
selectionchanged = (sender, e) =>
{
SetSelectedItems(d, multiselector.SelectedItems);
};
SetSelectionChangedHandler(d, selectionchanged);
multiselector.SelectionChanged += GetSelectionChangedHandler(d);
}
else if (d is ListBox)
{
ListBox listbox = d as ListBox;
SelectionChangedEventHandler selectionchanged = null;
selectionchanged = (sender, e) =>
{
SetSelectedItems(d, listbox.SelectedItems);
};
SetSelectionChangedHandler(d, selectionchanged);
listbox.SelectionChanged += GetSelectionChangedHandler(d);
}}}
Usage in XAML:
<DataGrid ItemsSource="{Binding Path=SourceList}"
myControls:Attach.SelectedItems="{Binding Path=myMvvmSelectedItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectionMode="Extended" />
You might be interested in the BookLibrary sample application of the WPF Application Framework (WAF). It shows how to synchronize the DataGrid.SelectedItems with the ViewModel. This might be very similar to SelectedCells.
Somewhere between perfect MVVM bindings and full event handler codebehinds there is the gray area of interactivity EventTriggers (see Blend SDK) :)
If you put an eventtrigger to the datagrid, and set to "SelectionChanged" and pass the eventargs to a command (use an EventToCommand actiontrigger) you could get the selected items from the eventargs hopefully...
Or use the multibinding as said in the MS thread :)

MVVM- How can I bind to a property, which is not a DependancyProperty?

I have found this question MVVM and the TextBox's SelectedText property. However, I am having trouble getting the solution given to work. This is my non-working code, in which I am trying to display the first textbox's selected text in the second textbox.
View:
SelectedText and Text are just string properties from my ViewModel.
<TextBox Text="{Binding Path=Text, UpdateSourceTrigger=PropertyChanged}" Height="155" HorizontalAlignment="Left" Margin="68,31,0,0" Name="textBox1" VerticalAlignment="Top" Width="264" AcceptsReturn="True" AcceptsTab="True" local:TextBoxHelper.SelectedText="{Binding SelectedText, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}" />
<TextBox Text="{Binding SelectedText, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" Height="154" HorizontalAlignment="Left" Margin="82,287,0,0" Name="textBox2" VerticalAlignment="Top" Width="239" />
TextBoxHelper
public static class TextBoxHelper
{
#region "Selected Text"
public static string GetSelectedText(DependencyObject obj)
{
return (string)obj.GetValue(SelectedTextProperty);
}
public static void SetSelectedText(DependencyObject obj, string value)
{
obj.SetValue(SelectedTextProperty, value);
}
// Using a DependencyProperty as the backing store for SelectedText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedTextProperty =
DependencyProperty.RegisterAttached(
"SelectedText",
typeof(string),
typeof(TextBoxHelper),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedTextChanged));
private static void SelectedTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
TextBox tb = obj as TextBox;
if (tb != null)
{
if (e.OldValue == null && e.NewValue != null)
{
tb.SelectionChanged += tb_SelectionChanged;
}
else if (e.OldValue != null && e.NewValue == null)
{
tb.SelectionChanged -= tb_SelectionChanged;
}
string newValue = e.NewValue as string;
if (newValue != null && newValue != tb.SelectedText)
{
tb.SelectedText = newValue as string;
}
}
}
static void tb_SelectionChanged(object sender, RoutedEventArgs e)
{
TextBox tb = sender as TextBox;
if (tb != null)
{
SetSelectedText(tb, tb.SelectedText);
}
}
#endregion
}
What am I doing wrong?
The reason this is not working is that the property change callback isn't being raised (as the bound value from your VM is the same as the default value specified in the metadata for the property). More fundamentally though, your behavior will detach when the selected text is set to null. In cases like this, I tend to have another attached property that is simply used to enable the monitoring of the selected text, and then the SelectedText property can be bound. So, something like so:
#region IsSelectionMonitored
public static readonly DependencyProperty IsSelectionMonitoredProperty = DependencyProperty.RegisterAttached(
"IsSelectionMonitored",
typeof(bool),
typeof(PinnedInstrumentsViewModel),
new FrameworkPropertyMetadata(OnIsSelectionMonitoredChanged));
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static bool GetIsSelectionMonitored(TextBox d)
{
return (bool)d.GetValue(IsSelectionMonitoredProperty);
}
public static void SetIsSelectionMonitored(TextBox d, bool value)
{
d.SetValue(IsSelectionMonitoredProperty, value);
}
private static void OnIsSelectionMonitoredChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
TextBox tb = obj as TextBox;
if (tb != null)
{
if ((bool)e.NewValue)
{
tb.SelectionChanged += tb_SelectionChanged;
}
else
{
tb.SelectionChanged -= tb_SelectionChanged;
}
SetSelectedText(tb, tb.SelectedText);
}
}
#endregion
#region "Selected Text"
public static string GetSelectedText(DependencyObject obj)
{
return (string)obj.GetValue(SelectedTextProperty);
}
public static void SetSelectedText(DependencyObject obj, string value)
{
obj.SetValue(SelectedTextProperty, value);
}
// Using a DependencyProperty as the backing store for SelectedText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedTextProperty =
DependencyProperty.RegisterAttached(
"SelectedText",
typeof(string),
typeof(TextBoxHelper),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedTextChanged));
private static void SelectedTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
TextBox tb = obj as TextBox;
if (tb != null)
{
tb.SelectedText = e.NewValue as string;
}
}
static void tb_SelectionChanged(object sender, RoutedEventArgs e)
{
TextBox tb = sender as TextBox;
if (tb != null)
{
SetSelectedText(tb, tb.SelectedText);
}
}
#endregion
And then in your XAML, you'd have to add that property to your first TextBox:
<TextBox ... local:TextBoxHelper.IsSelectionMonitored="True" local:TextBoxHelper.SelectedText="{Binding SelectedText, Mode=OneWayToSource}" />
In order for the SelectedTextChanged handler to fire the SelectedText property must have an initial value. If you don't initialize this to some value (string.Empty as a bare minimum) then this handler will never fire and in turn you'll never register the tb_SelectionChanged handler.
This works for me using the class TextBoxHelper. As other mentioned, you need to initialize the SelectedText property of TextBoxHelper with a non null value. Instead of data binding to a string property (SelText) on the view you should bind to a string property of your VM which should implement INotifyPropertyChanged.
XAML:
<Window x:Class="TextSelectDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TextSelectDemo"
Height="300" Width="300">
<StackPanel>
<TextBox local:TextBoxHelper.SelectedText="{Binding Path=SelText, Mode=TwoWay}" />
<TextBox Text="{Binding Path=SelText}" />
</StackPanel>
</Window>
Code behind:
using System.ComponentModel;
using System.Windows;
namespace TextSelectDemo
{
public partial class Window1 : Window, INotifyPropertyChanged
{
public Window1()
{
InitializeComponent();
SelText = string.Empty;
DataContext = this;
}
private string _selText;
public string SelText
{
get { return _selText; }
set
{
_selText = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelText"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Your binding attempts to bind the Text property of your TextBox to a SelectedText property of the TextBox's current data context. Since you're working with an attached property, not with a property hanging off of your data context, you will need to give more information in your binding:
<TextBox Text="{Binding local:TextBoxHelper.SelectedText, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" ... />
Where local has been associated with the CLR namespace containing the TextBoxHelper class.
You need a normal .net property wrapper for the dependencyproperty, some like:
public string SelectedText
{
set {SetSelectedText(this, value);}
...
It is not required by runtime (runtime use set/get) but it is required by designer and compiler.

WPF textbox that only updates binding when enter is pressed

all. I have a usercontrol "NumericTextBox" that only allows numeric entries. I need to exhibit another specialized behaviour, that is, I need it to be able to bind it to a VM value OneWayToSource and only have the VM value update when I press enter while focusing the textbox. I already have the an EnterPressed event that fires when I press the key, I'm just having a hard time figuring out a way to cause that action to update the binding...
In your binding expression, set the UpdateSourceTrigger to Explicit.
Text="{Binding ..., UpdateSourceTrigger=Explicit}"
Then, when handling the EnterPressed event, call UpdateSource on the binding expression, this will push the value from the textbox to the actual bound property.
BindingExpression exp = textBox.GetBindingExpression(TextBox.TextProperty);
exp.UpdateSource();
Here is a complete version of the idea provided by Anderson Imes:
public static readonly DependencyProperty UpdateSourceOnKeyProperty =
DependencyProperty.RegisterAttached("UpdateSourceOnKey",
typeof(Key), typeof(TextBox), new FrameworkPropertyMetadata(Key.None));
public static void SetUpdateSourceOnKey(UIElement element, Key value) {
element.PreviewKeyUp += TextBoxKeyUp;
element.SetValue(UpdateSourceOnKeyProperty, value);
}
static void TextBoxKeyUp(object sender, KeyEventArgs e) {
var textBox = sender as TextBox;
if (textBox == null) return;
var propertyValue = (Key)textBox.GetValue(UpdateSourceOnKeyProperty);
if (e.Key != propertyValue) return;
var bindingExpression = textBox.GetBindingExpression(TextBox.TextProperty);
if (bindingExpression != null) bindingExpression.UpdateSource();
}
public static Key GetUpdateSourceOnKey(UIElement element) {
return (Key)element.GetValue(UpdateSourceOnKeyProperty);
}
If you are using MVVM you can use a combination of decastelijau's approach along with a custom attached property that calls UpdateSource on the textbox when PreviewKeyUp.
public static readonly DependencyProperty UpdateSourceOnKey = DependencyProperty.RegisterAttached(
"UpdateSourceOnKey",
typeof(Key),
typeof(TextBox),
new FrameworkPropertyMetadata(false)
);
public static void SetUpdateSourceOnKey(UIElement element, Key value)
{
//TODO: wire up specified key down event handler here
element.SetValue(UpdateSourceOnKey, value);
}
public static Boolean GetUpdateSourceOnKey(UIElement element)
{
return (Key)element.GetValue(UpdateSourceOnKey);
}
Then you can do:
<TextBox myprops:UpdaterProps.UpdateSourceOnKey="Enter" ... />

Resources