【发布时间】:2012-03-08 19:50:31
【问题描述】:
考虑这种情况,使用 MVVM:
在我的 ModelView 上,我有一个“字符串”类型的属性,它确实通过 INotifyPropertyChanged 通知属性的更改。
在视图中,有(或没有)一个控件,其 DependencyProperty“通知”类型不是字符串。该控件可能会或可能不会更改该属性,具体取决于只有控件知道的事实(ModelView 或 View 都不知道这些)。该控件甚至可能位于当前可视树上的其他视图上。
在视图中,我需要在该控件的 DependencyProperty 和 ViewModel 的属性之间架起一座桥梁,以便更改视图属性使控件更改其属性,更改控件的 DependencyProperty 使视图模型的属性更改其值。
我已经让它工作了,但我认为这不是一个优雅的解决方案。这些天我可能在思考,所以我想问是否有一些明显的东西我可能错过了。
显而易见的方法是让 ViewModel 属性成为一个 DependencyProperty(因此它可以以两种方式绑定),但是现在这是不可能的(另外,它会破坏 MVVM 模式,将特定于视图的实现添加到视图模型)。
另一种明显的方法是将控件的 DependencyProperty 绑定到 ViewModel 的属性:这有效,但仅适用于一个视图...多个属性不能(或者,我不知道该怎么做)绑定到同一个DependencyProperty:当我设置一个绑定时,我会丢失另一个。
目前我是这样做的:
public class BaseViewUserControl : UserControl
{
// Dependency property, bound to the view's property
public string AudioNotification
{
get { return (string)GetValue(AudioNotificationProperty); }
set { SetValue(AudioNotificationProperty, value); }
}
public static readonly DependencyProperty AudioNotificationProperty = DependencyProperty.Register("AudioNotification", typeof(string), typeof(BaseViewUserControl), new FrameworkPropertyMetadata("None", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnAudioNotificationPropertyChanged));
// Dependency property, bound to the control's dependency property
public AudioNotificationType AudioNotificationToControl
{
get { return (AudioNotificationType)GetValue(AudioNotificationToControlProperty); }
set { SetValue(AudioNotificationToControlProperty, value); }
}
public static readonly DependencyProperty AudioNotificationToControlProperty = DependencyProperty.Register("AudioNotificationToControl", typeof(AudioNotificationType), typeof(BaseViewUserControl), new FrameworkPropertyMetadata(AudioNotificationType.None, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, OnAudioNotificationToControlCoerceValue));
// Converter
private static IValueConverter _audioNotificationTypeConverter;
private static IValueConverter AudioNotificationTypeConverter
{
get { return _audioNotificationTypeConverter ?? (_audioNotificationTypeConverter = new AudioNotificationConverter()); }
}
private Binding _audioNotificationBinding;
private bool PrepareAudioNotificationControlBinding()
{
if (_audioNotificationBinding != null) return true;
var b = this.FindVisualTreeRoot().TryFindChild<AudioNotification>();
if (b == null) return false;
_audioNotificationBinding = new Binding { Source = b, Mode = BindingMode.TwoWay, Path = new PropertyPath("Notification") };
SetBinding(AudioNotificationToControlProperty, _audioNotificationBinding);
return true;
}
private static void OnAudioNotificationPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
if (!(source is BaseViewUserControl)) return;
var src = (BaseViewUserControl)source;
if(src.PrepareAudioNotificationControlBinding())
{
var val = AudioNotificationTypeConverter.ConvertValue<AudioNotificationType>(e.NewValue);
src.AudioNotificationToControl = val;
}
}
private static object OnAudioNotificationToControlCoerceValue(DependencyObject source, object basevalue)
{
if (!(source is BaseViewUserControl)) return basevalue;
var src = (BaseViewUserControl)source;
var val = AudioNotificationTypeConverter.ConvertBackValue<string>(basevalue);
src.AudioNotification = val;
return basevalue;
}
public BaseViewUserControl()
{
var ab = new Binding { Path = new PropertyPath("AudibleNotification"), Mode = BindingMode.TwoWay };
SetBinding(AudibleNotificationProperty, ab);
}
}
注意: 我将它用于几件事,而不仅仅是用于音频通知(这只是一个示例)。不要依赖名称来给出解决方案(如果有的话),这需要非常通用。此外,任何错别字都来自于将代码简化为问题(我已经删除了很多代码并更改了一些属性名称以进行澄清)。
正如我所说,它有效......我只是觉得它很不优雅,我相信应该有比这更好的解决方案。
欢迎提出任何建议。
更新
根据 Julien 的代码,我制作了这个 Behavior,这正是我想要的。我使用Converter 实现了它,但为了清楚起见,我最终在控件本身上进行了转换,并使用strings 沿 传递变量(如果我仍然想使用控件中的未记录属性)本机数据类型)
public class BridgePropertyBinderBehavior : Behavior<DependencyObject>
{
public static BridgePropertyBinderBehavior PrepareBindingToControl(FrameworkElement sourceView, string viewModelPropertyPath, FrameworkElement targetControl, string controlPropertyPath)
{
var b = new BridgePropertyBinderBehavior();
BindingOperations.SetBinding(b, AProperty, new Binding(viewModelPropertyPath) { Source = sourceView.DataContext, Mode = BindingMode.TwoWay, BindsDirectlyToSource = true, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
BindingOperations.SetBinding(b, BProperty, new Binding(controlPropertyPath) { Source = targetControl, Mode = BindingMode.TwoWay });
Interaction.GetBehaviors(sourceView).Add(b);
return b;
}
public object A { get { return GetValue(AProperty); } set { SetValue(AProperty, value); } }
public static readonly DependencyProperty AProperty = DependencyProperty.Register("A", typeof(object), typeof(BridgePropertyBinderBehavior), new FrameworkPropertyMetadata(null, (d, e) => ((BridgePropertyBinderBehavior)d).OnAChanged(e.NewValue)));
public object B { get { return GetValue(BProperty); } set { SetValue(BProperty, value); } }
public static readonly DependencyProperty BProperty = DependencyProperty.Register("B", typeof(object), typeof(BridgePropertyBinderBehavior), new FrameworkPropertyMetadata(null, (d, e) => ((BridgePropertyBinderBehavior)d).OnBChanged(e.NewValue)));
private void OnAChanged(object value) { B = value; }
private void OnBChanged(object value) { A = value; }
protected override Freezable CreateInstanceCore()
{
return new BridgePropertyBinderBehavior();
}
}
在我看来我是这样使用的:
var audioNotificationControl = this.FindVisualTreeRoot().TryFindChild<AudioNotification>();
BridgePropertyBinderBehavior.PrepareBindingToControl(this, "AudioNotification", audioNotificationControl, "Notification");
或
<AudioNotification x:Name="Control">
<ia:Interaction.Behaviors>
<BridgePropertyBinderBehavior
A="{Binding Path=Notification, ElementName=Control, Mode=TwoWay}"
B="{Binding Path=AudioNotification, Mode=TwoWay}" />
</ia:Interaction.Behaviors>
</AudioNotification>
我已经接受了他的回答,因为这让我走上了正轨,谢谢
【问题讨论】: