Как достичь фокуса reset для обновления BindingSource TextBox перед любым действием

Я наблюдал какое-то неожиданное или, по крайней мере, не совсем идеальное совпадение с моими требованиями к текстовым полям, связанным с textproperties, когда я не могу использовать, используя UpdateTrigger = PropertyChanged для моей привязки. Вероятно, это не проблема с текстовым полем, но будет происходить и с другими редакторами.

В моем примере (исходный код прилагается) у меня есть WPF TabControl, связанный с некоторой коллекцией. На каждой вкладке вы можете редактировать элемент из коллекции, различными способами вы можете инициировать сохранение-действие, которое должно сохранить изменения в какой-либо модели. Текстовые поля, привязанные к свойствам каждого элемента, (специально) сохраняются в стандартном триггере обновления "OnFocusLost". Это связано с тем, что при установке нового значения происходит некоторая дорогостоящая проверка.

Теперь я обнаружил, что есть как минимум два способа инициировать мое действие сохранения таким образом, что последнее сфокусированное текстовое поле не обновляет связанное значение. 1) Изменение элемента табуляции с помощью щелчка мыши по его заголовку, а затем нажатия кнопки сохранения.  (переход на предыдущую вкладку показывает, что новое значение даже потеряно) 2) Запуск команды сохранения с помощью KeyGesture.

Я настраиваю пример приложения, демонстрирующего поведение. Нажав "Сохранить все", вы увидите все значения элементов, другая кнопка сохранения отобразит только текущий элемент.

Q:. Каким будет лучший способ убедиться, что все bindingsources всех моих текстовых полей будут обновлены до того, как будут связаны связанные объекты? Предпочтительно, должен быть единственный способ, который улавливает все возможности, мне не нравится ловить каждое событие по-разному, так как я опасаюсь забыть некоторые события. Наблюдение за событием с изменением выбора элемента управления табло, например, могло бы решить проблему 1), но не выдавать 2).

Теперь к примеру:

XAML сначала:

<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>

И соответствующий класс

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; }
  }
}

Ответ 1

Может быть, неплохо ответить на собственные вопросы, но я думаю, что этот ответ более подходит для вопроса, чем другие, и поэтому стоит писать. Конечно, это было также потому, что я не описал проблему достаточно ясно.

Наконец, так же, как простое "грязное" доказательство концепции, я работал вокруг него следующим образом: LostFocus-Event никогда не запускается в TextBox, когда я переключаю вкладку. Следовательно, привязка не обновляется, и введенное значение теряется, потому что возврат обратно делает обновление привязки из его источника. Но то, что увольняется, - это PreviewLostFocus-Event, поэтому я подключился к этой крошечной функции, которая вручную запускает обновление в источник привязки:

private void BeforeFocusLost(object sender, KeyboardFocusChangedEventArgs e)
{
  if (sender is TextBox) {
    var tb = (TextBox)sender;

    var bnd = BindingOperations.GetBindingExpression(tb, TextBox.TextProperty);

    if (bnd != null) {
      Console.WriteLine(String.Format("Preview Lost Focus: TextBox value {0} / Data value {1} NewFocus will be {2}", tb.Text, bnd.DataItem, e.NewFocus));
      bnd.UpdateSource();
    }
    Console.WriteLine(String.Format("Preview Lost Focus Update forced: TextBox value {0} / Data value {1} NewFocus will be {2}", tb.Text, bnd.DataItem, e.NewFocus));
  }
}

Результат по цепочке событий с PreviewLostFocus, LostFocus (как из TextBox), так и SelectionChanged (из TabControl) будет выглядеть так:

Предварительный просмотр Lost Focus: значение TextBox Smith123456/Значение данных John Smith123 NewFocus будет System.Windows.Controls.TabItem Заголовок: Peter Miller Содержимое: Peter Miller Предварительный просмотр Утерянный фокус Обновление принудительно: значение TextBox Smith123456/Значение данных John Smith123456 NewFocus будет System.Windows.Controls.TabItem Заголовок: Peter Miller Содержимое: Peter Miller Выбор был изменен с System.Object [] на System.Object [] Предварительный просмотр Lost Focus: значение TextBox Миллер/Значение данных Питер Миллер NewFocus будет System.Windows.Controls.TextBox: Peter Предварительный просмотр Утерянный фокус Обновление принудительно: значение TextBox Миллер/Значение данных Питер Миллер NewFocus будет System.Windows.Controls.TextBox: Peter Потерянный фокус, имеющий значение Миллер

Мы видим, что LostFocus происходит только в конце, но не до изменения TabItem. Тем не менее, я думаю, это странно, возможно, ошибка в WPF или в стандартных шаблонах управления. Спасибо всем за ваши предложения, извините, что я не мог их подписать, чтобы отвечать, так как они не решают проблемы с записью вкладок.

Ответ 2

Вы можете написать стиль, предназначенный для всех текстовых полей, в которых у вас будет EventSetter в событиях GotFocus или GotKeyboardFocus, а также в дополнительных событиях LostFocus. В обработчике, связанном с событиями GotFocus, вы должны установить логическую переменную "canSave" в false, что в обработчике LostFocus вы вернетесь к true. Все, что вам нужно сделать, это проверить перед сохранением, если ваша переменная позволяет вам тоже. Если нет, вы можете уведомить пользователя или просто переключить фокус с текстового поля на другое. Таким образом, триггер обновления привязки для редактируемого в настоящее время текстового поля срабатывает соответствующим образом, когда его фокус теряется.

Ответ 3

Возможно, установите свойство UpdateSourceTrigger свойства привязки:

<TextBox Text="{Binding FirstName, UpdateSourceTrigger=Explicit}"/>

Я не уверен, что это то, что вы ищете.