В .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", когда преобразователь будет работать после первого нажатия клавиши.