none
WPFのTextBox内に日本語入力する時のキャレットの挙動がおかしい RRS feed

  • 質問

  • TextBoxコントロールをXAML内に
        <TextBox AcceptsReturn="True" Height="100" />
    と記述して配置したとします。

    TextBox 内の入力で、
    1行目の入力は Enter で改行し、
    2行目の先頭で日本語入力(IME)を On にして、
    「あ」と入力した後、確定せずに Back Space キーを押した場合、
    「あ」の文字は削除され、キャレットの
    位置は2行目の先頭に移動するのが正常な動作だと思いますが、
    どういうわけか、キャレットの位置は1行目に移動してしまいます。

    また、IME の代わりに ATOK (手元にあるのは ATOK 2014) を
    使った場合も挙動がおかしいことがあります。
    1行目の入力は Enter で改行し、
    2行目の先頭で日本語入力(ATOK)を On にして、
    「あい」と入力して、スペースキーで「愛」に変換した時、
    キャレットの位置が1行目に移動してしまいます。

    TextBoxコントロール側のバグだと思うのですが、いかがでしょうか?

    前者のIMEの場合は、CaretIndex に
    PreviewTextInput のタイミングで正しい値をセットすること
    でなんとか回避できたのですが、
    後者のATOKの場合は、お手上げ状態です。

    キャレットの挙動を正常にしたいのですが、
    なにかよい解決方法をご存知でしたらご教授ください。
    よろしくお願いいたします。

    なお、こちらの開発環境は以下の通りです。
        Visual Studio 2017 Community
        .NET 4.5.1
        WPF
        Windows7 Pro

    ATOK の動作確認については Windows 10 上で行いました。
    .NET 4.7 も試してみましたが、症状は同じでした。

    2017年10月10日 7:30

回答

  • その後、ゼロ幅スペースを使わない方法を思いつきました。

    一時的にキャレットを透明にすることで不具合を回避できました。

    <Window x:Class="WpfApp1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:app="clr-namespace:WpfApp1"
            Title="MainWindow" Height="350" Width="525">
        <StackPanel>
            <TextBlock Text="対策なし"/>
            <TextBox x:Name="textBox1" Height="100" AcceptsReturn="true" Margin="5"
                    app:TextBoxTool.ImeFix="false"/>
            <TextBlock Text="対策あり"/>
            <TextBox x:Name="textBox2" Height="100" AcceptsReturn="true" Margin="5"
                    app:TextBoxTool.ImeFix="true"/>
        </StackPanel>
    </Window>
    
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Threading;
    
    namespace WpfApp1
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
        }
    
        class TextBoxTool
        {
            private static bool imeStart;
            private static bool fixTarget;
    
            static TextBoxTool()
            {
                imeStart = false;
                fixTarget = false;
            }
    
            public static bool GetImeFix(DependencyObject obj)
            {
                return (bool)obj.GetValue(ImeFixProperty);
            }
    
            public static void SetImeFix(DependencyObject obj, bool value)
            {
                obj.SetValue(ImeFixProperty, value);
            }
    
            public static readonly DependencyProperty ImeFixProperty
                = DependencyProperty.RegisterAttached
                ("ImeFix"
                , typeof(bool)
                , typeof(TextBoxTool)
                , new UIPropertyMetadata(default(bool), new PropertyChangedCallback(OnImeFixPropertyChanged)));
    
            private static void OnImeFixPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e)
            {
                if (dpo is TextBox target) {
                    bool newValue = (bool)e.NewValue;
                    bool oldValue = (bool)e.OldValue;
    
                    if (oldValue) {
                        target.TextChanged -= TextBox_TextChanged;
                        TextCompositionManager.RemovePreviewTextInputHandler(target, TextBox_PreviewTextInput);
                        TextCompositionManager.RemoveTextInputUpdateHandler(target, TextBox_TextInputUpdate);
                    }
                    if (newValue) {
                        target.TextChanged += TextBox_TextChanged;
                        TextCompositionManager.AddPreviewTextInputHandler(target, TextBox_PreviewTextInput);
                        TextCompositionManager.AddTextInputUpdateHandler(target, TextBox_TextInputUpdate);
                    }
                }
            }
    
            static void TextBox_TextChanged(object sender, TextChangedEventArgs e)
            {
                var tb = sender as TextBox;
    
                if (fixTarget) {
                    tb.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
                    {
                        int i = tb.CaretIndex;
    
                        tb.CaretBrush = null;
                        tb.CaretIndex = i;
                    }));
                }
            }
    
            static void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
            {
                if (imeStart) {
                    imeStart = false;
                    fixTarget = false;
                }
            }
    
            static void TextBox_TextInputUpdate(object sender, TextCompositionEventArgs e)
            {
                var tb = sender as TextBox;
    
                if (imeStart) {
                    if (fixTarget) {
                        tb.CaretBrush = new SolidColorBrush(Color.FromArgb(0, 0, 0, 0));
                    }
                }
                else {
                    int i = tb.CaretIndex;
    
                    if (i > 0 && tb.Text[i - 1] == '\n') {
                        fixTarget = true;
                    }
                    imeStart = true;
                }
            }
        }
    }
    

    2017年10月16日 6:26

