【问题标题】:WPF- Problem with TextBox in DataTemplateWPF- DataTemplate 中的文本框问题
【发布时间】:2011-04-23 12:48:14
【问题描述】:

我正在开发一个应用程序,其中Repository 对象通过包含TextBoxmodified 版本的DataTemplate 显示,它支持绑定到SelectionStartSelectionLengthVerticalOffset

DataTemplate 如下所示:

<DataTemplate DataType="{x:Type m:Repository}">
<controls:ModdedTextBox 
x:Name="textBox" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"  
BindableSelectionStart="{Binding SelectionStart, UpdateSourceTrigger=PropertyChanged}" 
BindableSelectionLength="{Binding SelectionLength, UpdateSourceTrigger=PropertyChanged}"
BindableVerticalOffset="{Binding VerticalOffset, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>

问题是当我更改当前显示的Repository时; SelectionStartSelectionLengthVerticalOffset 似乎都设置为 0,即使 Repository 对象的这些属性不是 0。

我认为这是发生在文本显示之前的瞬间SelectionStartSelectionLengthVerticalOffset不能大于0。这不仅设置了@987654338的实际属性@ 为零,但也会更新绑定并将Repository 对象的属性设置为零。

有什么办法可以防止这种情况发生吗?

--编辑--

我不知道是否在 SO 上发布 dl 链接到项目是否禁止,但这里有一个指向我创建的项目的链接,以证明我遇到的问题:http://dl.dropbox.com/u/1520079/RepositoryProblemDemo.zip

当您运行演示应用程序时,您可以单击“切换存储库”按钮来更改文本框中显示的存储库。如果您查看文本框的右侧,当您切换到另一个时,当前存储库的属性都将设置为零。

这个演示和我的实际应用程序之间的区别在于,在我的应用程序存储库中,将通过热键而不是按钮来切换。

【问题讨论】:

  • 您能否发布有关您的 Bindable*** 依赖属性是如何创建/绑定到基础 Selection 属性的信息?
  • 您的绑定是否需要 2-way 并在 PropertyChanged 上触发,或者您是否可以使用 OneWay 或 LostFocus?
  • 我链接到这个问题:stackoverflow.com/questions/1175618/…
  • 这就是 Bindable 依赖属性的创建方式。
  • @Steve,是的,它们确实需要在 PropertyChanged 上触发 2 向绑定。 Repository 对象的属性需要与 ui 同步,并且当前 Repository 对象的更改不会导致文本框失去焦点。

标签: wpf textbox datatemplate


【解决方案1】:

问题是由于绑定是串行评估的,当 Text 属性被更改时,它会导致所有选择信息被删除(您可以通过在 ModdedTextBox 事件处理程序上放置断点来查看这一点)。由于此时 BindableSelection... 绑定仍然处于活动状态,它会导致选择信息被重置。

根据您想要的确切行为,可能有一种方法可以解决此问题,但您需要了解更多细节...

编辑以响应 cmets: 这个解决方案并没有完全回答你原来的问题,它可能不是很好的做法,但它至少可以工作......

尝试更改您的 ModdedTextBox,以便不公开选择信息的可绑定属性,而是公开一个类型为 Repository 的 DP 并绑定到该 DP:

<local:ModdedTextBox
               x:Name="textBox" 
                Repository="{Binding CurrentRepository}"
               TextWrapping="Wrap"
               />

然后在你的 DP 上处理 changed 事件来设置文本框属性:

public static DependencyProperty RepositoryProperty =
                DependencyProperty.Register("Repository",
                typeof(Repository), typeof(ModdedTextBox), new PropertyMetadata(null, OnRepositoryChanged));

    public Repository Repository
    {
        get { return (Repository)base.GetValue(RepositoryProperty); }
        set { base.SetValue(RepositoryProperty, value); }
    }

    private static void OnRepositoryChanged(DependencyObject senderObject, DependencyPropertyChangedEventArgs e)
    {
        var sender = (ModdedTextBox)senderObject;
        var oldRepository = e.OldValue as Repository;
        var newRepository = e.NewValue as Repository;
        if (oldRepository != null)
        {
            oldRepository.Text = sender.Text;
            oldRepository.SelectionStart = sender.SelectionStart;
            //etc
        }

        if (newRepository != null)
        {
            sender.Text = newRepository.Text;
            sender.SelectionStart = newRepository.SelectionStart;
            //etc
        }
    }

这实际上消除了绑定评估的连续性。

注意:您也可以使用附加属性实现相同的效果,这比子类化 TextBox 更好,但这更接近您最初的尝试,所以我认为它更容易解释!

