2012-04-30 2 views
4

Я использую тэг привязки Delay .Net 4.5, но я хочу изменить цвет фона текстового поля, пока изменения не «зафиксированы». Как установить свойство IsDirty в значение true во время задержки?Как узнать, когда изменяется текстовое поле?

Я попытался использовать событие TextChanged, чтобы установить флаг IsDirty, а затем удалить флаг, когда свойство привязки было установлено. Проблема в том, что TextChanged срабатывает всякий раз, когда свойство bound изменяется, а не только когда пользователь изменяет текст.

Я получил «работу» очень неуклюжим и хрупким способом, отслеживая событие TextChanged и связанное свойство. Излишне говорить, что это очень подвержено ошибкам, поэтому я хотел бы получить более чистое решение. Есть ли способ узнать, что текстовое поле было изменено, но еще не выполнено (по Delay)?

ответ

5

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

Тем не менее, вы не сможете точно отслеживать это имущество. Поэтому, как я вижу, вам нужно будет использовать оба события TextChanged и SourceUpdated, чтобы знать, когда NeedsUpdate, возможно, изменился.

Update
Я создал присоединенное поведение, которое делает это, он может быть использован для отслеживания отложенных обновлений на любом DependencyProperty. Обратите внимание, что NotifyOnSourceUpdated должно быть установлено значение true.

Загрузил небольшой образец проекта здесь: PendingUpdateExample.zip

Пример

<TextBox Text="{Binding ElementName=textBoxSource, 
         Path=Text, 
         NotifyOnSourceUpdated=True, 
         UpdateSourceTrigger=PropertyChanged, 
         Delay=1000}" 
     ab:UpdatePendingBehavior.MonitorPendingUpdates="{x:Static TextBox.TextProperty}"> 
    <TextBox.Style> 
     <Style TargetType="TextBox"> 
      <Style.Triggers> 
       <Trigger Property="ab:UpdatePendingBehavior.HasPendingUpdates" 
         Value="True"> 
        <Setter Property="Background" Value="Green"/> 
       </Trigger> 
      </Style.Triggers> 
     </Style> 
    </TextBox.Style> 
</TextBox> 

UpdatePendingBehavior

public class UpdatePendingBehavior 
{ 
    #region MonitorPendingUpdates 

    public static DependencyProperty MonitorPendingUpdatesProperty = 
     DependencyProperty.RegisterAttached("MonitorPendingUpdates", 
              typeof(DependencyProperty), 
              typeof(UpdatePendingBehavior), 
              new UIPropertyMetadata(null, MonitorPendingUpdatesChanged)); 

    public static DependencyProperty GetMonitorPendingUpdates(FrameworkElement obj) 
    { 
     return (DependencyProperty)obj.GetValue(MonitorPendingUpdatesProperty); 
    } 
    public static void SetMonitorPendingUpdates(FrameworkElement obj, DependencyProperty value) 
    { 
     obj.SetValue(MonitorPendingUpdatesProperty, value); 
    } 

    public static void MonitorPendingUpdatesChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) 
    { 
     DependencyProperty property = e.NewValue as DependencyProperty; 
     if (property != null) 
     { 
      FrameworkElement element = target as FrameworkElement; 
      element.SourceUpdated += elementProperty_SourceUpdated; 

      if (element.IsLoaded == true) 
      { 
       SubscribeToChanges(element, property); 
      } 
      element.Loaded += delegate { SubscribeToChanges(element, property); }; 
      element.Unloaded += delegate { UnsubscribeToChanges(element, property); }; 
     } 
    } 

    private static void SubscribeToChanges(FrameworkElement element, DependencyProperty property) 
    { 
     DependencyPropertyDescriptor propertyDescriptor = 
      DependencyPropertyDescriptor.FromProperty(property, element.GetType()); 
     propertyDescriptor.AddValueChanged(element, elementProperty_TargetUpdated); 
    } 

    private static void UnsubscribeToChanges(FrameworkElement element, DependencyProperty property) 
    { 
     DependencyPropertyDescriptor propertyDescriptor = 
       DependencyPropertyDescriptor.FromProperty(property, element.GetType()); 
     propertyDescriptor.RemoveValueChanged(element, elementProperty_TargetUpdated); 
    } 

    private static void elementProperty_TargetUpdated(object sender, EventArgs e) 
    { 
     FrameworkElement element = sender as FrameworkElement; 
     UpdatePendingChanges(element); 
    } 

    private static void elementProperty_SourceUpdated(object sender, DataTransferEventArgs e) 
    { 
     FrameworkElement element = sender as FrameworkElement; 
     if (e.Property == GetMonitorPendingUpdates(element)) 
     { 
      UpdatePendingChanges(element); 
     } 
    } 

    private static void UpdatePendingChanges(FrameworkElement element) 
    { 
     BindingExpressionBase beb = BindingOperations.GetBindingExpressionBase(element, GetMonitorPendingUpdates(element)); 
     BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic; 
     PropertyInfo needsUpdateProperty = beb.GetType().GetProperty("NeedsUpdate", bindingFlags); 
     SetHasPendingUpdates(element, (bool)needsUpdateProperty.GetValue(beb)); 
    } 

    #endregion // MonitorPendingUpdates 

    #region HasPendingUpdates 

    public static DependencyProperty HasPendingUpdatesProperty = 
     DependencyProperty.RegisterAttached("HasPendingUpdates", 
              typeof(bool), 
              typeof(UpdatePendingBehavior), 
              new UIPropertyMetadata(false)); 

    public static bool GetHasPendingUpdates(FrameworkElement obj) 
    { 
     return (bool)obj.GetValue(HasPendingUpdatesProperty); 
    } 
    public static void SetHasPendingUpdates(FrameworkElement obj, bool value) 
    { 
     obj.SetValue(HasPendingUpdatesProperty, value); 
    } 

    #endregion // HasPendingUpdates 
} 

Другой способ сотрудничества uld следует использовать MultiBinding, который связывает оба источника и цель и сравнивает их значения в конвертере. Затем вы можете изменить Background в Style. Предполагается, что вы не конвертируете значение.Пример с двумя TextBoxes

<TextBox Text="{Binding ElementName=textBoxSource, 
         Path=Text, 
         UpdateSourceTrigger=PropertyChanged, 
         Delay=2000}"> 
    <TextBox.Style> 
     <Style TargetType="TextBox"> 
      <Style.Triggers> 
       <DataTrigger Value="False"> 
        <DataTrigger.Binding> 
         <MultiBinding Converter="{StaticResource IsTextEqualConverter}"> 
          <Binding RelativeSource="{RelativeSource Self}" 
            Path="Text"/> 
          <Binding ElementName="textBoxSource" Path="Text"/> 
         </MultiBinding> 
        </DataTrigger.Binding> 
        <Setter Property="Background" Value="Green"/> 
       </DataTrigger> 
      </Style.Triggers> 
     </Style> 
    </TextBox.Style> 
</TextBox> 
<TextBox Name="textBoxSource"/> 

IsTextEqualConverter

public class IsTextEqualConverter : IMultiValueConverter 
{ 
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 
    { 
     return values[0].ToString() == values[1].ToString(); 
    } 
    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 
    { 
     throw new NotSupportedException(); 
    } 
} 
0

Вы можете попробовать другое событие, например, PreviewTextInput или один из ключевых. (Вероятно, вам нужны версии tunneling, поскольку события с пузырьками, вероятно, обрабатываются внутренне)

+0

Это не поймать пасту на текстовом поле. – Manuel

+0

@ Manuel: Понятно, это было бы проблемой ... –

0

Я не уверен в привязке к задержке. Однако в .Net 4.0 я бы использовал BindingGroup. BindingGroup имеет свойство CanRestoreValues, которое будет сообщать вам именно то, что вы хотите: если UpdateSourceTrigger является Явным (это по умолчанию, если есть BindingGroup), CanRestoreValues ​​будет истинным с того момента, когда одно связанное значение управления было изменено до тех пор, пока не вызывается BindingGroup.CommitEdit и значения передаются связанному объекту. Однако CanRestoreValues ​​будет правдой, как только любой элемент управления, который разделяет BindingGroup, имеет ожидающее значение, поэтому, если вам нужна обратная связь для каждого отдельного свойства, вам придется использовать одну BindingGroup для каждого элемента управления, что делает вызов CommitEdit немного менее удобным.

Смежные вопросы