I have observed some unexpected or at least not-perfectly-matching-my-needs behaviour of textboxes bound to textproperties when I can't use using UpdateTrigger=PropertyChanged for my binding. Probably it is not an issue with the textbox but will occur with other editors as well.
In my example (source code attached), I have a WPF TabControl bound to some collection. On each tab, you can edit an item from the collection, in various ways you can trigger a save-action, which should save the edits to some model. The textboxes bound to each items' properties are (on purpose) kept to default update-trigger 'OnFocusLost'. This is because there is some expensive validation taking place when a new value is set.
Now I found there are at least two ways to trigger my save-action in such a way, that the last focused textbox does not update the bound value. 1) Changing the tab-item via mouse-click on its header and then clicking some save-button. (changing back to the previous tab shows that the new value is even lost) 2) Triggering the save-command via KeyGesture.
I setup an example application that demonstrates the behaviour. Clicking on "Save All" will show all item values, the other save-button only shows the current item.
Q: What would be the best way to make sure that all bindingsources of all my textboxes will be updated before the bound objects are comitted? Preferably there should be a single way that catches all possibilites, I dislike to catch each event differently, since I would worry to have forgotten some events. Observing the selection-changed-event of the tab-control for example would solve issue 1) but not issue 2).
Now to the example:
XAML first:
<Window x:Class="TestOMat.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:TestOMat="clr-namespace:TestOMat"
Title="TestOMat" x:Name="wnd">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="dtPerson" DataType="{x:Type TestOMat:Person}">
<StackPanel Orientation="Vertical">
<StackPanel.CommandBindings>
<CommandBinding Command="Close" Executed="CmdSaveExecuted"/>
</StackPanel.CommandBindings>
<TextBox Text="{Binding FirstName}"/>
<TextBox Text="{Binding LastName}"/>
<Button Command="ApplicationCommands.Stop" CommandParameter="{Binding}">Save</Button>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.CommandBindings>
<CommandBinding Command="ApplicationCommands.Stop" Executed="CmdSaveAllExecuted"/>
</Grid.CommandBindings>
<TabControl ItemsSource="{Binding ElementName=wnd, Path=Persons}" ContentTemplate="{StaticResource dtPerson}" SelectionChanged="TabControl_SelectionChanged"/>
<Button Grid.Row="1" Command="ApplicationCommands.Stop">Save All</Button>
</Grid></Window>
And the corresponding class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace TestOMat
{
/// <summary>
/// Interaction logic for TestOMat.xaml
/// </summary>
public partial class TestWindow : Window
{
public TestWindow()
{
InitializeComponent();
}
private List<Person> persons = new List<Person>
{
new Person {FirstName = "John", LastName = "Smith"},
new Person {FirstName = "Peter", LastName = "Miller"}
};
public List<Person> Persons
{
get { return persons; }
set { persons = value; }
}
private void CmdSaveExecuted(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
Person p = e.Parameter as Person;
if (p != null)
{
MessageBox.Show(string.Format("FirstName={0}, LastName={1}", p.FirstName, p.LastName));
e.Handled = true;
}
}
private void CmdSaveAllExecuted(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
MessageBox.Show(String.Join(Environment.NewLine, Persons.Select(p=>string.Format("FirstName={0}, LastName={1}", p.FirstName, p.LastName)).ToArray()));
e.Handled = true;
}
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Console.WriteLine(String.Format("Selection changed from {0} to {1}", e.RemovedItems, e.AddedItems));
// Doing anything here only avoids loss on selected-tab-change
}
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}