Search code examples
c#wpfmvvmbindingtextbox

How to Bind to CaretIndex aka curser position of an Textbox


Hi I'm trying to bind to the TextBox.CaretIndex property which isn't a DependencyProperty, so I created a Behavior, but it doesn't work as expected.

Expectation (when focused)

  • default = 0
  • if I change the value in my view it should change the value in my viewmodel
  • if I change the value in my viewmodel it should change the value in my view

Current behavior

  • viewmodel value gets called ones when the window opens

Code-behind

public class TextBoxBehavior : DependencyObject
{
    public static readonly DependencyProperty CursorPositionProperty =
        DependencyProperty.Register(
            "CursorPosition",
            typeof(int),
            typeof(TextBoxBehavior),
            new FrameworkPropertyMetadata(
                default(int),
                new PropertyChangedCallback(CursorPositionChanged)));

    public static void SetCursorPosition(DependencyObject dependencyObject, int i)
    {
        // breakpoint get never called
        dependencyObject.SetValue(CursorPositionProperty, i); 
    }

    public static int GetCursorPosition(DependencyObject dependencyObject)
    {
        // breakpoint get never called
        return (int)dependencyObject.GetValue(CursorPositionProperty);
    }

    private static void CursorPositionChanged(
        DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        // breakpoint get never called
        //var textBox = dependencyObject as TextBox;
        //if (textBox == null) return;
    }
}

XAML

<TextBox Text="{Binding TextTemplate,UpdateSourceTrigger=PropertyChanged}"
         local:TextBoxBehavior.CursorPosition="{Binding CursorPosition}"/>

Further Information

I think there is something really wrong here because I need to derive it from DependencyObject which was never needed before, because CursorPositionProperty is already a DependencyProperty, so this should be enough. I also think I need to use some events in my Behavior to set my CursorPositionProperty correctly, but I don't know which.


Solution

  • After fighting with my Behavior i can present you a 99% working solution

    Behavior

    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    
    namespace WpfMVVMTextBoxCursorPosition
    {
        public class TextBoxCursorPositionBehavior : DependencyObject
        {
            public static void SetCursorPosition(DependencyObject dependencyObject, int i)
            {
                dependencyObject.SetValue(CursorPositionProperty, i);
            }
    
            public static int GetCursorPosition(DependencyObject dependencyObject)
            {
                return (int)dependencyObject.GetValue(CursorPositionProperty);
            }
    
            public static readonly DependencyProperty CursorPositionProperty =
                                               DependencyProperty.Register("CursorPosition"
                                                                           , typeof(int)
                                                                           , typeof(TextBoxCursorPositionBehavior)
                                                                           , new FrameworkPropertyMetadata(default(int))
                                                                           {
                                                                               BindsTwoWayByDefault = true
                                                                               ,DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
                                                                           }
                                                                           );
    
            public static readonly DependencyProperty TrackCaretIndexProperty =
                                                        DependencyProperty.RegisterAttached(
                                                            "TrackCaretIndex",
                                                            typeof(bool),
                                                            typeof(TextBoxCursorPositionBehavior),
                                                            new UIPropertyMetadata(false
                                                                                    , OnTrackCaretIndex));
    
            public static void SetTrackCaretIndex(DependencyObject dependencyObject, bool i)
            {
                dependencyObject.SetValue(TrackCaretIndexProperty, i);
            }
    
            public static bool GetTrackCaretIndex(DependencyObject dependencyObject)
            {
                return (bool)dependencyObject.GetValue(TrackCaretIndexProperty);
            }
    
            private static void OnTrackCaretIndex(DependencyObject dependency, DependencyPropertyChangedEventArgs e)
            {
                var textbox = dependency as TextBox;
    
                if (textbox == null)
                    return;
                bool oldValue = (bool)e.OldValue;
                bool newValue = (bool)e.NewValue;
    
                if (!oldValue && newValue) // If changed from false to true
                {
                    textbox.SelectionChanged += OnSelectionChanged;
                }
                else if (oldValue && !newValue) // If changed from true to false
                {
                    textbox.SelectionChanged -= OnSelectionChanged;
                }
            }
    
            private static void OnSelectionChanged(object sender, RoutedEventArgs e)
            {
                var textbox = sender as TextBox;
    
                if (textbox != null)
                    SetCursorPosition(textbox, textbox.CaretIndex); // dies line does nothing
            }
        }
    }
    

    XAML

        <TextBox Height="50" VerticalAlignment="Top"
                 Name="TestTextBox"
                 Text="{Binding MyText}"
                 vm:TextBoxCursorPositionBehavior.TrackCaretIndex="True"
                 vm:TextBoxCursorPositionBehavior.CursorPosition="{Binding CursorPosition,Mode=TwoWay}"/>
    
        <TextBlock Height="50" Text="{Binding CursorPosition}"/>
    

    there is just on thing i don't know why it doesn't work => BindsTwoWayByDefault = true. it has no effect on the binding as far as i can tell you because of this i need to set the binding mode explicit in XAML