in general TextBox control TextChanged event is working but in ItemsControl, the TextBox TextChanged Event is not fired how can i do this. I trying to do by using following code which I have implemented but not getting result which I want.
So, what am I doing wrong?
View
<Window x:Class="SoniSoft.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ff="clr-namespace:SoniSoft"
Title="Window1" Height="300" Width="300">
<Window.DataContext>
<ff:ViewModels/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="38"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" ff:TextBoxBehaviour.TextChangedCommand="{Binding TextChanged}" />
<ItemsControl Margin="7,0,0,0" Grid.Row="3" ItemsSource="{Binding Path=ViewModelSearchResults}" x:Name="list">
<ItemsControl.ItemTemplate>
<DataTemplate >
<Grid>
<TextBox ff:TextBoxBehaviour.TextChangedCommand="{Binding TextChanged}" Text="{Binding Path=CategoryName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" FontSize="14" FontWeight="Normal" x:Name=" TextBoxCategoryName" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
View Models
class ViewModels :ViewModelBase
{
public ObservableCollection<Category> AllCategorys = new ObservableCollection<Category>();
DatabaseDataContext db = new DatabaseDataContext();
private ListCollectionView _objViewModelSearchResults;
public ListCollectionView ViewModelSearchResults
{
get { return _objViewModelSearchResults; }
set
{
_objViewModelSearchResults = value;
OnPropertyChanged("ViewModelSearchResults");
}
}
public ViewModels()
{
AllCategorys.Clear();
foreach (var item in db.Categories.OrderBy(c => c.CategoryName))
{
AllCategorys.Add(item);
}
ViewModelSearchResults = new ListCollectionView(AllCategorys);
}
public ICommand TextChanged
{
get
{
// this is very lazy: I should cache the command!
return new TextChangedCommand();
}
}
private class TextChangedCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show("Text Changed");
}
public bool CanExecute(object parameter)
{
return true;
}
}
}
DependencyProperty
class EventBehaviourFactory
{
public static DependencyProperty CreateCommandExecutionEventBehaviour(RoutedEvent routedEvent, string propertyName, Type ownerType)
{
DependencyProperty property = DependencyProperty.RegisterAttached(propertyName, typeof(ICommand), ownerType,
new PropertyMetadata(null,
new ExecuteCommandOnRoutedEventBehaviour(routedEvent).PropertyChangedHandler));
return property;
}
private class ExecuteCommandOnRoutedEventBehaviour : ExecuteCommandBehaviour
{
private readonly RoutedEvent _routedEvent;
public ExecuteCommandOnRoutedEventBehaviour(RoutedEvent routedEvent)
{
_routedEvent = routedEvent;
}
/// <summary>
/// Handles attaching or Detaching Event handlers when a Command is assigned or unassigned
/// </summary>
/// <param name="sender"></param>
/// <param name="oldValue"></param>
/// <param name="newValue"></param>
protected override void AdjustEventHandlers(DependencyObject sender, object oldValue, object newValue)
{
UIElement element = sender as UIElement;
if (element == null) { return; }
if (oldValue != null)
{
element.RemoveHandler(_routedEvent, new RoutedEventHandler(EventHandler));
}
if (newValue != null)
{
element.AddHandler(_routedEvent, new RoutedEventHandler(EventHandler));
}
}
protected void EventHandler(object sender, RoutedEventArgs e)
{
HandleEvent(sender, e);
}
}
internal abstract class ExecuteCommandBehaviour
{
protected DependencyProperty _property;
protected abstract void AdjustEventHandlers(DependencyObject sender, object oldValue, object newValue);
protected void HandleEvent(object sender, EventArgs e)
{
DependencyObject dp = sender as DependencyObject;
if (dp == null)
{
return;
}
ICommand command = dp.GetValue(_property) as ICommand;
if (command == null)
{
return;
}
if (command.CanExecute(e))
{
command.Execute(e);
}
}
/// <summary>
/// Listens for a change in the DependencyProperty that we are assigned to, and
/// adjusts the EventHandlers accordingly
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void PropertyChangedHandler(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// the first time the property changes,
// make a note of which property we are supposed
// to be watching
if (_property == null)
{
_property = e.Property;
}
object oldValue = e.OldValue;
object newValue = e.NewValue;
AdjustEventHandlers(sender, oldValue, newValue);
}
}
}
class TextBoxBehaviour
{
public static readonly DependencyProperty TextChangedCommand = EventBehaviourFactory.CreateCommandExecutionEventBehaviour(TextBox.TextChangedEvent, "TextChangedCommand", typeof(TextBoxBehaviour));
public static void SetTextChangedCommand(DependencyObject o, ICommand value)
{
o.SetValue(TextChangedCommand, value);
}
public static ICommand GetTextChangedCommand(DependencyObject o)
{
return o.GetValue(TextChangedCommand) as ICommand;
}
}
Here is the problem. You are setting the command in an ItemTemplate. Thus it is binding to the Category object you have in the ListCollectionView. Now this is the object that doesnt contain any command for your text changed. What does contain the command for your TextChanged is the DataContext of the UserControl and you need to bind it to that.
Now there are is a way to work around and its called Ancestor RelativeSource. As I work with silverlight it might work different but this line of code should do.
Edit:
The actual line should be. this because it is ofcourse a window and you need to have the DataContext (the viewmodel) and then the property TextChanged:
<TextBox ff:TextBoxBehaviour.TextChangedCommand="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}, Path=DataContext.TextChanged}" />
Related
I have a problem with binding in KeyBinding in WPF. I'm developing .net 3.5 project using WPF with MVVM pattern. I have to fire command whenever some letter will be typed. Unfortunately Command and CommandParameter aren't Dependency Properties in this .net version and i can't bind to them. So I've written attached properties to assign command and command parameter from my view model. But binding to them isn't working, when I change binding to text (in command parameter) CommandBindingParameterChanged will rise but it doesn't when there is binding to parameter. I tired to set window's name and pass that to binding but it also didn't work. But when I'll assign the same command to button it works fine. Here is my code snippet:
Attached properties:
public class Helper
{
public static readonly DependencyProperty CommandBindingProperty = DependencyProperty.RegisterAttached("CommandBinding", typeof(ICommand), typeof(Helper), new FrameworkPropertyMetadata(default(ICommand), FrameworkPropertyMetadataOptions.None, CommandChanged));
public static ICommand GetCommandBinding(DependencyObject o)
{
return (ICommand)o.GetValue(CommandBindingProperty);
}
public static void SetCommandBinding(DependencyObject o, ICommand value)
{
o.SetValue(CommandBindingProperty, value);
}
private static void CommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var input = d as InputBinding;
input.Command = (ICommand)e.NewValue;
}
public static readonly DependencyProperty CommandBindingParameterProperty = DependencyProperty.RegisterAttached("CommandBindingParameter", typeof(object), typeof(Helper), new PropertyMetadata(CommandParameterChanged));
private static void CommandParameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var input = d as InputBinding;
if (input != null)
input.CommandParameter = e.NewValue;
}
public static object GetCommandBindingParameter(DependencyObject o)
{
return o.GetValue(CommandBindingParameterProperty);
}
public static void SetCommandBindingParameter(DependencyObject o, object value)
{
o.SetValue(CommandBindingParameterProperty, value);
}
}
ViewModel
public class MainWindowViewModel : ViewModelBase
{
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
RaisePropertyChanged("Text");
}
}
private bool _parameter;
public bool Parameter
{
get { return _parameter; }
set
{
_parameter = value;
RaisePropertyChanged("Parameter");
}
}
public MainWindowViewModel()
{
Parameter = true;
}
private RelayCommand<bool> _someCommand;
public ICommand SomeCommand
{
get { return _someCommand ?? (_someCommand = new RelayCommand<bool>(Execute, CanExecute)); }
}
private bool CanExecute(bool arg)
{
return arg;
}
private void Execute(bool obj)
{
//do something
}
}
XAML:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:Test"
Name="Window"
DataContext="{Binding Main, Source={StaticResource Locator}}"
>
<Grid>
<StackPanel>
<TextBox Text="{Binding Text}">
<TextBox.InputBindings>
<KeyBinding Key="A" local:Helper.CommandBinding="{Binding DataContext.SomeCommand, ElementName=Window}" local:Helper.CommandBindingParameter="{Binding DataContext.Parameter, ElementName=Window}"/>
</TextBox.InputBindings>
</TextBox>
<Button Content="SomeButton" Command="{Binding SomeCommand}" CommandParameter="{Binding Parameter}"/>
</StackPanel>
</Grid>
you may want to try this solution.
Use Blend 3 Interactions, i.e. Add System.Windows.Interactivity & Microsoft.Expression.Interactions.dll as reference into your project. I have tested the changes below. Execute method (defined in ViewModel) is called the movement textbox is keyed in.
Modified XAML:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:Test"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Name="Window">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<local:CommandAction Command="{Binding Path=SomeCommand}" CommandParameter="{Binding Path=Parameter}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</StackPanel>
</Grid>
</Window>
CommandAction.CS: Instead of Helper, use CommandAction. CommandAction is found at this location
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using Microsoft.Expression.Interactivity;
using System.Windows.Interactivity;
namespace Test
{
/// <summary>
/// The CommandAction allows the user to route a FrameworkElement's routed event to a Command.
/// For instance this makes it possible to specify--in Xaml--that right-clicking on a Border
/// element should execute the Application.Close command (this example may not make much sense,
/// but it does illustrate what's possible).
///
/// CommandParameter and CommandTarget properties are provided for consistency with the Wpf
/// Command pattern.
///
/// The action's IsEnabled property will be updated according to the Command's CanExecute value.
///
/// In addition a SyncOwnerIsEnabled property allows the user to specify that the owner element
/// should be enabled/disabled whenever the action is enabled/disabled.
/// </summary>
public class CommandAction : TargetedTriggerAction<FrameworkElement>, ICommandSource
{
#region Properties to Expose
[Category("Command Properties")]
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof(ICommand), typeof(CommandAction), new PropertyMetadata(
(ICommand)null, OnCommandChanged));
[Category("Command Properties")]
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(
"CommandParameter", typeof(object), typeof(CommandAction), new PropertyMetadata());
[Category("Command Properties")]
public IInputElement CommandTarget
{
get { return (IInputElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register(
"CommandTarget", typeof(IInputElement), typeof(CommandAction), new PropertyMetadata());
[Category("Command Properties")]
public bool SyncOwnerIsEnabled
{
get { return (bool)GetValue(SyncOwnerIsEnabledProperty); }
set { SetValue(SyncOwnerIsEnabledProperty, value); }
}
/// <summary>
/// When SyncOwnerIsEnabled is true then changing CommandAction.IsEnabled will automatically
/// update the owner (Target) IsEnabled property.
/// </summary>
public static readonly DependencyProperty SyncOwnerIsEnabledProperty = DependencyProperty.Register(
"SyncOwnerIsEnabled", typeof(bool), typeof(CommandAction), new PropertyMetadata());
#endregion
#region Command implementation
/// <summary>
/// This is a strong reference to the Command.CanExecuteChanged event handler. The commanding
/// system uses a weak reference and if we don't enforce a strong reference then the event
/// handler will be gc'ed.
/// </summary>
private EventHandler CanExecuteChangedHandler;
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var action = (CommandAction)d;
action.OnCommandChanged((ICommand)e.OldValue, (ICommand)e.NewValue);
}
private void OnCommandChanged(ICommand oldCommand, ICommand newCommand)
{
if (oldCommand != null)
UnhookCommand(oldCommand);
if (newCommand != null)
HookCommand(newCommand);
}
private void UnhookCommand(ICommand command)
{
command.CanExecuteChanged -= CanExecuteChangedHandler;
UpdateCanExecute();
}
private void HookCommand(ICommand command)
{
// Save a strong reference to the Command.CanExecuteChanged event handler. The commanding
// system uses a weak reference and if we don't save a strong reference then the event
// handler will be gc'ed.
CanExecuteChangedHandler = new EventHandler(OnCanExecuteChanged);
command.CanExecuteChanged += CanExecuteChangedHandler;
UpdateCanExecute();
}
private void OnCanExecuteChanged(object sender, EventArgs e)
{
UpdateCanExecute();
}
private void UpdateCanExecute()
{
if (Command != null)
{
RoutedCommand command = Command as RoutedCommand;
if (command != null)
IsEnabled = command.CanExecute(CommandParameter, CommandTarget);
else
IsEnabled = Command.CanExecute(CommandParameter);
if (Target != null && SyncOwnerIsEnabled)
Target.IsEnabled = IsEnabled;
}
}
#endregion
protected override void Invoke(object o)
{
if (Command != null)
{
var command = Command as RoutedCommand;
if (command != null)
command.Execute(CommandParameter, CommandTarget);
else
Command.Execute(CommandParameter);
}
}
}
}
Screenshot: if System.Windows.Interactivity & Microsoft.Expression.Interactions.dll are missing in your environment, please install blend. Blend is very easy to isntall and Installation doesn't take much time.
Can anyone show me a simple working example for a WPF MVVM application to set the ItemsSource of combobox B based on the SelectedItem of ComboBox A?
It seems from what I've found on this site that it gets all too complicated all too quickly.
What's the "right" MVVM way to get it done?
Thank you.
EDIT
I updated using Didier's example.
An extract of my XAML:
<ComboBox Name="BrowserStackDesktopOS" ItemsSource="Binding Platforms.AvailableBrowserStackDesktopOSes}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopOSSelectedValue, Mode=TwoWay}"/>
<ComboBox Name="BrowserStackDesktopOSVersion" ItemsSource="{Binding Platforms.AvailableBrowserStackDesktopOSVersions}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopOSVersionSelectedValue, Mode=TwoWay}"/>
<ComboBox Name="BrowserStackDesktopBrowser" ItemsSource="{Binding Platforms.AvailableBrowserStackDesktopBrowsers}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopBrowserSelectedValue, Mode=TwoWay}"/>
<ComboBox Name="BrowserStackDesktopBrowserVersion" ItemsSource="{Binding Platforms.AvailableBrowserStackDesktopBrowserVersions}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopBrowserVersionSelectedValue, Mode=TwoWay}"/>
And an example of my code behind:
public string BrowserStackDesktopOSSelectedValue {
get { return (string)GetValue(BrowserStackDesktopOSSelectedValueProperty); }
set { SetValue(BrowserStackDesktopOSSelectedValueProperty, value);
AvailableBrowserStackDesktopOSVersions = AvailableBrowserStackDesktopPlatforms.GetOSVersions(BrowserStackDesktopOSSelectedValue);
NotifyPropertyChanged("BrowserStackDesktopOSSelectedValue");
}
}
However when I select a value for the first ComboBox nothing happens. I am wanting the Itemsource of the next ComboBox to by populated.
What have I done wrong?
Basically you need to expose in your MVVM 2 collections of values for combo-box choices and two properties for selected values.
In the beginning only the first collection if filled with values. When the first selected value changes the second collection will be filled in with appropriate values. Here is an example implementation:
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//Set the data context of the window
DataContext = new TestVM();
}
}
public class TestVM : INotifyPropertyChanged
{
#region Class attributes
protected static string[] firstComboValues = new string[] { "Choice_1", "Choice_2" };
protected static string[][] secondComboValues =
new string[][] {
new string[] { "value_1_1", "value_1_2", "value_1_3" },
new string[] { "value_2_1", "value_2_2", "value_2_3" }
};
#endregion
#region Public Properties
#region FirstSelectedValue
protected string m_FirstSelectedValue;
/// <summary>
///
/// </summary>
public string FirstSelectedValue
{
get { return m_FirstSelectedValue; }
set
{
if (m_FirstSelectedValue != value)
{
m_FirstSelectedValue = value;
UpdateSecondComboValues();
NotifyPropertyChanged("FirstSelectedValue");
}
}
}
#endregion
#region SecondSelectedValue
protected string m_SecondSelectedValue;
/// <summary>
///
/// </summary>
public string SecondSelectedValue
{
get { return m_SecondSelectedValue; }
set
{
if (m_SecondSelectedValue != value)
{
m_SecondSelectedValue = value;
NotifyPropertyChanged("SecondSelectedValue");
}
}
}
#endregion
#region FirstComboValues
protected ObservableCollection<string> m_FirstComboValues;
/// <summary>
///
/// </summary>
public ObservableCollection<string> FirstComboValues
{
get { return m_FirstComboValues; }
set
{
if (m_FirstComboValues != value)
{
m_FirstComboValues = value;
NotifyPropertyChanged("FirstComboValues");
}
}
}
#endregion
#region SecondComboValues
protected ObservableCollection<string> m_SecondComboValues;
/// <summary>
///
/// </summary>
public ObservableCollection<string> SecondComboValues
{
get { return m_SecondComboValues; }
set
{
if (m_SecondComboValues != value)
{
m_SecondComboValues = value;
NotifyPropertyChanged("SecondComboValues");
}
}
}
#endregion
#endregion
public TestVM()
{
FirstComboValues = new ObservableCollection<string>(firstComboValues);
}
/// <summary>
/// Update the collection of values for the second combo box
/// </summary>
protected void UpdateSecondComboValues()
{
int firstComboChoice;
for (firstComboChoice = 0; firstComboChoice < firstComboValues.Length; firstComboChoice++)
{
if (firstComboValues[firstComboChoice] == FirstSelectedValue)
break;
}
if (firstComboChoice == firstComboValues.Length)// just in case of a bug
SecondComboValues = null;
else
SecondComboValues = new ObservableCollection<string>(secondComboValues[firstComboChoice]);
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And the associated XAML
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="window" x:Class="Testing1.MainWindow">
<Grid>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center" Width=" 300">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="10"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox x:Name="FirstOne" ItemsSource="{Binding FirstComboValues}" SelectedItem="{Binding FirstSelectedValue, Mode=TwoWay}"/>
<ComboBox x:Name="SecondOne" ItemsSource="{Binding SecondComboValues}" SelectedItem="{Binding SecondSelectedValue, Mode=TwoWay}" Grid.Row="2"/>
</Grid>
</Grid>
</Window>
As you can see the SelectedValue properties of combo boxes are binded in TwoWay mode so when SelectedValue property of the combo box changes it changes the value on the VM side. And in FirstSelectedValue property setter UpdateSecondComboValues() method is called to update values for the second combo box.
EDIT:
It happens because you mixed both INotifPropertyChanged and DependencyObject. You should choose one of them. Usually you implement INotifyPropertyChanged in your VM and the code in the property setter will work.
If you inherit from DependencyObject however, you should not write any code in the setter/getter. It will never be called by the TwoWay binding. It will just call GetValue(...) internally. To be able to execute an action on DependencyProperty change you should declare it differently with a property changed handler:
#region BrowserStackDesktopOSSelectedValue
/// <summary>
/// BrowserStackDesktopOSSelectedValue Dependency Property
/// </summary>
public static readonly DependencyProperty BrowserStackDesktopOSSelectedValue Property =
DependencyProperty.Register("BrowserStackDesktopOSSelectedValue ", typeof(string), typeof(YourVM),
new FrameworkPropertyMetadata((string)null,
new PropertyChangedCallback(OnBrowserStackDesktopOSSelectedValue Changed)));
/// <summary>
/// Gets or sets the BrowserStackDesktopOSSelectedValue property. This dependency property
/// indicates ....
/// </summary>
public string BrowserStackDesktopOSSelectedValue
{
get { return (string)GetValue(BrowserStackDesktopOSSelectedValue Property); }
set { SetValue(BrowserStackDesktopOSSelectedValue Property, value); }
}
/// <summary>
/// Handles changes to the BrowserStackDesktopOSSelectedValue property.
/// </summary>
private static void OnBrowserStackDesktopOSSelectedValue Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
YourVM target = (YourVM)d;
string oldBrowserStackDesktopOSSelectedValue = (string)e.OldValue;
string newBrowserStackDesktopOSSelectedValue = target.BrowserStackDesktopOSSelectedValue ;
target.OnBrowserStackDesktopOSSelectedValue Changed(oldBrowserStackDesktopOSSelectedValue , newBrowserStackDesktopOSSelectedValue );
}
/// <summary>
/// Provides derived classes an opportunity to handle changes to the BrowserStackDesktopOSSelectedValue property.
/// </summary>
protected virtual void OnBrowserStackDesktopOSSelectedValue Changed(string oldBrowserStackDesktopOSSelectedValue , string newBrowserStackDesktopOSSelectedValue )
{
//Here write some code to update your second ComboBox content.
AvailableBrowserStackDesktopOSVersions = AvailableBrowserStackDesktopPlatforms.GetOSVersions(BrowserStackDesktopOSSelectedValue);
}
#endregion
By the way I always use Dr WPF snippets to write DPs so it goes much faster.
I'm using MVVM and have the following problem. My TextBox.Text is bound with UpdateSourceTrigger=LostFocus (thats what the user want). I have a Button with a SaveCommand CommandBinding - this works. Now i have a KeyBinding with Strg+S wich also execute the SaveCommand. And here is the problem: when i m in the Textbox and press Strg+s, the changes are not in the viewmodel.
is there any way to get MVVM Commands with KeyBinding and TextBox UpdateSourceTrigger=LostFocus working together?
some code to check out the problem
<Window>
<Window.InputBindings>
<KeyBinding Key="S" Modifiers="Control" Command="{Binding SaveCommand}"></KeyBinding>
</Window.InputBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding MyText1, UpdateSourceTrigger=LostFocus}" Width="100"></TextBox>
<Button Grid.Row="1" Content="_Save" Command="{Binding SaveCommand}" IsDefault="True"></Button>
</Grid>
</Window>
public partial class MainWindow : Window
{
private Viewmodel _data;
public MainWindow()
{
_data = new Viewmodel();
InitializeComponent();
this.DataContext = _data;
}
}
public class Viewmodel : INPCBase
{
private string _myText1;
private Lazy<DelegateCommand> _save;
public Viewmodel()
{
this._save = new Lazy<DelegateCommand>(()=> new DelegateCommand(this.SaveCommandExecute));
}
private void SaveCommandExecute()
{
MessageBox.Show(MyText1);
}
public string MyText1
{
get { return _myText1; }
set { _myText1 = value; this.NotifyPropertyChanged(()=>MyText1);}
}
public ICommand SaveCommand
{
get { return _save.Value; }
}
}
at the moment i came up with the following workaround. within the usercontrol/views where i define my KeyBindings, i also listen to the PreviewKeyDown event and set the focus to the next element when eg. Strg+S is pressed.
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.S && e.KeyboardDevice.Modifiers == ModifierKeys.Control)
{
var fe = Keyboard.FocusedElement as UIElement;
if (fe != null)
{
fe.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
}
I have the same problem and end up with attached property for TextBox.
public static bool GetCommitOnSave(DependencyObject obj)
{
return (bool)obj.GetValue(CommitOnSaveProperty);
}
public static void SetCommitOnSave(DependencyObject obj, bool value)
{
obj.SetValue(CommitOnSaveProperty, value);
}
public static readonly DependencyProperty CommitOnSaveProperty =
DependencyProperty.RegisterAttached("CommitOnSave", typeof(bool), typeof(Helper), new PropertyMetadata(false, CommitOnSavePropertyChanged));
private static void CommitOnSavePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TextBox textBox)
{
if ((bool)e.NewValue)
{
if ((bool)e.NewValue)
{
textBox.KeyDown += TextBox_KeyDown;
}
else
{
textBox.KeyDown -= TextBox_KeyDown;
}
}
}
}
private static void TextBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
var textBox = (TextBox)sender;
if (e.Key == Key.S && Keyboard.Modifiers == ModifierKeys.Control)
{
BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty).UpdateSource();
}
}
Using <TextBox Text="{Binding Name}" local:Helper.CommitOnSave="True" />
Of course you can set attached property in style for all TextBoxes in a form.
I think I find the best solution for me. I mix solution #blindmeis and my previous one with using attached property.
I create command which update binding source of actual keyboard focused element:
public class CommitValueCommand : ICommand
{
private static CommitValueCommand _instance;
public static CommitValueCommand Command => _instance ?? (_instance = new CommitValueCommand());
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
if (Keyboard.FocusedElement is TextBox textBox)
{
BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty).UpdateSource();
}
//for combobox etc.
else if (Keyboard.FocusedElement is Selector selector)
{
BindingOperations.GetBindingExpression(selector, Selector.SelectedValueProperty).UpdateSource();
}
}
}
In Execute method of command SaveCommand just at beginning invoke CommitValueCommand.Command.Execute().
I've set up a simple Silverlight 4 control which is supposed to switch the visibility of two textboxes based on a public property. I add the control to a view and set the databinding of the control's property to a property of the parent view's viewmodel.
When a change in the parent viewmodel's property occurs, nothing happens in the usercontrol. Although it's bound, the OnPropertyChanged doesnt seem to interest the bound property of the user control. Below is the code of the user control.
<UserControl x:Class="Controls.EAPPasswordBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" x:Name="_root" >
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Top">
<PasswordBox x:Name="pwdBox" />
<TextBox x:Name="txtBox" />
</StackPanel>
</Grid>
public partial class EAPPasswordBox : UserControl, INotifyPropertyChanged
{
public bool ShowText
{
get { return (bool)GetValue(ShowTextProperty); }
set {
SetValue(ShowTextProperty, value);
if (value == true)
{
this.pwdBox.Visibility = System.Windows.Visibility.Collapsed;
this.txtBox.Visibility = System.Windows.Visibility.Visible;
}
else
{
this.pwdBox.Visibility = System.Windows.Visibility.Collapsed;
this.txtBox.Visibility = System.Windows.Visibility.Visible;
}
}
}
private Visibility _PwdBoxVisibility;
public Visibility PwdBoxVisibility
{
get { return _PwdBoxVisibility; }
set
{
_PwdBoxVisibility = value; NotifyPropertyChanged("PwdBoxVisibility");
}
}
private Visibility _TxtBoxVisibility;
public Visibility TxtBoxVisibility
{
get { return _TxtBoxVisibility; }
set
{
_TxtBoxVisibility = value; NotifyPropertyChanged("TxtBoxVisibility");
}
}
public static readonly DependencyProperty ShowTextProperty =
DependencyProperty.Register("ShowText", typeof(bool), typeof(EAPPasswordBox),null);
public EAPPasswordBox()
{
InitializeComponent();
}
private static void OnShowTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Here is how I use it in my parent view:
<local:EAPPasswordBox x:Name="pwdBox"
Grid.Column="1" Grid.Row="0" Grid.ColumnSpan="2" ShowText="{Binding showPassword, Mode=TwoWay}"></local:EAPPasswordBox>
private bool _showPassword;
public bool showPassword
{
get
{
return _showPassword;
}
set
{
_showPassword = value;
RaisePropertyChanged("showPassword");
}
}
When the "showPassword" in the parent view's viewmodel changes, nothing happens in the user control, and it's driving me crazy :)
Any ideas? Thank you.
Updates to bound Dependency Properties don't occur with the normal get/set accessors of the property but behind the scenes. As such the only way to intercept when the value is changed is to provider a DependencyPropertyChangedEventHandler in the PropertyMetadata when you create the Dependency Property.
As follows:
public static readonly DependencyProperty ShowTextProperty =
DependencyProperty.Register("ShowText", typeof(bool), typeof(EAPPasswordBox), new PropertyMetadata(ShowTextPropertyChanged));
private static void ShowTextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
EAPPasswordBox passwordBox = sender as EAPPasswordBox;
if (passwordBox != null)
{
passwordBox.SetVisibilityOfTextBoxes();
}
}
Hope it helps.
Implement what you do in the setter of the property in the OnShowTextPropertyChanged handler. The setter will only be used to initialise the binding.
I am throwing an ApplicationException if the FirstName value is null or empty and I am trying to display the error message in the TextBlock which is part of the ErrorTemplate. But it always shows "Exception has been thrown at the target of invocation".
public string FirstName
{
get { return _firstName;}
set
{
if(String.IsNullOrEmpty(value))
throw new ApplicationException("FirstName cannot be null or empty!");
_firstName = value;
OnPropertyChanged("FirstName");
}
}
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Orange"
FontSize="12pt"
Text="{Binding ElementName=MyAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
</TextBlock>
<Border BorderBrush="Green" BorderThickness="1">
<AdornedElementPlaceholder Name="MyAdorner" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And finally here is the TextBox control:
<TextBox Name="txtFirstName" Style="{StaticResource TextBoxStyle}" Grid.Column="1" Grid.Row="0" Height="20" Width="100" Margin="10">
<TextBox.Text>
<Binding Path="FirstName">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
I went through this the other day, I ended up taking the exception off the property and using a validation class.
<TextBox Name="txtFirstName" Style="{StaticResource TextBoxStyle}" Grid.Column="1" Grid.Row="0" Height="20" Width="100" Margin="10">
<TextBox.Text>
<Binding Path="FirstName" >
<Binding.ValidationRules>
<validators:StringRangeValidationRule
MinimumLength="1"
MaximumLength="40"
ErrorMessage="Required" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
Remove the exception from your property so it just looks like this ...
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
OnPropertyChanged("FirstName");
}
}
Heres the validation class (I poached this off the internet somewhere) ...
public class StringRangeValidationRule : ValidationRule
{
private int _minimumLength = -1;
private int _maximumLength = -1;
private string _errorMessage;
public int MinimumLength
{
get { return _minimumLength; }
set { _minimumLength = value; }
}
public int MaximumLength
{
get { return _maximumLength; }
set { _maximumLength = value; }
}
public string ErrorMessage
{
get { return _errorMessage; }
set { _errorMessage = value; }
}
public override ValidationResult Validate(object value,
CultureInfo cultureInfo)
{
ValidationResult result = new ValidationResult(true, null);
string inputString = (value ?? string.Empty).ToString();
if (inputString.Length < this.MinimumLength ||
(this.MaximumLength > 0 &&
inputString.Length > this.MaximumLength))
{
result = new ValidationResult(false, this.ErrorMessage);
}
return result;
}
}
You'll need to add a namespace reference in your xaml to where the validation class lives called validators (i'm sure you know this but just for completeness)
Something like ...
xmlns:validators="clr-namespace:WpfApplication1"
Hope this helps!
Cheers,
Andy
Again, I went through exactly the same thing this week! I found the following on the web, wrap your text boxes in this ...
<validators:ValidatedContent Name="Validator" >
<!-- All your textboxes here -->
</validators:ValidatedContent>
Add the class below in the same place you put the other one ... and then you can call Validator.Validate() from a button click or where ever you want. There is also an IsContentValid property you can use to decide whether you want to save etc.
public class ValidatedContent : Decorator
{
#region Public Constructors
/// <summary>
/// Initializes a new instance of ValidatedContent
/// </summary>
public ValidatedContent()
{
ErrorMessages = new ObservableCollection<string>();
Loaded += new RoutedEventHandler(OnValidatedContentLoaded);
}
#endregion
#region Event Handlers
/// <summary>
/// Handles the loaded event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnValidatedContentLoaded(object sender, RoutedEventArgs e)
{
Queue<DependencyObject> elementQueue = new Queue<DependencyObject>();
elementQueue.Enqueue(this.Child);
// Iterate over all the child elements
while (elementQueue.Count > 0)
{
// Dequeue the first element in the queue
DependencyObject element = elementQueue.Dequeue();
if (element != null)
{
foreach (var childElement in LogicalTreeHelper.GetChildren(element))
{
if (childElement is DependencyObject)
elementQueue.Enqueue((DependencyObject)childElement);
}
}
Control control = element as Control;
// Mark the element as valid if it is a control
if (control != null && GetIsRequired(element))
{
control.SetValue(Control.StyleProperty, RequiredControlStyle);
}
}
}
#endregion
#region Dependency Properties
public static readonly DependencyProperty IsContentValidProperty =
DependencyProperty.Register("IsContentValid", typeof(bool),
typeof(ValidatedContent), new UIPropertyMetadata(false));
public static readonly DependencyProperty ErrorMessagesProperty =
DependencyProperty.Register("ErrorMessages", typeof(ObservableCollection<string>),
typeof(ValidatedContent), new UIPropertyMetadata(null));
public static readonly DependencyProperty RequiredControlStyleProperty =
DependencyProperty.Register("RequiredControlStyle", typeof(Style),
typeof(ValidatedContent), new UIPropertyMetadata(null));
public static readonly DependencyProperty IsRequiredProperty =
DependencyProperty.RegisterAttached("IsRequired", typeof(bool),
typeof(ValidatedContent), new UIPropertyMetadata(false));
#endregion
#region Public Properties
/// <summary>
/// Gets or sets the style to mark a required control
/// </summary>
public Style RequiredControlStyle
{
get { return (Style)GetValue(RequiredControlStyleProperty); }
set { SetValue(RequiredControlStyleProperty, value); }
}
/// <summary>
/// Gets or sets the error messages for the validated content
/// </summary>
public ObservableCollection<string> ErrorMessages
{
get { return (ObservableCollection<string>)GetValue(ErrorMessagesProperty); }
private set { SetValue(ErrorMessagesProperty, value); }
}
/// <summary>
/// Gets if the content is valid
/// </summary>
public bool IsContentValid
{
get { return (bool)GetValue(IsContentValidProperty); }
private set { SetValue(IsContentValidProperty, value); }
}
#endregion
#region Public Methods
/// <summary>
/// Validates the content of the decorator
/// </summary>
public void Validate()
{
IsContentValid = true;
ErrorMessages.Clear();
Queue<DependencyObject> elementQueue = new Queue<DependencyObject>();
elementQueue.Enqueue(this.Child);
// Iterate over all the child elements
while (elementQueue.Count > 0)
{
// Dequeue the first element in the queue
DependencyObject element = elementQueue.Dequeue();
foreach (var childElement in LogicalTreeHelper.GetChildren(element))
{
if (childElement is DependencyObject)
elementQueue.Enqueue((DependencyObject)childElement);
}
// Validate the bindings of the element
ValidateBindings(element);
}
}
#endregion
#region Private Methods
/// <summary>
/// Validates the bindings of the dependency object
/// </summary>
/// <param name="element"></param>
private void ValidateBindings(DependencyObject element)
{
if (element != null)
{
Type elementType = element.GetType();
FieldInfo[] dependencyPropertyFields = elementType.GetFields(
BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly);
// Iterate over all dependency properties
foreach (FieldInfo dependencyPropertyField in dependencyPropertyFields)
{
DependencyProperty dependencyProperty =
dependencyPropertyField.GetValue(element) as DependencyProperty;
if (dependencyProperty != null)
{
Binding binding = BindingOperations.GetBinding(element, dependencyProperty);
BindingExpression bindingExpression = BindingOperations.GetBindingExpression(element, dependencyProperty);
// Issue 1822 - Extra check added to prevent null reference exceptions
if (binding != null && bindingExpression != null)
{
// Validate the validation rules of the binding
foreach (ValidationRule rule in binding.ValidationRules)
{
ValidationResult result = rule.Validate(element.GetValue(dependencyProperty),
CultureInfo.CurrentCulture);
bindingExpression.UpdateSource();
if (!result.IsValid)
{
ErrorMessages.Add(result.ErrorContent.ToString());
}
IsContentValid &= result.IsValid;
}
}
}
}
}
}
#endregion
#region Static Methods
/// <summary>
/// Gets the value for the IsRequired attached property
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static bool GetIsRequired(DependencyObject obj)
{
return (bool)obj.GetValue(IsRequiredProperty);
}
/// <summary>
/// Sets the value for the IsRequired attached property
/// </summary>
/// <param name="obj"></param>
/// <param name="value"></param>
public static void SetIsRequired(DependencyObject obj, bool value)
{
obj.SetValue(IsRequiredProperty, value);
}
#endregion
}
I had some issues with this too so I am posting my workaround in case it would help someone.
The message shown in the Text block in not the correct one because the Exception that is triggered in the code is wrapped up into a TargetInvocationException. So the error message displayed is the one of that exception which is "Exception has been thrown at the target of invocation".
The exception you want to display is actually accessible in the InnerException of the TargetInvocationException and you can therefore display it message using the following statement in your XAML
Text="{Binding ElementName=MyAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent.InnerException.Message}"
I am pretty sure there must be a way that the error contained in the ErrorContent field is the correct one but I have not digged enough to understand how.