1

私は、最も基本的なレベルで、多くの TextBoxes を含む StackPanel (Orientation=Vertical) を持つ ScrollViewer であるコントロールを持っています。

<ScrollViewer>
    <StackPanel x:Name="MyStackPanel"
                Orientation="Vertical">
        <TextBox Text="{Binding PropertyA, ValidatesOnDataErrors=True}" />
        <TextBox Text="{Binding PropertyB, ValidatesOnDataErrors=True}" />
        <TextBox Text="{Binding PropertyC, ValidatesOnDataErrors=True}" />
        <!-- ... -->
        <TextBox Text="{Binding PropertyX, ValidatesOnDataErrors=True}" />
        <TextBox Text="{Binding PropertyY, ValidatesOnDataErrors=True}" />
        <TextBox Text="{Binding PropertyZ, ValidatesOnDataErrors=True}" />
    </StackPanel>
</ScrollViewer>

エラーが発生したときに、エラーのあるコントロールをスクロールして表示したい。したがって、たとえば、ユーザーがリストの一番上にあり、PropertyX にバインドされた TextBox がエラーになっている場合、ScrollViewer をそこまでスクロールさせます。

現在、ScrollViewer から継承し、次のメソッドを追加しました。

    public void ScrollErrorTextBoxIntoView()
    {
        var controlInError = GetFirstChildControlWithError(this);

        if (controlInError == null)
        {
            return;
        }            
            controlInError.BringIntoView();
        }
    }

    public Control GetFirstChildControlWithError(DependencyObject parent)
    {
        if (parent == null)
        {
            return null;
        }

        Control findChildInError = null;

        var children = LogicalTreeHelper.GetChildren(parent).OfType<DependencyObject>();

        foreach (var child in children)
        {
            var childType = child as Control;
            if (childType == null)
            {
                findChildInError = GetFirstChildControlWithError(child);

                if (findChildInError != null)
                {
                    break;
                }
            }
            else
            {
                var frameworkElement = child as FrameworkElement;

                // If the child is in error
                if (Validation.GetHasError(frameworkElement))
                {
                    findChildInError = (Control)child;
                    break;
                }
            }
        }

        return findChildInError;
    }

うまく動かせなくて困っています。私の見方では、2 つの選択肢があります。

  1. ScrollErrorTextBoxIntoView メソッドを実行する ViewModel の取得を試みます。それを行う最善の方法が何であるかはわかりません。私はプロパティを設定してそれから行動しようとしていましたが、それは正しくないように見えました (とにかくうまくいきませんでした)

  2. コントロールに自己完結型の方法で実行させます。これには、ScrollViewer がその子を (再帰的に) リッスンし、それらのいずれかがエラー状態にある場合はメソッドを呼び出す必要があります。

だから私の質問は:

  1. これら 2 つのオプションのどちらが優れていて、どのように実装しますか?

  2. これを行うより良い方法はありますか?(動作など?) MVVM である必要があります。

注意。GetFirstChildControlWithError は、この質問から適応されました。名前またはタイプで WPF コントロールを見つけるにはどうすればよいですか?

4

1 に答える 1

1

次の仮定の下で作業します。

  • あなたのビューモデルはINotifyPropertyChanged正しく実装されており、IDataErrorInfo
  • IDataErrorInfo.Error少なくとも 1 つのプロパティに検証エラーがある場合、プロパティは null ではありません。
  • M と VM の厳密な分離を維持したい。したがって、ViewModel は、ビューを調整するためだけに存在するメソッドを呼び出すべきではありません。

基本的に、DataContext プロパティの変更をリッスンし、DataError が存在するかどうかを調べます。

ビヘイビアを見ると、 から継承せずにこれを解決できますScrollViewer

実装例を次に示します。

using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

public class ScrollToFirstInvalidElementBehavior : Behavior<ScrollViewer>
{
    protected override void OnAttached()
    {
        ResetEventHandlers(null, AssociatedObject.DataContext);
        AssociatedObject.DataContextChanged += OnDataContextChanged;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.DataContextChanged -= OnDataContextChanged;
    }

    private void OnDataContextChanged(object sender, 
          DependencyPropertyChangedEventArgs e)
    {
        ResetEventHandlers(e.OldValue, e.NewValue);
    }

    private void ResetEventHandlers(object oldValue, object newValue)
    {
        var oldContext = oldValue as INotifyPropertyChanged;
        if (oldContext != null)
        {
            oldContext.PropertyChanged -= OnDataContextPropertyChanged;
        }

        var newContext = newValue as INotifyPropertyChanged;
        if (newContext is IDataErrorInfo)
        {
            newContext.PropertyChanged += OnDataContextPropertyChanged;
        }
    }

    private void OnDataContextPropertyChanged(object sender, 
         PropertyChangedEventArgs e)
    {
        var dataError = (IDataErrorInfo) sender;

        if (!string.IsNullOrEmpty(dataError.Error))
        {
            var controlInError = GetFirstChildControlWithError(AssociatedObject);
            if (controlInError != null)
            {
                controlInError.BringIntoView();
            }

        }
    }

    private Control GetFirstChildControlWithError(ScrollViewer AssociatedObject)
    {
        //...
    }
}
于 2013-10-18T14:21:07.523 に答える