我有一个OneWayToSource绑定,当我设置目标控件的DataContext时,它的行为不像我预期的那样。源的属性设置为默认值,而不是目标控件属性的值。
我在标准的WPF窗口中创建了一个非常简单的程序来说明我的问题:
XAML
<StackPanel>
<TextBox x:Name="tb"
Text="{Binding Path=Text,Mode=OneWayToSource,UpdateSourceTrigger=PropertyChanged}"
TextChanged="TextBox_TextChanged"/>
<Button Content="Set DataContext" Click="Button1_Click"/>
</StackPanel>
MainWindow.cs
public partial class MainWindow : Window
{
private ViewModel _vm = new ViewModel();
private void Button1_Click(object sender, RoutedEventArgs e)
{
Debug.Print("'Set DataContext' button clicked");
tb.DataContext = _vm;
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
Debug.Print("TextBox changed to " + tb.Text);
}
}
ViewModel.cs
public class ViewModel
{
private string _Text;
public string Text
{
get { return _Text; }
set
{
Debug.Print(
"ViewModel.Text (old value=" + (_Text ?? "<null>") +
", new value=" + (value ?? "<null>") + ")");
_Text = value;
}
}
}
TextBox tb
以null DataContext开头,因此不希望绑定做任何事情。因此,如果我在文本框中输入内容,例如“X”,则ViewModel.Text属性保持为空。
如果我然后单击设置DataContext 按钮,我希望将ViewModel.Text
属性设置为TextBox.Text
属性的“X”。而是设置为“”。当然绑定工作正常,因为如果我在文本框中输入“Y”,在“X”之后,它会将ViewModel.Text
属性设置为“XY”。
以下是输出的示例(由于评估顺序,最后两行是反直觉的,但它们肯定都在键入“Y”后立即出现):
TextBox更改为X
点击“设置DataContext”按钮 ViewModel.Text(旧值=&lt; null&gt;,新值=)
ViewModel.Text(旧值=,新值= XY)
TextBox更改为XY
为什么在设置DataContext时将ViewModel.Text
属性设置为“”而不是“X”?
我做错了什么?我错过了什么吗?我是否误解了有关绑定的内容?
编辑:我原本期望输出为:
TextBox更改为X
点击“设置DataContext”按钮 ViewModel.Text(旧值=&lt; null&gt ;,新值= X )
ViewModel.Text(旧值= X ,新值= XY)
TextBox更改为XY
答案 0 :(得分:2)
它不是一个bug或perhabs。微软声称它的设计。首先输入x然后通过单击Button来杀死DataContext,这样为什么TextBox保持x并且你的viewModel.Text属性被新初始化(它为空)。当在datacontext上更改时,仍会调用getter。最后你没有机会解决这个问题。
然而,您可以使用两种方式让它成为。
答案 1 :(得分:0)
TextBox在其TextProperty中有一个Binding,当你设置TextBox的DataContext时,无论UpdateSourceTrigger是哪种类型,TextBox都会更新它的源(viewmodel.Text)。
据说viewmodel中的第一个输出
“ViewModel.Text (old value=<null>, new value=)
”
不是由UpdateSourceTrigger=PropertyChanged
触发的。
这只是一个init的过程:
private string _Text;
public string Text
{
get { return _Text; }
set
{
Debug.Print(
"ViewModel.Text (old value=" + (_Text ?? "<null>") +
", new value=" + (value ?? "<null>") + ")");
_Text = value;
}
}
因为它不是由UpdateSourceTrigger=PropertyChanged
触发的,所以viewmodel不会知道TextBox.Text的值。
当您键入“Y”时,PropertyChanged的触发器将起作用,因此viewmodel读取TextBox的文本。
答案 2 :(得分:0)
您需要UpdateSource
,如下所示:
private void Button1_Click(object sender, RoutedEventArgs e)
{
Debug.Print("'Set DataContext' button clicked");
tb.DataContext = _vm;
var bindingExp = tb.GetBindingExpression(TextBox.TextProperty);
bingExp.UpdateSource();
}
答案 3 :(得分:0)
.NET 4中存在一个错误,它有一种方法可以为OneWayToSource绑定调用getter,这就是为什么你遇到这个问题。你可以通过在tb.DataContext = _vm上放置断点来验证它。你会发现setter被调用,就在Text属性上调用getter之后。你可以通过在分配datacontext之前手动从视图中提供viewmodel值来解决你的问题.NET 4.5解决了这个问题。 see here和here too
private void Button1_Click(object sender, RoutedEventArgs e)
{
Debug.Print("'Set DataContext' button clicked");
_vm.Text=tb.Text;
tb.DataContext = _vm;
}
答案 4 :(得分:0)
您需要Attached property
:
public static readonly DependencyProperty OneWaySourceRaiseProperty = DependencyProperty.RegisterAttached("OneWaySourceRaise", typeof(object), typeof(FrameworkElementExtended), new FrameworkPropertyMetadata(OneWaySourceRaiseChanged));
public static object GetOneWaySourceRaise(DependencyObject o)
{
return o.GetValue(OneWaySourceRaiseProperty);
}
public static void SetOneWaySourceRaise(DependencyObject o, object value)
{
o.SetValue(OneWaySourceRaiseProperty, value);
}
private static void OneWaySourceRaiseChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
return;
var target = (FrameworkElement)d;
target.Dispatcher.InvokeAsync(() =>
{
var bindings = target.GetBindings().Where(i => i.ParentBinding?.Mode == BindingMode.OneWayToSource).ToArray();
foreach (var i in bindings)
{
i.DataItem.SetProperty(i.ParentBinding.Path.Path, d.GetValue(i.TargetProperty));
}
});
并在XAML中设置绑定:
extendends:FrameworkElementExtended.OneWaySourceRaise="{Binding}"
其中{Binding}
-绑定到DataContext
。
您需要:
public static IEnumerable<BindingExpression> GetBindings<T>(this T element, Func<DependencyProperty, bool> func = null) where T : DependencyObject
{
var properties = element.GetType().GetDependencyProperties();
foreach (var i in properties)
{
var binding = BindingOperations.GetBindingExpression(element, i);
if (binding == null)
continue;
yield return binding;
}
}
private static readonly ConcurrentDictionary<Type, DependencyProperty[]> DependencyProperties = new ConcurrentDictionary<Type, DependencyProperty[]>();
public static DependencyProperty[] GetDependencyProperties(this Type type)
{
return DependencyProperties.GetOrAdd(type, t =>
{
var properties = GetDependencyProperties(TypeDescriptor.GetProperties(type, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.All) }));
return properties.ToArray();
});
}
private static IEnumerable<DependencyProperty> GetDependencyProperties(PropertyDescriptorCollection collection)
{
if (collection == null)
yield break;
foreach (PropertyDescriptor i in collection)
{
var dpd = DependencyPropertyDescriptor.FromProperty(i);
if (dpd == null)
continue;
yield return dpd.DependencyProperty;
}
}