【讨论】:

  • 我不知道如何解决这个问题。你能说得更详细些吗?您需要我提供更多信息吗?
  • 我不确定它是否可以完全“修复”,正如您所期望的那样。我的意思是,可能有另一种方法可以实现您想要的结果 - 您想对选择信息做什么?
  • 我希望它能够在我在存储库之间切换时恢复选择信息。
【解决方案2】:

这是对另一个解决方案的重写。这一项考虑到 text 属性未在其他属性之前绑定。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;

namespace WpfApplication1
{
    public class SelectionBindingTextBox : TextBox
    {
        public static readonly DependencyProperty BindableSelectionStartProperty =
            DependencyProperty.Register(
            "BindableSelectionStart",
            typeof(int),
            typeof(SelectionBindingTextBox),
            new PropertyMetadata(OnBindableSelectionStartChanged));

        public static readonly DependencyProperty BindableSelectionLengthProperty =
            DependencyProperty.Register(
            "BindableSelectionLength",
            typeof(int),
            typeof(SelectionBindingTextBox),
            new PropertyMetadata(OnBindableSelectionLengthChanged));

        private bool isBindingComplete = false;

        public SelectionBindingTextBox()
            : base()
        {
            this.SelectionChanged += this.OnSelectionChanged;
            this.TextChanged += this.OnTextChanged;
        }

        public int BindableSelectionStart
        {
            get
            {
                return (int)this.GetValue(BindableSelectionStartProperty);
            }

            set
            {
                this.SetValue(BindableSelectionStartProperty, value);
            }
        }

        public int BindableSelectionLength
        {
            get
            {
                return (int)this.GetValue(BindableSelectionLengthProperty);
            }

            set
            {
                this.SetValue(BindableSelectionLengthProperty, value);
            }
        }


        private static void OnBindableSelectionStartChanged(DependencyObject dependencyObject, 
            DependencyPropertyChangedEventArgs args)
        {
            var textBox = dependencyObject as SelectionBindingTextBox;

            if (textBox.isBindingComplete)
            {
                textBox.SetupSelection();
            }
        }

        private static void OnBindableSelectionLengthChanged(DependencyObject dependencyObject, 
            DependencyPropertyChangedEventArgs args)
        {
            var textBox = dependencyObject as SelectionBindingTextBox;
            if (textBox.isBindingComplete)
            {
                textBox.SetupSelection();
            }
        }

        private void OnSelectionChanged(object sender, RoutedEventArgs e)
        {
            if (isBindingComplete)
            {
                this.BindableSelectionStart = this.SelectionStart;
                this.BindableSelectionLength = this.SelectionLength;
            }
        }

        private void OnTextChanged(object sender, RoutedEventArgs e)
        {
            if (!isBindingComplete)
            {
                SetupSelection();
            }
            isBindingComplete = true;
        }

        private void SetupSelection()
        {
           // this.Focus();
            this.SelectionLength = this.BindableSelectionLength;
            this.SelectionStart = this.BindableSelectionStart;
        }
    }
}

【讨论】:

  • 文本框始终保持焦点。
【解决方案3】:

绑定的更新取决于 WPF 或 Silverlight 引擎评估的顺序,看起来您的 SelectionStart 和 SelectionEnd 绑定在 Text 之前更新,因此当 Text 发生更改时,SelectionStart 和 SelectionEnd 都会变回零。

唯一的方法是挂钩 TextChanged 事件并刷新 SelectionStart 和 SelectionEnd 的绑定,或者在 WPF 中您可以如下扩展文本框

public class MyTextBox : TextBox{

    protected override OnTextChanged(TextChangedEventArgs e){
        BindingExpression be = this.GetBindingExpression(SelectionStartProperty);
        if(be!=null){
             be.UpdateTarget();
        }
        be = this.GetBindingExpression(SelectionEndProperty);
        if(be!=null){
             be.UpdateTarget();
        }
        be = this.GetBindingExpression(VerticalOffsetProperty);
        if(be!=null){
             be.UpdateTarget();
        }
    }

}

这里有一个技巧,您仍然需要更改上述逻辑以适应您的逻辑,因为每次文本更新都会更新绑定,因此您必须找出何时刷新这些绑定。因为这将始终无法在运行时更改您的文本框的值,因为文本会修改并且选择只会转到先前的选择。

【讨论】:

    猜你喜欢
    • 2012-12-16
    • 1970-01-01
    • 1970-01-01
    • 2011-07-05
    • 1970-01-01
    • 2010-12-20
    • 1970-01-01
    • 2023-03-23
    • 1970-01-01
    相关资源
    最近更新 更多