我有一个控件(在下面的示例中名为
GridView
)和一个视图模型,它们通过其 SelectedValue
属性上的 2 路绑定进行绑定。我想通过使用 GridView
来禁止 CoerceValueCallback
控件中该属性的某些值。
当绑定将该无效值(本例中为 42)推入
DependencyProperty
时,CoerceValue
方法会丢弃该值,并且控件按预期保留其先前的值。不幸的是,由于 DependencyProperty
尚未更改,因此绑定不会更新视图模型中的源值。
取消属性值更改时如何用控件中的值更新视图模型中的属性?
class ViewModel : INotifyPropertyChanged
{
public int? SelectedValue
{
get { return _selectedValue; }
set
{
_selectedValue = value;
RaisePropertyChanged("SelectedValue");
}
}
private int? _selectedValue;
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string name)
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
class GridView : FrameworkElement
{
public object SelectedValue
{
get { return (object)GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(object), typeof(GridView),
new PropertyMetadata(null, null, new CoerceValueCallback(CoerceValue)));
private static object CoerceValue(DependencyObject d, object baseValue)
{
// forbid value 42 and return previous value
if (Equals(baseValue, 42))
{
return d.GetValue(GridView.SelectedValueProperty);
}
return baseValue;
}
}
[STAThread]
static void Main()
{
ViewModel vm = new ViewModel();
GridView grid = new GridView();
Binding binding = new Binding("SelectedValue") { Source = vm, Mode = BindingMode.TwoWay };
grid.SetBinding(GridView.SelectedValueProperty, binding);
vm.SelectedValue = 12;
Console.WriteLine("should be 12: {0} = {1}", grid.SelectedValue, vm.SelectedValue);
grid.SelectedValue = 23;
Console.WriteLine("should be 23: {0} = {1}", grid.SelectedValue, vm.SelectedValue);
vm.SelectedValue = 42;
Console.WriteLine("should still be 23: {0} = {1}", grid.SelectedValue, vm.SelectedValue);
}
我已经尝试过
var binding = BindingOperations.GetBindingExpression(d,
GridView.SelectedValueProperty);
binding.UpdateSource();
在
CoerceValue
方法中无济于事。我什至用 Dispatcher.CurrentDispatcher.BeginInvoke(updateSource, DispatcherPriority.DataBind);
尝试了前两行,这是在类似的堆栈溢出问题中建议的。
还有其他想法吗?
更新:
@Berryl 建议我应该在视图模型中执行该逻辑,我同意他的观点......只是我不能。我将解释我在上面的代码中尝试简化的完整用例。
GridView 不是我的控件,而是我继承的第 3 方数据网格。当用户编辑记录时,我们会弹出一个窗口,用户在其中编辑记录,然后视图模型刷新数据并使用其 ID 重新选择旧记录。一切工作正常,直到用户使用网格的过滤功能。如果对记录的编辑会因过滤器而隐藏该行,则会发生以下情况:
ValueList
属性中查看模型集SelectedValue
中的记录设置
ValueList
SelectedValue
但将其丢弃,因为它已被过滤掉ICommand
在视图模型中被调用SelectedValue
属性的记录我在很久以前的电子邮件中发现了这个解决方案,并认为我应该分享它(进行一些清理)。这需要当时致电 Telerik 和 Microsoft 提供支持。
基本上,我无法从 CoerceValueCallback 取消属性更新的原因是:
现在是关于如何修复它的长答案:
static GridView()
{
SelectedItemProperty.OverrideMetadata(typeof(GridView), new FrameworkPropertyMetadata(null, CoerceSelectedItemProperty));
}
// Rewriting SelectedItem coercion logic because of the following issue (we had support calls to both Telerik and Microsoft)
// When
// - a filter is applied on the grid
// - user refreshes after modifying a record
// - the record he has changed would be filtered out by the the grid filter
// - the view model sets the SelectedItem to the modified record (which is hidden now)
//
// Telerik's coercion method resets the selected item to a visible row but because WPF's
// binding occurs before the coercion method, the view model is not updated.
// Even a call to UpdateSource() on the binding does not work in this case because the binding
// is already updating the target while this happens so it does nothing when you call it.
private static object CoerceSelectedItemProperty(DependencyObject d, object baseValue)
{
// Call normal Coercion method because we don't want to rewrite Telerik's logic
// and keep result to return it at the end.
object returnValue = SelectedItemProperty.GetMetadata(typeof(RadGridView)).CoerceValueCallback(d, baseValue);
var control = (GridView)d;
// If coerce returned something other than DependencyProperty.UnsetValue we can use it to push it back to
// the binding source because it is of the right type and the right value.
// The only case when we can use control.SelectedItem is when coerce returned UnsetValue otherwise the
// view model is always one click late.
object officialValue = returnValue == DependencyProperty.UnsetValue
? control.SelectedItem
: returnValue;
var binding = control.GetBindingExpression(SelectedItemProperty);
var source = binding.ResolvedSource;
var property = source.GetType().GetProperty(binding.ResolvedSourcePropertyName);
property.SetValue(source, officialValue, null);
return returnValue;
}
我在这里发布了类似问题的解决方案:使用不同的 vlaue 从目标更新源
简短:神奇的线条是
await Task.Yield();
使用此功能,您可以暂时将控制权交还给 UI 线程,使其能够处理挂起的更新,例如重绘 UI 或处理由 INotifyPropertyChanged 触发的属性更改