0

新しいアイテムが追加されるたびに一番下までスクロールするItemsControlにアタッチされた動作があります。私はチャットタイプのプログラムに取り組んでいるので、ユーザーがスクロールバーを一番下以外の場所に持っている場合はスクロールしたくありません。そうしないと非常に煩わしくなります(一部のチャットプログラムはこれを実行し、ひどいです)。

どうすればこれを達成できますか?ラッピングするScrollViewerにアクセスする方法がわかりません。それ以外の場合は、それを表示する必要があるかどうかを判断します。

これは、StackOverflowの誰かから実際に取得した動作クラスです。私はまだ自分で行動について学んでいます。

public class ScrollOnNewItem : Behavior<ItemsControl>
{
    protected override void OnAttached()
    {
        AssociatedObject.Loaded += OnLoaded;
        AssociatedObject.Unloaded += OnUnLoaded;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Loaded -= OnLoaded;
        AssociatedObject.Unloaded -= OnUnLoaded;
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        var incc = AssociatedObject.ItemsSource as INotifyCollectionChanged;
        if (incc == null) return;

        incc.CollectionChanged += OnCollectionChanged;
    }

    private void OnUnLoaded(object sender, RoutedEventArgs e)
    {
        var incc = AssociatedObject.ItemsSource as INotifyCollectionChanged;
        if (incc == null) return;

        incc.CollectionChanged -= OnCollectionChanged;
    }

    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            int count = AssociatedObject.Items.Count;
            if (count == 0)
                return;

            var item = AssociatedObject.Items[count - 1];

            var frameworkElement = AssociatedObject.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
            if (frameworkElement == null) return;

            frameworkElement.BringIntoView();
        }
    }
}
4

2 に答える 2

3

さて、これが私が自分で思いついた答えです。

依存オブジェクトには GetSelfAndAncestors メソッドがあることがわかりました。それを使用して、AssociatedObject (ItemsControl) の ScrollViewer 祖先 (存在する場合) を取得し、それを操作することができます。

だから私は自分の行動にこのフィールドを追加しました

private ScrollViewer scrollViewer;
private bool isScrollDownEnabled;

そして、OnLoaded イベント ハンドラーで、次のコードを割り当てました。

scrollViewer = AssociatedObject.GetSelfAndAncestors().Where(a => a.GetType().Equals(typeof(ScrollViewer))).FirstOrDefault() as ScrollViewer;

OnCollectionChanged イベント ハンドラーでは、次のようにすべてのロジックを if ステートメントでラップしました。

        if (scrollViewer != null)
        {
            isScrollDownEnabled = scrollViewer.ScrollableHeight > 0 && scrollViewer.VerticalOffset + scrollViewer.ViewportHeight < scrollViewer.ExtentHeight;

            if (e.Action == NotifyCollectionChangedAction.Add && !isScrollDownEnabled)
            {
                 // Do stuff
            }
        }

まとめると、コードは次のようになります

public class ScrollOnNewItem : Behavior<ItemsControl>
{
    private ScrollViewer scrollViewer;
    private bool isScrollDownEnabled;

    protected override void OnAttached()
    {
        AssociatedObject.Loaded += OnLoaded;
        AssociatedObject.Unloaded += OnUnLoaded;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Loaded -= OnLoaded;
        AssociatedObject.Unloaded -= OnUnLoaded;
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        var incc = AssociatedObject.ItemsSource as INotifyCollectionChanged;
        if (incc == null) return;

        incc.CollectionChanged += OnCollectionChanged;
        scrollViewer = AssociatedObject.GetSelfAndAncestors().Where(a => a.GetType().Equals(typeof(ScrollViewer))).FirstOrDefault() as ScrollViewer;
    }

    private void OnUnLoaded(object sender, RoutedEventArgs e)
    {
        var incc = AssociatedObject.ItemsSource as INotifyCollectionChanged;
        if (incc == null) return;

        incc.CollectionChanged -= OnCollectionChanged;
    }

    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (scrollViewer != null)
        {
            isScrollDownEnabled = scrollViewer.ScrollableHeight > 0 && scrollViewer.VerticalOffset + scrollViewer.ViewportHeight < scrollViewer.ExtentHeight;

            if (e.Action == NotifyCollectionChangedAction.Add && !isScrollDownEnabled)
            {
                int count = AssociatedObject.Items.Count;
                if (count == 0)
                    return;

                var item = AssociatedObject.Items[count - 1];

                var frameworkElement = AssociatedObject.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
                if (frameworkElement == null) return;

                frameworkElement.BringIntoView();
            }
        }
    }
}

コメントで尋ねられたように、動作を使用するには、動作を含むコード内の領域の xaml ファイルに新しい xmlns を追加するだけです。

             xmlns:behaviors="clr-namespace:Infrastructure.Behaviors;assembly=Infrastructure"

次に、コントロールに動作を追加します。

    <ScrollViewer VerticalScrollBarVisibility="Auto">
        <ItemsControl Name="Blah" ItemsSource="{Binding Messages}" ItemTemplate="{StaticResource MessageTemplate}">
            <i:Interaction.Behaviors>
                <behaviors:ScrollOnNewItem />
            </i:Interaction.Behaviors>
        </ItemsControl>
    </ScrollViewer>

iクラスは単なるインタラクティブな名前空間です。xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

于 2012-09-04T00:17:14.780 に答える
1

この動作を実装する別の方法があります。この方法は上記よりも簡単です。以下のようなメソッドを呼び出すだけです。

public void AppendText(RichTextBox richTextBox, string data){       
   richTextBox.AppendText(data);
   bool isScrollDownEnabled = richTextBox.VerticalOffset == 0 ||
        richTextBox.VerticalOffset + richTextBox.ViewportHeight == richTextBox.ExtentHeight;
   if (isScrollDownEnabled)
       richTextBox.ScrollToEnd();
}

にも適していTextBoxます。

于 2013-01-24T02:38:32.430 に答える