В .NET 4.0 привязка OneWayToSource

OneWayToSource Связывание кажется сломанным в .NET 4.0

У меня есть этот простой кусок Xaml

<StackPanel>
    <TextBox Text="{Binding TextProperty, Mode=OneWayToSource}"/>
    <Button/>
</StackPanel>

И мой код выглядит так:

public MainWindow()
{
    InitializeComponent();
    this.DataContext = this;
}
private string m_textProperty;
public string TextProperty
{
    get
    {
        return "Should not be used in OneWayToSource Binding";
    }
    set
    {
        m_textProperty = value;
    }
}

В .NET 3.5 это работает, как вы могли бы исключить. Поместите некоторый текст в TextBox, нажмите Tab, чтобы потерять Focus, а TextProperty обновляет любой текст, который был введен в TextBox

В .NET 4.0, если я наберу текст в TextBox, а затем нажмите Tab, чтобы потерять Focus, TextBox вернется к значению для TextProperty (что означает "Нельзя использовать в привязке OneWayToSource" ). Это перечитывание предназначено для привязки OneWayToSource в .NET 4.0? Я просто хочу, чтобы TextBox нажал его значение в TextProperty, а не наоборот.

Обновление
Добавление Bounty к этому вопросу, поскольку это стало неудобством мэра в моем проекте, и я хотел бы знать причину, по которой это изменилось. Кажется, что get вызывается после того, как Binding обновил источник. Является ли это желаемым поведением для OneWayToSource Binding в .NET 4.0?

Если Да

  • В чем была проблема с тем, как он работал в версии 3.5?
  • В каких сценариях это новое поведение лучше?

Или - это на самом деле ошибка, которую мы можем надеяться зафиксировать в будущей версии?

Ответ 1

Блог Karl Shifflett и ответ @Simpzon уже охватывают причины, по которым они добавили эту функцию, и почему это не проблема для свойств, которые всегда получают то, что было установлено. В вашем собственном коде вы всегда используете промежуточное свойство, которое имеет надлежащую семантику для привязки и использует внутреннее свойство, которое имеет семантику, которую вы хотите. Я бы назвал промежуточное свойство "блокирующим", потому что он блокирует getter от вашего внутреннего свойства.

Но в том случае, если у вас нет доступа к исходному коду для объекта, на который вы устанавливаете свойство, и вы хотите использовать старое поведение, вы можете использовать конвертер.

Вот блокирующий конвертер с состоянием:

public class BlockingConverter : IValueConverter
{
    public object lastValue;

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return lastValue;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        lastValue = value;
        return value;
    }
}

и вы можете использовать его для своего примера следующим образом:

<Grid>
    <Grid.Resources>
        <local:BlockingConverter x:Key="blockingConverter" x:Shared="False"/>
    </Grid.Resources>
    <StackPanel>
        <TextBox Text="{Binding TextProperty, Mode=OneWayToSource, Converter={StaticResource blockingConverter}}"/>
        <Button Content="Click"/>
    </StackPanel>
</Grid>

Обратите внимание, что, поскольку у преобразователя есть состояние, вам нужен отдельный экземпляр каждый раз, когда используется ресурс, и для этого мы можем использовать атрибут x:Shared="False" на ресурсе.

Ответ 2

Это действительно по дизайну. Как правило, это не должно беспокоить, но, по вашему мнению, реализация вашей собственности, по крайней мере, нестандартная. Getters и seters (accessors) должны действительно быть не намного больше, чем получать и устанавливать, и каждый get должен соответствовать последнему соответствующему набору. (извините, нет источника для этого, это именно то, что мы назвали хорошим гражданством во всех командах разработчиков, в которых я был).

Подробнее об этой функции: http://karlshifflett.wordpress.com/2009/05/27/wpf-4-0-data-binding-change-great-feature/

Ответ 3

Ошибка, определенно.

если это "особенность", это очень плохо...

кажется, что они добавили вызов функции get() после выполнения set(), даже в режиме OneWayToSource... может ли кто-нибудь объяснить, почему?

Кроме того, спасибо за указание на это, это объясняет проблему, с которой я столкнулся с тех пор, как обновил свой проект до .net 4.0 и что я не мог объяснить до сих пор...

