WPF - проблема с TextBox в DataTemplate - PullRequest
0 голосов
/ 10 октября 2010

Я работаю над приложением, в котором объекты Repository отображаются с помощью DataTemplate, который содержит модифицированную версию TextBox, которая поддерживает привязку к SelectionStart, SelectionLength иVerticalOffset.

Шаблон данных выглядит следующим образом:

<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, которое отображается в данный момент;SelectionStart, SelectionLength и VerticalOffset все, кажется, устанавливаются в 0, даже когда эти свойства объекта Repository не равны 0.

Я думаю, что это происходит вза мгновение до отображения текста, когда SelectionStart, SelectionLength и VerticalOffset не могут быть больше 0. Это не только устанавливает фактические свойства TextBox на ноль, но также обновляет привязки и устанавливаетсвойства объекта Repository равны нулю.

Можно ли как-нибудь предотвратить это?

- Правка -

Я не знаю, является ли публикация dl-ссылок на проекты "нет-нет" или нет на SO, но вот ссылка на проект, который я создал, чтобы продемонстрировать мою проблему: http://dl.dropbox.com/u/1520079/RepositoryProblemDemo.zip

Когда вы запускаете демонстрационное приложение, вы можете нажать кнопку «Переключить репозиторий», чтобы изменить репозиторий, отображаемый в текстовом поле.Если вы посмотрите справа от текстового поля, все свойства текущего репозитория будут установлены на ноль при переключении на другое.

Разница между этой демонстрацией и моим настоящим приложением заключается в том, что в моем репозитории приложений будут переключатьсяс помощью горячих клавиш, а не кнопки.

Ответы [ 3 ]

1 голос
/ 26 октября 2010

Проблема связана с тем, что привязки оцениваются последовательно, а когда свойство Text изменяется, это приводит к удалению всей информации выбора (это можно увидеть, поставив точки останова в обработчиках событий ModdedTextBox ). Поскольку привязки BindableSelection ... все еще активны в этой точке, это приводит к сбросу информации о выборе.

В зависимости от того, какое поведение вы хотите, возможно, есть способ обойти это, но вам нужно знать немного больше деталей ...

Редактировать в ответ на комментарии: Это решение не точно отвечает на ваш первоначальный вопрос, и, вероятно, это не очень хорошая практика, но, по крайней мере, оно работает ...

Попробуйте изменить свой ModdedTextBox так, чтобы вместо предоставления привязываемых свойств для информации выбора, выставлялся один DP типа Repository и связывался с ним:

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

Затем обработайте измененное событие на вашем DP, чтобы установить свойства текстового поля:

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, но это ближе к вашим первоначальным попыткам, поэтому я думаю, что это легче объяснить!

0 голосов
/ 27 октября 2010

Что ж, обновление привязки зависит от порядка, в котором будут оцениваться WPF или Silverlight Engine, похоже, что привязки 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();
        }
    }

}

Что ж, здесь есть хитрость, вам все равно придется изменить вышеуказанную логику, чтобы она соответствовала вашей логике, потому что при каждом обновлении текста это будет обновлять привязку, поэтому вы должны выяснить, когда обновлять эти привязки. Потому что это постоянно не сможет изменить значение вашего текстового поля во время выполнения, так как текст изменится, и выбор перейдет только к предыдущему выбору.

0 голосов
/ 22 октября 2010

Вот переписывание другого решения. Это учитывает свойство текста, не связанное с другими свойствами.


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;
        }
    }
}

...