Visual Studio | WPF > Behavior > 複数行のTextBox > 最終行に移動する(自動スクロール)実装

```txt:動作環境
Windows 8.1 Pro (64bit)
Microsoft Visual Studio 2017 Community
Sublime Text 2
```

### 実装内容

ロギングなどで複数行のTextBoxの表示をする場合、1行追加のたびに最終行が表示されることが望ましい場合がある。

[C++ Builder | TMemo > 最終行に移動する実装](https://qiita.com/7of9/items/e64243405b4ffcc8e9aa)

WPFではどう実装するか調べた。
https://stackoverflow.com/questions/7010462/how-to-make-wpf-textbox-with-a-scrollbar-automatically-scroll-to-the-bottom-when/7010608
> answered Aug 10 '11 at 12:14
bitbonk

>You could write an attached property or even better a behavior that listens to the TextChanged event and scrolls to the bottom in the callback.

Behavior を使ってみる。

### 参考

[添付ビヘイビアを使い TextBox で数値以外を入力できなくする。](http://d.hatena.ne.jp/hilapon/20101021/1287641423) by hilaponさん
にBehaviorの使用例があり、これを元に自動スクロールにしてみる。

### code 

```MainWindow.xaml.cs
<Window x:Class="_171207_t1930_behavior.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_171207_t1930_behavior"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <StackPanel>
            <Button Name="B_add" Content="Add" Height="30" Click="B_add_Click"/>
            <TextBox Name="T_memo" Height="250" Width="500"
                     VerticalScrollBarVisibility="Visible"
                     local:TextBoxBehaviors.AutoScrollToEnd="True"/>
        </StackPanel>
    </Grid>
</Window>
```

```MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace _171207_t1930_behavior
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void B_add_Click(object sender, RoutedEventArgs e)
        {
            T_memo.Text += "HELLO" + System.Environment.NewLine;
        }
    }

}
```

```TextBoxBehavior.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 以下を追加した
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace _171207_t1930_behavior
{
    /// <summary>
    /// TextBox 添付ビヘイビア
    /// </summary>
    public class TextBoxBehaviors
    {
        /// <summary>
        /// 複数行のテキストを扱う
        /// テキスト追加時に最終行が表示されるようにする
        /// </summary>
        public static readonly DependencyProperty AutoScrollToEndProperty =
                    DependencyProperty.RegisterAttached(
                        "AutoScrollToEnd", typeof(bool),
                        typeof(TextBoxBehaviors),
                        new UIPropertyMetadata(false, IsTextChanged)
                    );

        [AttachedPropertyBrowsableForType(typeof(TextBox))]
        public static bool GetAutoScrollToEnd(DependencyObject obj)
        {
            return (bool)obj.GetValue(AutoScrollToEndProperty);
        }

        public static void SetAutoScrollToEnd(DependencyObject obj, bool value)
        {
            obj.SetValue(AutoScrollToEndProperty, value);
        }

        private static void IsTextChanged
            (DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var textBox = sender as TextBox;
            if (textBox == null) return;

            // イベントを登録・削除 
            textBox.TextChanged -= OnTextChanged;

            Console.Write("-");

            var newValue = (bool)e.NewValue;
            if (newValue)
            {
                textBox.TextChanged += OnTextChanged;
            }
        }

        private static void OnTextChanged(object sender, TextChangedEventArgs e)
        {
            var textBox = sender as TextBox;
            if (textBox == null) return;

            if (string.IsNullOrEmpty(textBox.Text))
            {
                return;
            }

            textBox.ScrollToEnd();
            Console.Write("*");
        }
    }
}
```

### 実行例

自動スクロールになった。

![qiita.png](https://qiita-image-store.s3.amazonaws.com/0/32870/984a3716-2739-2fd4-4693-7a5ab0cc644f.png)

デバッグウィンドウに表示される項目は下記である。

- `-`: 起動時に表示される
    - イベント登録処理
- `*`: TextChangedごとに表示される

### 補足

Behaviorを使うことで、MVVMにした場合にも動作するようになると考えている。

### 関連

- [Visual Studio | WPF > カスタム添付プロパティの使用例 | link:AttachedPropertyBrowsableForType](https://qiita.com/7of9/items/eff609eeb0b7c4381d0b)
- [Visual Studio | WPF > カスタム添付プロパティ > AttachedPropertyBrowsableForType > Getには指定して、Setには指定しない](https://qiita.com/7of9/items/e8d639ea94eeba42d8e9)
- [Visual Studio | WPF > カスタム添付プロパティ > AttachedPropertyBrowsableForType の効果 > デザイン時にプロパティウィンドウに表示するかどうか](https://qiita.com/7of9/items/a512e16ecff1d8c74304)