просто примечание: я решил это, используя свойства зависимостей в конце.

Ответ 4

Это довольно явная ошибка. Кажется, что кто-то сообщил хотя бы один раз. https://connect.microsoft.com/VisualStudio/feedback/details/612444/onewaytosource-broken-in-net-4-0

Я согласен со многими из других, что один путь должен быть одним из способов.

Мой сценарий сложный, и функциональность, измененная между версиями каркаса, вызвала у меня настоящую головную боль.

У меня есть текстовое поле, связанное с свойством. У меня есть конвертер, который меняет формат на лету. EG: Я ввожу EU5 в текстовое поле, свойство получает EU005. У меня есть набор привязки, который запускается при изменении свойства, поскольку мне нужно выполнять поиск в ViewModel по типу пользователя. Новая реализация изменяет значение текстового поля по мере ввода. Поэтому, если я хочу набрать EU512, я не мог бы легко, так как текст текстового поля продолжал бы меняться.

Я пробовал много вещей - Явное связывание (где вы решаете, когда обновлять и каким образом.) У этой проблемы есть такая же проблема. Если я говорю, UpdateSource, он делает, но также затем перечитывает свойство и изменяет цель тоже.

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

Если MS сделала привязку так, как она логически названа, моя проблема исчезнет. Даже свойство привязки к отказу от реализации .net4 и ведет себя как 3.5, будет работать для меня.

У кого-нибудь есть предложения для меня о том, как я могу обойти это?

Ответ 5

Является ли это желательным поведением для OneWayToSource Binding в .NET 4.0?

Да. Это делается для способности разработчика изменять предоставленное значение без неуклюжих конвертеров.

В чем была проблема с тем, как он работал в версии 3.5?

Нет проблем. Способ, которым он работал в 3.5, не позволял исправлять предоставленные значения.

В каких сценариях это новое поведение лучше?

Когда вам нужно исправить предоставленные значения. Если вам это не нужно, тогда вы должны просто написать правильный getter и setter.

public string TextProperty
{
    get;
    set;
}

Однако, как я вижу, изменение UpdateSourceTrigger на "PropertyChanged" сохраняет значения от перечитания (чтобы вы могли оставить объявление старой собственности):

<StackPanel>
    <TextBox Text="{Binding TextProperty, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"/>
    <Button/>
</StackPanel>

    private string m_textProperty;
    public string TextProperty
    {
        get
        {
            return "Should not be used in OneWayToSource Binding";
        }
        set
        {
            m_textProperty = value;
        }
    }

Ответ 6

У меня был вариант этой проблемы для двусторонней привязки. Я понимаю, что это не совсем то же самое, что обсуждаемая проблема, но этот ответ поднялся во время поиска, и это привело к моему решению. Надеюсь, кто-то сочтет это полезным.

Мое решение блокирует повторное чтение свойства backing, но будет обновлять интерфейс, когда он будет изменен каким-либо другим источником. Я основывал свое решение на блокирующем конвертере в ответе Рика Слэндэ.

Он просто добавляет проверку конвертеру, чтобы увидеть, будет ли поле lastValue конвертировать в одно и то же хранилище. Если нет, значение хранилища резервной копии должно быть изменено с другого источника, и пользовательский интерфейс должен быть обновлен.

public class MyConverter : IValueConverter
{
    public object lastValue;

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (LastValue != null && MyConvertBack(LastValue).Equals(value))
            return lastValue;
        else
            return MyConvert(value);

    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        lastValue = value;
        return MyConvertBack(value);
    }

    private object MyConvertBack(Object value)
    {
        //Conversion Code Here
    }

    private object MyConvert(Object value)
    {
        //Conversion Code Here
    }
}

В моем конкретном случае для этого у меня был суффикс длины и размера, хранящийся в текстовом поле (10 м, 100 мм и т.д.). Преобразователь проанализировал это на двойное значение или добавил суффикс (в зависимости от направления преобразования). Без конвертера он добавит суффикс для каждого обновления текстового поля. Попытка ввести "10" приведет к "1m0", когда преобразователь будет работать после первого нажатия клавиши.