4

.Net 4.5のDelayバインディングタグを使用していますが、変更が「コミット」されていないときにテキストボックスの背景色を変更したいと思います。遅延が発生しているときにIsDirtyプロパティをtrueに設定するにはどうすればよいですか?

TextChangedイベントを使用してIsDirtyフラグを設定し、バインドされたプロパティが設定されたときにフラグを削除してみました。問題は、ユーザーがテキストを変更したときだけでなく、バ​​インドされたプロパティが変更されたときにTextChangedが起動することです。

TextChangedイベントとバインドされたプロパティを監視することで、非常に不格好で壊れやすい方法で「機能」しました。言うまでもなく、これはバグが発生しやすいので、よりクリーンなソリューションが必要です。テキストボックスが変更されたが、まだコミットされていないことを知る方法はありますか(遅延によって)?

4

3 に答える 3

6

私はソースコードを調べましたが、BindingExpressionBaseそれ自体が。というプロパティを通じてこれを認識していますNeedsUpdate。ただし、このプロパティは内部的なものであるため、リフレクションを使用して取得する必要があります。

ただし、このプロパティを簡単な方法で監視することはできません。したがって、私の見方では、両方のイベントを使用し、いつ変更されたかを知る必要がTextChangedありSourceUpdatedますNeedsUpdate

更新
これを行う添付の動作を作成しました。これを使用して、任意ので保留中の更新を監視できますDependencyPropertytrueに設定する必要があることに注意してくださいNotifyOnSourceUpdated

ここに小さなサンプルプロジェクトをアップロードしました: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
}

別の方法はMultiBinding、ソースとターゲットの両方にバインドし、コンバーターでそれらの値を比較するを使用することです。Background次に、を変更できますStyleこれは、値を変換しないことを前提としています。2つの例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();
    }
}
于 2012-05-14T22:25:44.743 に答える
0

のような他のイベントや、関連する重要なイベントの1つを試すことができPreviewTextInputます。(バブリングイベントはおそらく内部で処理されるため、トンネリングバージョンが必要になる可能性があります)

于 2012-05-13T21:16:51.533 に答える
0

遅延バインディングについてはよくわかりません。ただし、.Net 4.0では、BindingGroupを使用します。BindingGroupにはCanRestoreValuesプロパティがあり、必要なものを正確に報告します。UpdateSourceTriggerがExplicit(BindingGroupがある場合はデフォルト)の場合、CanRestoreValuesは、1つのバインドされたコントロール値が変更されてからBindingGroup.CommitEditが呼び出されて値はバインドされたオブジェクトに転送されます。ただし、BindingGroupを共有するコントロールに保留中の値があるとすぐにCanRestoreValuesがtrueになるため、個別のプロパティごとにフィードバックが必要な場合は、コントロールごとに1つのBindingGroupを使用する必要があります。これにより、CommitEditの呼び出しが少し不便になります。

于 2012-05-14T18:19:34.110 に答える