Как добиться сброса фокуса, чтобы обновить BindingSource TextBox перед любым действием - PullRequest
10 голосов
/ 15 июля 2009

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

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

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

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

В: Как лучше всего убедиться, что все источники связывания всех моих текстовых полей будут обновлены до того, как связанные объекты будут завершены? Желательно, чтобы был единый способ, позволяющий отловить все возможные варианты. Мне не нравится перехватывать каждое событие по-разному, так как я бы беспокоился о том, чтобы забыть о некоторых событиях. Например, наблюдение за изменением выбора элемента управления вкладками решит проблему 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; }
  }
}

Ответы [ 3 ]

3 голосов
/ 17 сентября 2009

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

Наконец, как быстрое и грязное подтверждение концепции, я обошел ее примерно так: событие LostFocus никогда не запускается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 Lost Focus принудительное обновление: значение TextBox Smith123456 / значение данных John Smith123456 NewFocus будет System.Windows.Controls.TabItem Заголовок: Peter Miller Содержимое: выбор Питера Миллера изменен с System.Object [] на System.Object [] PreviewПотерянный фокус: значение TextBox Миллер / значение данных Питер Миллер NewFocus будет System.Windows.Controls.TextBox: Peter Preview Потерянный фокус Обновление принудительно: значение TextBox Миллер / значение данных Питер Миллер NewFocus будет System.Windows.Controls.TextBox: Peter LostФокус со значением Миллера

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

1 голос
/ 16 сентября 2009

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

0 голосов
/ 14 сентября 2009

Может быть установлено свойство привязки UpdateSourceTrigger :

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

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

...