.NET Framework Developer Center >
.NET Development Forums
>
Windows Presentation Foundation (WPF)
>
Textbox not updating binding when focus lost.
Textbox not updating binding when focus lost.
- On a tabcontrol I have several tabpages, on each tab page there is a textbox. This textbox is content bound with a simple Path=Foo and navigates to the Foo property of the ViewModel through datacontext setting on the view. I.e. mvvm setup. Now, when I enter something in the textbox, and click on another tab page, the text in the text box is discarded instead of being updated as focus leaves the textbox. Only if I place something else on the same tab page that can recieve focus, the textbox is correctly updating the viewmodel when focus is lost. Any ideas why textbox dont trigger LostFocus when changing tab page?
Another thing, is it possible to update bindings in a textbox explicit when e.g. the enter key is pressed? Is it possible from xaml alone?
Best regards Jesper Odgaard Nielsen
Answers
- Hi,
here is a Submit Sticky Command:
Namespace Validation Public Class Submit Inherits DependencyObject Public Shared ReadOnly CommandProperty As DependencyProperty = DependencyProperty.RegisterAttached("Command", GetType(RoutedCommand), GetType(Submit), New PropertyMetadata(AddressOf OnCommandChanged)) Public Shared Function GetCommand(ByVal d As DependencyObject) As RoutedCommand Return d.GetValue(CommandProperty) End Function Public Shared Sub SetCommand(ByVal d As DependencyObject, ByVal value As RoutedCommand) d.SetValue(CommandProperty, value) End Sub Private Shared Sub OnCommandChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs) Dim f As FrameworkElement = TryCast(d, FrameworkElement) If f IsNot Nothing Then If e.OldValue IsNot Nothing Then Detach(f, e.OldValue) End If If e.NewValue IsNot Nothing Then Attach(f, e.NewValue) End If End If End Sub Public Shared ReadOnly KeyGestureProperty As DependencyProperty = DependencyProperty.RegisterAttached("KeyGesture", GetType(KeyGesture), GetType(Submit), New PropertyMetadata(AddressOf OnKeyGestureChanged)) Public Shared Function GetKeyGesture(ByVal d As DependencyObject) As KeyGesture Return d.GetValue(KeyGestureProperty) End Function Public Shared Sub SetKeyGesture(ByVal d As DependencyObject, ByVal value As KeyGesture) d.SetValue(KeyGestureProperty, value) End Sub Private Shared Sub OnKeyGestureChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs) Dim f As FrameworkElement = TryCast(d, FrameworkElement) If f IsNot Nothing Then If e.OldValue IsNot Nothing Then DetachKeyGesture(f) End If If e.NewValue IsNot Nothing Then AttachKeyGesture(f, GetCommand(f), e.NewValue) End If End If End Sub Private Shared Sub Attach(ByVal d As FrameworkElement, ByVal command As RoutedCommand) d.CommandBindings.Add(New SubmitCommandBinding(command)) Dim gesture As KeyGesture = GetKeyGesture(d) AttachKeyGesture(d, command, gesture) If d.BindingGroup Is Nothing Then d.BindingGroup = New BindingGroup End If End Sub Private Shared Sub AttachKeyGesture(ByVal d As FrameworkElement, ByVal command As RoutedCommand, ByVal gesture As KeyGesture) ' Can only attach a KeyGesture when both the gesture and the command have been set. If command IsNot Nothing And gesture IsNot Nothing Then d.InputBindings.Add(New SubmitInputBinding(command, gesture)) End If End Sub Private Shared Sub Detach(ByVal d As FrameworkElement, ByVal command As RoutedCommand) ' Use the type if the CommandBinding to find the sticky binding without storing a reference to it For Each b As SubmitCommandBinding In d.CommandBindings.OfType(Of SubmitCommandBinding)() d.CommandBindings.Remove(b) Next DetachKeyGesture(d) End Sub Private Shared Sub DetachKeyGesture(ByVal d As FrameworkElement) ' Use the type of the InputBinding to find the sticky binding without storing a reference to it For Each i As SubmitInputBinding In d.InputBindings.OfType(Of SubmitInputBinding)() d.InputBindings.Remove(i) Next End Sub Private Shared Sub OnCanExecuteSubmit(ByVal sender As Object, ByVal e As CanExecuteRoutedEventArgs) Dim result As Boolean = False Dim target As FrameworkElement = TryCast(sender, FrameworkElement) If target IsNot Nothing Then ' Separate event specific code from domain specific code... result = CanExecuteSubmit(target) End If e.CanExecute = result e.Handled = True End Sub ''' <summary> ''' Contains the logic that checks whether the command can be executed ''' </summary> ''' <param name="target"></param> ''' <returns></returns> ''' <remarks></remarks> Private Shared Function CanExecuteSubmit(ByVal target As FrameworkElement) As Boolean Dim result As Boolean = False If target IsNot Nothing Then result = (target.BindingGroup IsNot Nothing AndAlso Not System.Windows.Controls.Validation.GetHasError(target)) End If Return result End Function Private Shared Sub OnExecutedSubmit(ByVal sender As Object, ByVal e As ExecutedRoutedEventArgs) Dim target As FrameworkElement = TryCast(sender, FrameworkElement) If target IsNot Nothing Then If CanExecuteSubmit(target) Then ExecuteSubmit(target) End If e.Handled = True End If End Sub ''' <summary> ''' Contains the logic that executes the command... ''' </summary> ''' <param name="target"></param> ''' <remarks></remarks> Private Shared Sub ExecuteSubmit(ByVal target As FrameworkElement) target.BindingGroup.UpdateSources() End Sub ''' <summary> ''' Dummy class in order to mark CommandBindings that have been set up through this sticky ''' command with their type. ''' </summary> ''' <remarks></remarks> Public Class SubmitCommandBinding Inherits CommandBinding Public Sub New(ByVal command As RoutedCommand) MyBase.New(command, AddressOf OnExecutedSubmit, AddressOf OnCanExecuteSubmit) End Sub End Class ''' <summary> ''' Dummy class in order to mark InputBindings that have been set up through this sticky ''' command with their type. ''' </summary> ''' <remarks></remarks> Public Class SubmitInputBinding Inherits InputBinding Public Sub New(ByVal command As RoutedCommand, ByVal gesture As InputGesture) MyBase.New(command, gesture) End Sub End Class End Class End Namespace
And this is how you use it:
<Window x:Class="SubmitCommandWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ex="clr-namespace:WpfGlueExamples" xmlns:v="http://wpfglue.wordpress.com/validation" Title="SubmitCommandWindow"> <Window.Resources> <ex:Customer x:Key="data"/> <RoutedCommand x:Key="SubmitCommand"/> </Window.Resources> <StackPanel DataContext="{StaticResource data}"> <!-- CustomerGroupBox is the base for the SubmitCommand Sticky Command. All controls inside this container will connect the Enter key with the SubmitCommand. Because Submit.Command sets CustomerGroupBox.BindingGroup automatically, the UpdateSourceTrigger for all controls will be changed to Explicit, and they will update their sources together when the SubmitCommand is invoked. All Buttons inside this container will be able to send out the SubmitCommand without a CommandTarget parameter.--> <GroupBox x:Name="CustomerGroupBox" Header="Customer" v:Submit.Command="{StaticResource SubmitCommand}" v:Submit.KeyGesture="Enter"> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Label Grid.Row="0">Name</Label> <Label Grid.Row="1">Address</Label> <Label Grid.Row="2">City</Label> <Label Grid.Row="3">Country</Label> <TextBox x:Name="NameTextBox" Grid.Column="1" Grid.Row="0" Text="{Binding Name}"/> <TextBox x:Name="AddressTextBox" Grid.Column="1" Grid.Row="1" Text="{Binding Address}"/> <TextBox x:Name="CityTextBox" Grid.Column="1" Grid.Row="2" Text="{Binding City}"/> <TextBox x:Name="CountryTextBox" Grid.Column="1" Grid.Row="3" Text="{Binding Country}"/> <Button Command="{StaticResource SubmitCommand}" Grid.Row="4">Submit</Button> </Grid> </GroupBox> <GroupBox Header="Customer Data"> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Label Grid.Row="0">Name</Label> <Label Grid.Row="1">Address</Label> <Label Grid.Row="2">City</Label> <Label Grid.Row="3">Country</Label> <TextBlock x:Name="NameTextBlock" Grid.Column="1" Grid.Row="0" Text="{Binding Name}"/> <TextBlock x:Name="AddressTextBlock" Grid.Column="1" Grid.Row="1" Text="{Binding Address}"/> <TextBlock x:Name="CityTextBlock" Grid.Column="1" Grid.Row="2" Text="{Binding City}"/> <TextBlock x:Name="CountryTextBlock" Grid.Column="1" Grid.Row="3" Text="{Binding Country}"/> </Grid> </GroupBox> <!-- This Button invokes the SubmitCommand on the CustomerGroupBox directly by setting it as CommandTarget.--> <Button Command="{StaticResource SubmitCommand}" CommandTarget="{Binding ElementName=CustomerGroupBox}">Submit</Button> </StackPanel> </Window>
If you want to run the example, you'll also need this example class:
Public Class Customer Implements System.ComponentModel.INotifyPropertyChanged Protected Overridable Sub OnPropertyChanged(ByVal ParamArray propertynames() As String) If propertynames.Length = 0 Then RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("")) Else For Each propertyName As String In propertynames RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(propertyName)) Next End If End Sub Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged Private _Name As String Private _Address As String Private _City As String Private _Country As String Public Property Name() As String Get Return _Name End Get Set(ByVal value As String) _Name = value OnPropertyChanged("Name") End Set End Property Public Property Address() As String Get Return _Address End Get Set(ByVal value As String) _Address = value OnPropertyChanged("Address") End Set End Property Public Property City() As String Get Return _City End Get Set(ByVal value As String) _City = value OnPropertyChanged("City") End Set End Property Public Property Country() As String Get Return _Country End Get Set(ByVal value As String) _Country = value OnPropertyChanged("Country") End Set End Property End Class
http://wpfglue.wordpress.com- Marked As Answer byZhi-Xin YeMSFT, ModeratorTuesday, November 24, 2009 7:56 AM
All Replies
- Not sure why the binding is being thrown away when tab is switched. Maybe there is some UI virutlization going on that is breaking the UpdateSourceTrigger setting for textbox.
By default the binding's UpdateSourceTrigger is set to LostFocus for TextBoxes, you could override that and set it to PropertyChanged which will cause your binding to update on every keystroke (might not be exactly what you want)
- Can you try setting the Focusable property to true on your TabItems? Perhaps switching tabs by default is not considered a loss of focus (if they're not focusable).
- Hi Jesper,
Could you post some code to show how your TabControl is defined?
Best Regards,
Zhi-Xin
Please remember to mark the replies as answers if they help and unmark them if they provide no help.
Welcome to the All-In-One Code Framework! - Hi,
this is about the second part of the question, regarding a way to make a binding update itself when Enter is pressed, and doing this in XAML.
Well, the answer is yes and no: no, you can't do it right away in XAML, but yes, you can formulate your code so that you can use it through XAML wherever you want.
This would be a simple job for a Sticky Command. A sticky command is a design pattern where setting an attached property on an element in XAML registers a CommandBinding with the element. You have to implement the attached property and the code that handles the command in code, but once you have it, you can use the attached property wherever you want in XAML, e.g. in a Style, which would enable you to add commands to controls without subclassing.
In your special case, you'd have to do the following:
1. Define a shared RoutedCommand instance as reference for the update command
2. Implement an attached property. You could use a Boolean, in order to switch the ability to handle the command on and of by setting it to true or false
3. In the PropertyChangedHandler of the attached property, if NewValue is true, add a new CommandBinding for the update command, binding its CanExecute and Executed handlers to two static (shared) methods defined in the class that provides the attached property. You might want to store this CommandBinding in a static (shared) variable of the class, so that you can reuse it. Add this CommandBinding to the CommandBindings collection of the element the attached property is set on. If OldValue is true, remove the commandbinding from the CommandBindings collection.
4. In the implementation of the Executed handler, find the BindingGroup of the element on which the property is declared, and call its UpdateSources method. Using a BindingGroup here has the advantage that you can update a set of related controls at the same time, that it is easier to find out what bindings need to be updated, and that the UpdateSourceTrigger for all controls in the group will be set to Explicit automatically. (If you can't use a BindingGroup, you will have to find a way to tell the command implementation what binding on what property you are interested in, so you can call the UpdateSource method on the binding).
5. Create an InputBinding for the update command, connecting it to the Enter key. Then if you press enter in the correct group, the RoutedCommand mechanism should automatically detect the commandbinding that is able to handle the command and update all the controls in the group.
This would be a typical piece of WPFGlue. If you give me some time, I'll probably post an implementation in VB.
http://wpfglue.wordpress.com - Hi,
here is a Submit Sticky Command:
Namespace Validation Public Class Submit Inherits DependencyObject Public Shared ReadOnly CommandProperty As DependencyProperty = DependencyProperty.RegisterAttached("Command", GetType(RoutedCommand), GetType(Submit), New PropertyMetadata(AddressOf OnCommandChanged)) Public Shared Function GetCommand(ByVal d As DependencyObject) As RoutedCommand Return d.GetValue(CommandProperty) End Function Public Shared Sub SetCommand(ByVal d As DependencyObject, ByVal value As RoutedCommand) d.SetValue(CommandProperty, value) End Sub Private Shared Sub OnCommandChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs) Dim f As FrameworkElement = TryCast(d, FrameworkElement) If f IsNot Nothing Then If e.OldValue IsNot Nothing Then Detach(f, e.OldValue) End If If e.NewValue IsNot Nothing Then Attach(f, e.NewValue) End If End If End Sub Public Shared ReadOnly KeyGestureProperty As DependencyProperty = DependencyProperty.RegisterAttached("KeyGesture", GetType(KeyGesture), GetType(Submit), New PropertyMetadata(AddressOf OnKeyGestureChanged)) Public Shared Function GetKeyGesture(ByVal d As DependencyObject) As KeyGesture Return d.GetValue(KeyGestureProperty) End Function Public Shared Sub SetKeyGesture(ByVal d As DependencyObject, ByVal value As KeyGesture) d.SetValue(KeyGestureProperty, value) End Sub Private Shared Sub OnKeyGestureChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs) Dim f As FrameworkElement = TryCast(d, FrameworkElement) If f IsNot Nothing Then If e.OldValue IsNot Nothing Then DetachKeyGesture(f) End If If e.NewValue IsNot Nothing Then AttachKeyGesture(f, GetCommand(f), e.NewValue) End If End If End Sub Private Shared Sub Attach(ByVal d As FrameworkElement, ByVal command As RoutedCommand) d.CommandBindings.Add(New SubmitCommandBinding(command)) Dim gesture As KeyGesture = GetKeyGesture(d) AttachKeyGesture(d, command, gesture) If d.BindingGroup Is Nothing Then d.BindingGroup = New BindingGroup End If End Sub Private Shared Sub AttachKeyGesture(ByVal d As FrameworkElement, ByVal command As RoutedCommand, ByVal gesture As KeyGesture) ' Can only attach a KeyGesture when both the gesture and the command have been set. If command IsNot Nothing And gesture IsNot Nothing Then d.InputBindings.Add(New SubmitInputBinding(command, gesture)) End If End Sub Private Shared Sub Detach(ByVal d As FrameworkElement, ByVal command As RoutedCommand) ' Use the type if the CommandBinding to find the sticky binding without storing a reference to it For Each b As SubmitCommandBinding In d.CommandBindings.OfType(Of SubmitCommandBinding)() d.CommandBindings.Remove(b) Next DetachKeyGesture(d) End Sub Private Shared Sub DetachKeyGesture(ByVal d As FrameworkElement) ' Use the type of the InputBinding to find the sticky binding without storing a reference to it For Each i As SubmitInputBinding In d.InputBindings.OfType(Of SubmitInputBinding)() d.InputBindings.Remove(i) Next End Sub Private Shared Sub OnCanExecuteSubmit(ByVal sender As Object, ByVal e As CanExecuteRoutedEventArgs) Dim result As Boolean = False Dim target As FrameworkElement = TryCast(sender, FrameworkElement) If target IsNot Nothing Then ' Separate event specific code from domain specific code... result = CanExecuteSubmit(target) End If e.CanExecute = result e.Handled = True End Sub ''' <summary> ''' Contains the logic that checks whether the command can be executed ''' </summary> ''' <param name="target"></param> ''' <returns></returns> ''' <remarks></remarks> Private Shared Function CanExecuteSubmit(ByVal target As FrameworkElement) As Boolean Dim result As Boolean = False If target IsNot Nothing Then result = (target.BindingGroup IsNot Nothing AndAlso Not System.Windows.Controls.Validation.GetHasError(target)) End If Return result End Function Private Shared Sub OnExecutedSubmit(ByVal sender As Object, ByVal e As ExecutedRoutedEventArgs) Dim target As FrameworkElement = TryCast(sender, FrameworkElement) If target IsNot Nothing Then If CanExecuteSubmit(target) Then ExecuteSubmit(target) End If e.Handled = True End If End Sub ''' <summary> ''' Contains the logic that executes the command... ''' </summary> ''' <param name="target"></param> ''' <remarks></remarks> Private Shared Sub ExecuteSubmit(ByVal target As FrameworkElement) target.BindingGroup.UpdateSources() End Sub ''' <summary> ''' Dummy class in order to mark CommandBindings that have been set up through this sticky ''' command with their type. ''' </summary> ''' <remarks></remarks> Public Class SubmitCommandBinding Inherits CommandBinding Public Sub New(ByVal command As RoutedCommand) MyBase.New(command, AddressOf OnExecutedSubmit, AddressOf OnCanExecuteSubmit) End Sub End Class ''' <summary> ''' Dummy class in order to mark InputBindings that have been set up through this sticky ''' command with their type. ''' </summary> ''' <remarks></remarks> Public Class SubmitInputBinding Inherits InputBinding Public Sub New(ByVal command As RoutedCommand, ByVal gesture As InputGesture) MyBase.New(command, gesture) End Sub End Class End Class End Namespace
And this is how you use it:
<Window x:Class="SubmitCommandWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ex="clr-namespace:WpfGlueExamples" xmlns:v="http://wpfglue.wordpress.com/validation" Title="SubmitCommandWindow"> <Window.Resources> <ex:Customer x:Key="data"/> <RoutedCommand x:Key="SubmitCommand"/> </Window.Resources> <StackPanel DataContext="{StaticResource data}"> <!-- CustomerGroupBox is the base for the SubmitCommand Sticky Command. All controls inside this container will connect the Enter key with the SubmitCommand. Because Submit.Command sets CustomerGroupBox.BindingGroup automatically, the UpdateSourceTrigger for all controls will be changed to Explicit, and they will update their sources together when the SubmitCommand is invoked. All Buttons inside this container will be able to send out the SubmitCommand without a CommandTarget parameter.--> <GroupBox x:Name="CustomerGroupBox" Header="Customer" v:Submit.Command="{StaticResource SubmitCommand}" v:Submit.KeyGesture="Enter"> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Label Grid.Row="0">Name</Label> <Label Grid.Row="1">Address</Label> <Label Grid.Row="2">City</Label> <Label Grid.Row="3">Country</Label> <TextBox x:Name="NameTextBox" Grid.Column="1" Grid.Row="0" Text="{Binding Name}"/> <TextBox x:Name="AddressTextBox" Grid.Column="1" Grid.Row="1" Text="{Binding Address}"/> <TextBox x:Name="CityTextBox" Grid.Column="1" Grid.Row="2" Text="{Binding City}"/> <TextBox x:Name="CountryTextBox" Grid.Column="1" Grid.Row="3" Text="{Binding Country}"/> <Button Command="{StaticResource SubmitCommand}" Grid.Row="4">Submit</Button> </Grid> </GroupBox> <GroupBox Header="Customer Data"> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Label Grid.Row="0">Name</Label> <Label Grid.Row="1">Address</Label> <Label Grid.Row="2">City</Label> <Label Grid.Row="3">Country</Label> <TextBlock x:Name="NameTextBlock" Grid.Column="1" Grid.Row="0" Text="{Binding Name}"/> <TextBlock x:Name="AddressTextBlock" Grid.Column="1" Grid.Row="1" Text="{Binding Address}"/> <TextBlock x:Name="CityTextBlock" Grid.Column="1" Grid.Row="2" Text="{Binding City}"/> <TextBlock x:Name="CountryTextBlock" Grid.Column="1" Grid.Row="3" Text="{Binding Country}"/> </Grid> </GroupBox> <!-- This Button invokes the SubmitCommand on the CustomerGroupBox directly by setting it as CommandTarget.--> <Button Command="{StaticResource SubmitCommand}" CommandTarget="{Binding ElementName=CustomerGroupBox}">Submit</Button> </StackPanel> </Window>
If you want to run the example, you'll also need this example class:
Public Class Customer Implements System.ComponentModel.INotifyPropertyChanged Protected Overridable Sub OnPropertyChanged(ByVal ParamArray propertynames() As String) If propertynames.Length = 0 Then RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("")) Else For Each propertyName As String In propertynames RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(propertyName)) Next End If End Sub Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged Private _Name As String Private _Address As String Private _City As String Private _Country As String Public Property Name() As String Get Return _Name End Get Set(ByVal value As String) _Name = value OnPropertyChanged("Name") End Set End Property Public Property Address() As String Get Return _Address End Get Set(ByVal value As String) _Address = value OnPropertyChanged("Address") End Set End Property Public Property City() As String Get Return _City End Get Set(ByVal value As String) _City = value OnPropertyChanged("City") End Set End Property Public Property Country() As String Get Return _Country End Get Set(ByVal value As String) _Country = value OnPropertyChanged("Country") End Set End Property End Class
http://wpfglue.wordpress.com- Marked As Answer byZhi-Xin YeMSFT, ModeratorTuesday, November 24, 2009 7:56 AM
- Hi Jesper,
How about the issue now? Do you still need help on this issue? If you have any questions or concerns, please feel free to let me know.
Best Regards,
Zhi-Xin
Please remember to mark the replies as answers if they help and unmark them if they provide no help.
Welcome to the All-In-One Code Framework!