すべての返信

  • .NET Framework 4.5.1辺りには、確かにTextBox周りでキャレット系の不具合はあったと記憶していますが、.NET Framework 4.7では解消しているはずです。
    #確か、.NET Framework 4.6.2で解消されたはずです。

    プロジェクトのターゲットではなく、実際に動作するクライントに.NET Framework 4.7がインストールされている必要がありますが、その辺りは大丈夫でしょうか?
    私の方で以下の環境で試してみましたが、おっしゃるような不具合は再現できませんでした。
    ・Windows 7 pro 32bit, Visual Studio 2017 Community, .NET Framework 4.7
    ・Windows 10 pro 64bit, Visual Studio Enterprise 2017, .NET Framework 4.7

    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    2017年10月11日 1:03
    モデレータ
  • trapemiya 様

    ご回答ありがとうございます。

    クライントに.NET Framework 4.7はインストールしてあります。

    こちらのWindows 7 Pro のレジストリ
    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full
    の値は
        Release: 0x00070805 (460805)
        Version: 4.7.02053
    となっています。(他になにか確認すべき点がありましたら教えてください。)

    以下の3つの環境で再度試してみましたが、IME入力の症状は100%再現します。
        Windows 7 pro 32bit, Visual Studio 2017 Community, .NET Framework 4.7
        Windows 7 pro 64bit, Visual Studio 2017 Community, .NET Framework 4.7
        Windows 10 Home 64bit, Visual Studio 2017 Community, .NET Framework 4.7

    他の方はいかがでしょうか?


    • 編集済み murataya 2017年10月16日 1:56
    2017年10月11日 4:23
  • 症状の再現方法がうまく伝わっていないのかもしれないので補足説明いたします。

    2行目の先頭でIMEを On にした直後は、
        1. [A]
        2. [Back Space]
    の順にキーを押してください。

    [A] キーを押すと画面上には「あ」と表示されますが、
    [Enter] で確定しないでください。

    2017年10月11日 6:14
  • murataya さま よろしく。

    実行環境ではなく、プロジェクトのターゲット framework は変更なさいましたか?。

    2017年10月11日 9:14
  • 以下の組み合わせ全てで再現しました。
    MS-IME+Win7 x86/x64 , MS-IME+Win8.1 x64 , MS-IME+Win10 x64
    ATOK2017(体験版)+Win10 x64

    変換中の状態で、入力行の行頭にカーソルが移動できないんですかね

    とりあえず改行コードにゼロ幅スペースをくっつけて、行頭にも移動するように見せかけてみる。(言語不明なのでC#)

    <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:app="clr-namespace:WpfApplication1"
            Title="MainWindow" Height="350" Width="525">
        <StackPanel>
            <TextBlock Text="未対策"/>
            <TextBox x:Name="textBox1" Height="100" AcceptsReturn="true" Margin="5"
                    app:TextBoxTool.AppendZeroWidthReturn="false"/>
    
            <TextBlock Text="ゼロ幅スペース付き"/>
            <TextBox x:Name="textBox2" Height="100" AcceptsReturn="true" Margin="5"
                    app:TextBoxTool.AppendZeroWidthReturn="true"/>
        </StackPanel>
    </Window>
    using System;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    
    namespace WpfApplication1
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
        }
    
    
        class TextBoxTool
        {
            #region 添付プロパティ
    
            public static bool GetAppendZeroWidthReturn(DependencyObject obj)
            {
                return (bool)obj.GetValue(AppendZeroWidthReturnProperty);
            }
    
            public static void SetAppendZeroWidthReturn(DependencyObject obj, bool value)
            {
                obj.SetValue(AppendZeroWidthReturnProperty, value);
            }
    
            /// <summary>改行コードにゼロ幅を付加するか指定する</summary>
            public static readonly DependencyProperty AppendZeroWidthReturnProperty
                = DependencyProperty.RegisterAttached
                ("AppendZeroWidthReturn"
                , typeof(bool)
                , typeof(TextBoxTool)
                , new UIPropertyMetadata(default(bool), new PropertyChangedCallback(OnAppendZeroWidthReturnPropertyChanged)));
    
    
            private static void OnAppendZeroWidthReturnPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e)
            {
                TextBox target = dpo as TextBox;
                if (target != null)
                {
                    bool newValue = (bool)e.NewValue;
                    bool oldValue = (bool)e.OldValue;
    
                    if (oldValue)
                    {
                        target.PreviewKeyDown -= TextBox_PreviewKeyDown;
                        target.CommandBindings.Remove(cbPaste);
                        target.CommandBindings.Remove(cbCopy);
    
                        TextCompositionManager.RemoveTextInputStartHandler(target, TextBox_TextInputStart);
                        TextCompositionManager.RemovePreviewTextInputHandler(target, TextBox_PreviewTextInput);
                    }
                    if (newValue)
                    {
                        target.PreviewKeyDown += TextBox_PreviewKeyDown;
                        target.CommandBindings.Add(cbPaste);
                        target.CommandBindings.Add(cbCopy);
    
                        TextCompositionManager.AddTextInputStartHandler(target, TextBox_TextInputStart);
                        TextCompositionManager.AddPreviewTextInputHandler(target, TextBox_PreviewTextInput);
    
    
                    }
                }
            }
    
            static void TextBox_TextInputStart(object sender, TextCompositionEventArgs e)
            {
                var txb = (TextBox)sender;
                SetComposition(txb, e.TextComposition);
                System.Diagnostics.Debug.WriteLine(GetComposition(txb));
            }
            static void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
            {
                var txb = (TextBox)sender;
                SetComposition(txb, null);
                System.Diagnostics.Debug.WriteLine(GetComposition(txb));
    
            }
    
            #endregion
    
    
            private static TextComposition GetComposition(DependencyObject obj)
            {
                return (TextComposition)obj.GetValue(CompositionProperty);
            }
    
            private static void SetComposition(DependencyObject obj, TextComposition value)
            {
                obj.SetValue(CompositionProperty, value);
            }
    
            private static readonly DependencyProperty CompositionProperty
                = DependencyProperty.RegisterAttached("Composition", typeof(TextComposition), typeof(TextBoxTool), new UIPropertyMetadata(default(TextComposition)));
    
            ///// <summary>ゼロ幅スペース</summary>
            //#if DEBUG
            //        public const char ZEROWIDTH = '#';
            //#else
            public const char ZEROWIDTH = (char)0x200B;
            //#endif
    
            /// <summary>通常の改行コードの後ろにゼロ幅スペース(2行目以降の行頭にゼロ幅スペースを埋め込む)</summary>
            public static readonly string NewLineZero = System.Environment.NewLine + ZEROWIDTH;
            public static readonly int NewLineZeroLength = NewLineZero.Length;
    
            private static System.Reflection.PropertyInfo piTextSelectionInternal;
            private static System.Reflection.PropertyInfo piAnchorPosition;
    
            static TextBoxTool()
            {
                piTextSelectionInternal = typeof(TextBox).GetProperty("TextSelectionInternal", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy);
                piAnchorPosition = typeof(System.Windows.Documents.TextSelection).GetProperty("AnchorPosition", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
            }
    
    
            /// <summary>範囲選択のアンカー位置を取得</summary>
            public static TextPointer GetAnchorPointer(TextBox txb)
            {
                if (piTextSelectionInternal != null && piAnchorPosition != null)
                {
                    var sel = piTextSelectionInternal.GetValue(txb) as System.Windows.Documents.TextSelection;
                    if (sel != null)
                    {
                        var anchor = piAnchorPosition.GetValue(sel) as System.Windows.Documents.TextPointer;
                        return anchor;
                    }
                }
                return null;
            }
    
    
            /// <summary>ペーストコマンド</summary>
            private static CommandBinding cbPaste = new System.Windows.Input.CommandBinding(ApplicationCommands.Paste, (s, e) =>
            {
                var t = (TextBox)s;
                ExpandNewline(t);
    
                var text = Clipboard.GetText().Replace(System.Environment.NewLine, NewLineZero);
                t.SelectedText = text;
                e.Handled = true;
            });
    
            /// <summary>コピーコマンド</summary>
            private static CommandBinding cbCopy = new System.Windows.Input.CommandBinding(ApplicationCommands.Copy, (s, e) =>
            {
                var t = (TextBox)s;
                var text = t.SelectedText.Replace(ZEROWIDTH.ToString(), string.Empty);
                Clipboard.SetText(text);
                e.Handled = true;
            });
    
            /// <summary>改行コードの選択をゼロ幅スペースまで広げる</summary>
            /// <param name="t"></param>
            private static void ExpandNewline(TextBox t)
            {
                if (t.SelectedText.Length > 0 && t.SelectedText[0] == ZEROWIDTH)
                {
                    t.SelectionStart++;
                    t.SelectionLength--;
                }
                if (t.SelectedText.EndsWith(System.Environment.NewLine))
                {
                    var x = GetTailNext(t);
                    if (x.Length > 0 && x[0] == ZEROWIDTH)
                    {
                        t.SelectionLength++;
                    }
                }
            }
    
            /// <summary>選択範囲の次の文字</summary>
            private static string GetTailNext(TextBox txb)
            {
                int p = txb.SelectionStart + txb.SelectionLength;
                int len = Math.Max(0, Math.Min(System.Environment.NewLine.Length + 1, txb.Text.Length - p));
                return txb.Text.Substring(p, len);
            }
    
    
            private static void TextBox_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
            {
                var txb = (TextBox)sender;
                if (!txb.AcceptsReturn)
                {
                    return;
                }
    
                switch (e.Key)
                {
                    case System.Windows.Input.Key.Return:
    
                        {
                            ExpandNewline(txb);
    
                            var comp = GetComposition(txb);
                            if (Keyboard.Modifiers == ModifierKeys.Shift && comp != null && InputMethod.GetIsInputMethodEnabled(txb))
                            {
                                //IME入力途中でShit+Enter
                                InputMethod.SetIsInputMethodEnabled(txb, false);
                                InputMethod.SetIsInputMethodEnabled(txb, true);
                            }
    
                            int p = txb.SelectionStart;
                            txb.SelectedText = NewLineZero;
                            txb.Select(p + NewLineZeroLength, 0);
    
                            e.Handled = true;
                        }
                        break;
                    case System.Windows.Input.Key.Back:
                        {
                            ExpandNewline(txb);
    
                            if (txb.SelectionStart < NewLineZeroLength)
                            {
                                return;
                            }
                            if (txb.Text.Substring(txb.SelectionStart - NewLineZeroLength, NewLineZeroLength) == NewLineZero)
                            {
                                int p = txb.SelectionStart;
                                if (p == txb.Text.Length)
                                {
                                    txb.Text = txb.Text.Substring(0, p - NewLineZeroLength);
                                }
                                else
                                {
                                    txb.Text = txb.Text.Substring(0, p - NewLineZeroLength) + txb.Text.Substring(p);
                                }
                                txb.Select(p - NewLineZeroLength, 0);
                                e.Handled = true;
                            }
                        }
                        break;
                    case System.Windows.Input.Key.Delete:
                        {
                            if (txb.SelectionLength > 0)
                            {
                                ExpandNewline(txb);
                            }
                            else
                            {
                                string tail = GetTailNext(txb);
                                if (tail.StartsWith(System.Environment.NewLine))
                                {
                                    txb.SelectionLength = System.Environment.NewLine.Length;
                                    tail = GetTailNext(txb);
                                    if (tail.Length > 0 && tail[0] == ZEROWIDTH)
                                    {
                                        txb.SelectionLength++;
                                    }
                                }
                            }
                        }
                        break;
                    case Key.Left:
                        {
                            if (txb.SelectionStart > 0 && txb.SelectionLength > 0)
                            {
                                var pAnchor = GetAnchorPointer(txb);
                                if (pAnchor != null)
                                {
                                    var pCaret = pAnchor.DocumentStart.GetPositionAtOffset(txb.CaretIndex);
                                    if (pCaret.CompareTo(pAnchor) < 0)
                                    {
                                        var list = System.Environment.NewLine.Skip(1).ToList();
                                        list.Add(ZEROWIDTH);
                                        //行頭に向かって範囲拡張の場合
                                        do
                                        {
                                            EditingCommands.SelectLeftByCharacter.Execute(null, txb);
                                        } while (txb.SelectionStart > 0 && list.Contains(txb.SelectedText[0]));
                                        e.Handled = true;
                                    }
                                }
                            }
                        }
                        break;
                    default:
                        ExpandNewline(txb);
                        break;
                }
            }
        }
    }

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2017年10月11日 12:52
  • murataya さま よろしく。

    実行環境ではなく、プロジェクトのターゲット framework は変更なさいましたか?。

    もちろんです。ターゲット フレームワークの項目は「.NET Framework 4.7」となっています。

    ShiroYuki_Mot 様の環境では再現しないのでしょうか?

    2017年10月12日 0:24
  • 以下の組み合わせ全てで再現しました。
    MS-IME+Win7 x86/x64 , MS-IME+Win8.1 x64 , MS-IME+Win10 x64
    ATOK2017(体験版)+Win10 x64

    変換中の状態で、入力行の行頭にカーソルが移動できないんですかね

    とりあえず改行コードにゼロ幅スペースをくっつけて、行頭にも移動するように見せかけてみる。(言語不明なのでC#)

    gekka 様

    ご回答ありがとうございます。書き忘れていたのですが言語はC#であっています。

    再現したとのことで、とりあえず自分だけの症状ではないことがわかり安心しました。
    ソースコードまで付けていただいて感激です。
    ゼロ幅スペースをくっつけるという発想はなかったので勉強になりました。

    載せていただいたコードを早速コピペして、実行してみたのですが、
    どうも副作用があるようです。

    「あ」+「Back Space」の入力を「ゼロ幅スペース付き」TextBoxの中で行った後、
    「未対策」TextBoxの中で行い、もう1度「ゼロ幅スペース付き」TextBoxの中で行う
    と症状が再発します。

    また、「ゼロ幅スペース付き」TextBox内でカーソルキーの左(←)、右(→)を連射して
    キャレットを移動させていると、たまにキャレットが移動しなくなることがあります。

    • 編集済み murataya 2017年10月16日 1:52
    2017年10月12日 0:44
  • 副作用とうのは、CrLfとゼロ幅スペースの間にカーソルがある場合の処理が足らなかったからですかね

    private static void TextBox_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
    {
        var txb = (TextBox)sender;
        if (!txb.AcceptsReturn)
        {
            return;
        }
    
        if (e.Key != Key.Left)
        {
            if (txb.SelectionLength == 0)
            {
                if (GetTailNext(txb).StartsWith(ZEROWIDTH.ToString()))
                {
                    txb.SelectionStart++;
                    System.Diagnostics.Debug.WriteLine("HIT");
                }
            }
            else if (txb.SelectedText[0] == ZEROWIDTH)
            {
                txb.SelectionStart++;
                txb.SelectionLength--;
                System.Diagnostics.Debug.WriteLine("HIT");
            }
        }
    (略)
    ゼロ幅スペースに対する処理は不完全なので、実用したい場合はそれなりに手を入れてください。
    ZEROWIDTHを目に見える文字に設定すればデバッグしやすくなります。

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2017年10月12日 3:39
  • 副作用とうのは、CrLfとゼロ幅スペースの間にカーソルがある場合の処理が足らなかったからですかね

    (中略)

    ゼロ幅スペースに対する処理は不完全なので、実用したい場合はそれなりに手を入れてください。
    ZEROWIDTHを目に見える文字に設定すればデバッグしやすくなります。

    gekka 様

    ありがとうございます。了解いたしました。

    2017年10月12日 4:24
  • .NET Framework 4.5.1辺りには、確かにTextBox周りでキャレット系の不具合はあったと記憶していますが、.NET Framework 4.7では解消しているはずです。
    #確か、.NET Framework 4.6.2で解消されたはずです。

    プロジェクトのターゲットではなく、実際に動作するクライントに.NET Framework 4.7がインストールされている必要がありますが、その辺りは大丈夫でしょうか?
    私の方で以下の環境で試してみましたが、おっしゃるような不具合は再現できませんでした。
    ・Windows 7 pro 32bit, Visual Studio 2017 Community, .NET Framework 4.7
    ・Windows 10 pro 64bit, Visual Studio Enterprise 2017, .NET Framework 4.7

    trapemiya 様

    .NET Framework 4.6.2 Release Notes

    を確認してみましたが、該当する記述は見つかりませんでした。
    ([177621]や[245230] とかは別の不具合です。)
    その他(net46, net461, net47 と net471)のリリースノートも確認しましたが、
    該当する記述は見つかりませんでした。

    「.NET Framework 4.7では解消しているはず」との情報は本当なのでしょうか?

    また、そちらの環境では再現しないとのことですが、なにか特殊な環境
    (例えば、英語版のOSやキーボードを使用しているとか...)
    ということはないでしょうか?

    <本記事をご覧になった方へ>

    今のところ、再現したという人はgekka様のみで、
    TextBox側の不具合なのかどうかが明確になっていません。

    「再現した」あるいは「再現しない」の情報をいただけると幸いです。


    • 編集済み murataya 2017年10月16日 2:03
    2017年10月12日 23:55
  • 初めて Wpfで作ってみました。こんな設定で良いでしょうか。

    指摘のように、二行目で、"あいう"と打ち込んで、BackSpaceで消してゆくと、行頭では止まらずに、一行目の行末にカーソルが移動します。その時、右カーソルキーを押すと、二行目ではなく、三行目の先頭にカーソルが移動しました。

    指摘の現象かどうかは分かりませんが、報告まで。

    なお、環境は、Visual Stdio 2017 Communityです。
    プロジェクト作成時の フレームワークは、.NET Framework 4.6.1 でしたが、後から、変更しています。

    2017年10月14日 14:13
  • pepperleaf01 様

    ご報告ありがとうございます。こちらでも同じ症状を確認しました。
    最初に [Enter] を2回入力して3行目まで移動し、上カーソルキー(↑)で
    2行目に移動してから、「あいう」の入力を開始すると、
    「1行目から3行目の先頭にキャレットが移動する」という症状になりますね。

    「.NET Framework 4.7では解消しているはず」
    という情報は誤りではないでしょうか?>trapemiya 様


    • 編集済み murataya 2017年10月16日 1:48
    2017年10月16日 1:45
  • その後、ゼロ幅スペースを使わない方法を思いつきました。

    一時的にキャレットを透明にすることで不具合を回避できました。

    <Window x:Class="WpfApp1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:app="clr-namespace:WpfApp1"
            Title="MainWindow" Height="350" Width="525">
        <StackPanel>
            <TextBlock Text="対策なし"/>
            <TextBox x:Name="textBox1" Height="100" AcceptsReturn="true" Margin="5"
                    app:TextBoxTool.ImeFix="false"/>
            <TextBlock Text="対策あり"/>
            <TextBox x:Name="textBox2" Height="100" AcceptsReturn="true" Margin="5"
                    app:TextBoxTool.ImeFix="true"/>
        </StackPanel>
    </Window>
    
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Threading;
    
    namespace WpfApp1
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
        }
    
        class TextBoxTool
        {
            private static bool imeStart;
            private static bool fixTarget;
    
            static TextBoxTool()
            {
                imeStart = false;
                fixTarget = false;
            }
    
            public static bool GetImeFix(DependencyObject obj)
            {
                return (bool)obj.GetValue(ImeFixProperty);
            }
    
            public static void SetImeFix(DependencyObject obj, bool value)
            {
                obj.SetValue(ImeFixProperty, value);
            }
    
            public static readonly DependencyProperty ImeFixProperty
                = DependencyProperty.RegisterAttached
                ("ImeFix"
                , typeof(bool)
                , typeof(TextBoxTool)
                , new UIPropertyMetadata(default(bool), new PropertyChangedCallback(OnImeFixPropertyChanged)));
    
            private static void OnImeFixPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e)
            {
                if (dpo is TextBox target) {
                    bool newValue = (bool)e.NewValue;
                    bool oldValue = (bool)e.OldValue;
    
                    if (oldValue) {
                        target.TextChanged -= TextBox_TextChanged;
                        TextCompositionManager.RemovePreviewTextInputHandler(target, TextBox_PreviewTextInput);
                        TextCompositionManager.RemoveTextInputUpdateHandler(target, TextBox_TextInputUpdate);
                    }
                    if (newValue) {
                        target.TextChanged += TextBox_TextChanged;
                        TextCompositionManager.AddPreviewTextInputHandler(target, TextBox_PreviewTextInput);
                        TextCompositionManager.AddTextInputUpdateHandler(target, TextBox_TextInputUpdate);
                    }
                }
            }
    
            static void TextBox_TextChanged(object sender, TextChangedEventArgs e)
            {
                var tb = sender as TextBox;
    
                if (fixTarget) {
                    tb.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
                    {
                        int i = tb.CaretIndex;
    
                        tb.CaretBrush = null;
                        tb.CaretIndex = i;
                    }));
                }
            }
    
            static void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
            {
                if (imeStart) {
                    imeStart = false;
                    fixTarget = false;
                }
            }
    
            static void TextBox_TextInputUpdate(object sender, TextCompositionEventArgs e)
            {
                var tb = sender as TextBox;
    
                if (imeStart) {
                    if (fixTarget) {
                        tb.CaretBrush = new SolidColorBrush(Color.FromArgb(0, 0, 0, 0));
                    }
                }
                else {
                    int i = tb.CaretIndex;
    
                    if (i > 0 && tb.Text[i - 1] == '\n') {
                        fixTarget = true;
                    }
                    imeStart = true;
                }
            }
        }
    }
    

    2017年10月16日 6:26