【问题标题】:How to Bind to CaretIndex aka curser position of an Textbox如何绑定到 CaretIndex 又名文本框的光标位置
【发布时间】:2015-03-29 19:26:26
【问题描述】:

您好,我正在尝试绑定到不是DependencyPropertyTextBox.CaretIndex 属性,所以我创建了一个Behavior,但它没有按预期工作。

期望(专注时)

  • 默认 = 0
  • 如果我更改我的 view 中的值,它应该更改我的 viewmodel 中的值
  • 如果我更改 viewmodel 中的值,它应该更改我的 view 中的值

当前行为

  • viewmodel 值在窗口打开时被调用

代码隐藏

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}"/>

更多信息

我认为这里确实有问题,因为我需要从以前从未需要的DependencyObject 派生它,因为CursorPositionProperty 已经是DependencyProperty,所以这应该足够了。我还认为我需要在我的Behavior 中使用一些事件来正确设置我的CursorPositionProperty,但我不知道是哪个。

【问题讨论】:

    标签: c# wpf mvvm binding textbox


    【解决方案1】:

    在与我的行为作斗争后,我可以为您提供 99% 可行的解决方案

    行为

    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}"/>
    

    我不知道为什么它不起作用 => BindsTwoWayByDefault = true。据我所知,它对绑定没有影响,因此我需要在 XAML 中显式设置绑定模式

    【讨论】:

    • public static int GetTrackCaretIndex(DependencyObject dependencyObject) 应该返回 bool,因为它是一个布尔属性。 + return (bool) dependencyObject.GetValue(TrackCaretIndexProperty); 否则你会得到一个“输入字符串的格式不正确。” XAML 错误。
    • CursorPosition 也需要使用RegisterAttached
    • 这完美地设置了选择更改时的 videomodel 属性,但是当我从代码中更改 viewmodel 属性时,它不会更改 UI 上的 caretindex。有什么遗漏吗?
    【解决方案2】:

    我遇到了类似的问题,对我来说最简单的解决方案是从 TextBox 继承并添加一个 DependencyProperty。所以它看起来像这样:

    namespace UI.Controls
    {
        public class MyTextBox : TextBox
        {
            public static readonly DependencyProperty CaretPositionProperty =
                DependencyProperty.Register("CaretPosition", typeof(int), typeof(MyTextBox),
                    new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnCaretPositionChanged));
    
            public int CaretPosition
            {
                get { return (int)GetValue(CaretPositionProperty); }
                set { SetValue(CaretPositionProperty, value); }
            }
    
            public MyTextBox()
            {
                SelectionChanged += (s, e) => CaretPosition = CaretIndex;
            }
    
            private static void OnCaretPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                (d as MyTextBox).CaretIndex = (int)e.NewValue;
            }
        }
    }
    

    ...在我的 XAML 中:

    xmlns:controls="clr-namespace:IU.Controls"
    ...
    <controls:MyTextBox CaretPosition="{Binding CaretPosition}"/>
    

    ...当然还有视图模型中的 CaretPosition 属性。如果您不打算将您的视图模型绑定到其他文本编辑控件,这可能就足够了,如果是的话 - 您可能需要另一个解决方案。

    【讨论】:

      【解决方案3】:

      WiiMaxx 的解决方案对我来说有以下问题:

      1. 从代码中更改视图模型属性时,文本框中的插入符号索引不会更改。 Tejas Vaishnav 在对解决方案的评论中也提到了这一点。
      2. BindsTwoWayByDefault = true 不起作用。
      3. 他说他需要从DependencyObject继承很奇怪。
      4. TrackCaretIndex 属性仅用于初始化,感觉有点不必要。

      这是我解决这些问题的解决方案:

      行为

      public static class TextBoxAssist
      {
      
          // This strange default value is on purpose it makes the initialization problem very unlikely.
          // If the default value matches the default value of the property in the ViewModel,
          // the propertyChangedCallback of the FrameworkPropertyMetadata is initially not called
          // and if the property in the ViewModel is not changed it will never be called.
          private const int CaretIndexPropertyDefault = -485609317;
      
          public static void SetCaretIndex(DependencyObject dependencyObject, int i)
          {
              dependencyObject.SetValue(CaretIndexProperty, i);
          }
      
          public static int GetCaretIndex(DependencyObject dependencyObject)
          {
              return (int)dependencyObject.GetValue(CaretIndexProperty);
          }
      
          public static readonly DependencyProperty CaretIndexProperty =
              DependencyProperty.RegisterAttached(
                  "CaretIndex",
                  typeof(int),
                  typeof(TextBoxAssist),
                  new FrameworkPropertyMetadata(
                      CaretIndexPropertyDefault,
                      FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                      CaretIndexChanged));
      
          private static void CaretIndexChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
          {
              if (dependencyObject is not TextBox textBox || eventArgs.OldValue is not int oldValue || eventArgs.NewValue is not int newValue)
              {
                  return;
              }
      
              if (oldValue == CaretIndexPropertyDefault && newValue != CaretIndexPropertyDefault)
              {
                  textBox.SelectionChanged += SelectionChangedForCaretIndex;
              }
              else if (oldValue != CaretIndexPropertyDefault && newValue == CaretIndexPropertyDefault)
              {
                  textBox.SelectionChanged -= SelectionChangedForCaretIndex;
              }
      
              if (newValue != textBox.CaretIndex)
              {
                  textBox.CaretIndex = newValue;
              }
          }
      
          private static void SelectionChangedForCaretIndex(object sender, RoutedEventArgs eventArgs)
          {
              if (sender is TextBox textBox)
              {
                  SetCaretIndex(textBox, textBox.CaretIndex);
              }
          }
      
      }
      

      XAML

          <TextBox Height="50" VerticalAlignment="Top"
                   Name="TestTextBox"
                   Text="{Binding MyText}"
                   viewModels:TextBoxAssist.CaretIndex="{Binding CaretIndex}"/>
      

      对差异的一些说明:

      • 视图模型属性更改现在有效,因为 TextBox 上的插入符号索引设置在 CaretIndexChanged 的末尾。
      • BindsTwoWayByDefault 已通过使用相应的 FrameworkPropertyMetadata 构造函数参数进行修复。
      • 仅需要从 DependencyObject 继承,因为使用了 DependencyProperty.Register 而不是 DependencyProperty.RegisterAttached
      • 如果没有 TrackCaretIndex 属性,我遇到的问题是 FrameworkPropertyMetadatapropertyChangedCallback 从未被调用来正确初始化事物。仅当FrameworkPropertyMetadata 的默认值从一开始就与视图模型属性的值匹配并且视图模型属性未更改时,才会出现此问题。这就是我使用这个随机默认值的原因。

      【讨论】:

        【解决方案4】:

        正如您所说,TextBox.CaretIndex Property 不是 DependencyProperty,因此您无法对其进行数据绑定。即使使用您自己的DependencyProperty,它也不起作用......当TextBox.CaretIndex 属性发生变化时,您希望如何收到通知?

        【讨论】:

        • 我想我可以使用一些事件来改变值
        • 当然,您可以更改该值,但当它自行更改时,您不会收到通知。但是,您不需要 DependencyProperty 来更改值。
        • 好的,我明白了,那么您将如何尝试获得TextBox.CaretIndex
        • @WiiMaxx,您可以使用行为来存储TextBox 的实例,记住当前位置,启动计时器并轮询更改(我不确定如何将更改传递给视图模型并返回)。还可以订阅Unloaded 事件以停止计时器。
        • @Sheridan 如果你能看看我的回答会很好,也许你能告诉我我的BindsTwoWayByDefault = true 有什么问题。提前致谢
        猜你喜欢
        • 1970-01-01
        • 2010-10-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-04-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多