考虑这种情况,使用MVVM:
在我的ModelView上,我有一个类型为“string”的属性,它确实通过INotifyPropertyChanged通知属性的更改。
在视图中,有(或没有)一个控件,具有不是字符串的类型的DependencyProperty“Notification”。该控件可能会也可能不会更改该属性,具体取决于只有控件知道的事实(ModelView或View都不知道这些)。该控件甚至可能位于当前可视树上可能存在或不存在的其他视图上。
在View中,我需要在该控件的DependencyProperty和ViewModel的属性之间建立桥梁,以便更改view属性使控件更改其属性,并且更改控件的DependencyProperty会使viewmodel的属性更改其值。
我已经开始工作,但我认为这不是一个优雅的解决方案。这些天我可能会觉得模糊,所以我问是否有一些我可能错过的明显事物。
显而易见的方法是将ViewModel属性设置为DependencyProperty(因此它可以绑定两种方式),但是现在这是不可能的(另外,它会打破MVVM模式,添加特定于视图的实现到viewmodel)。
另一个显而易见的方法是将Control的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);
}
}
注意: 我正在使用它来做几件事情,而不只是用于音频通知(这只是一个例子)。不要依赖名称来提供解决方案(如果有的话),这需要非常通用。此外,任何拼写错误都来自简化代码到问题(我删除了很多代码并更改了一些属性名称以便澄清)。
正如我所说,它有效...我发现它非常优雅,我相信应该有一个更好的解决方案。
任何建议都将受到欢迎。
基于朱利安的代码,我做了这个行为,它正是我想要的。我使用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>
我接受了他的答案,因为这是让我走上正轨的原因,谢谢
答案 0 :(得分:3)
如果我理解正确,您需要将一个DP绑定到两个源,一个作为源,另一个作为目标。我实际上有这样的行为。
此行为的原理非常简单:它使用两个依赖项属性,并使一个(In
)的数据流入另一个(Out
)。使用单向绑定绑定In
,使用单向绑定绑定Out
,然后就完成了。
public class BindingBehavior : Behavior<DependencyObject> {
public static readonly DependencyProperty InProperty = DependencyProperty.Register(
"In",
typeof(object),
typeof(BindingBehavior),
new FrameworkPropertyMetadata(null, (d, e) => ((BindingBehavior) d).OnInPropertyChanged(e.NewValue)));
public static readonly DependencyProperty OutProperty = DependencyProperty.Register(
"Out",
typeof(object),
typeof(BindingBehavior),
new FrameworkPropertyMetadata(null));
// Bind OneWay
public object In {
get { return GetValue(InProperty); }
set { SetValue(InProperty, value); }
}
// Bind OneWayToSource
public object Out {
get { return GetValue(OutProperty); }
set { SetValue(OutProperty, value); }
}
private void OnInPropertyChanged(object value) {
Out = value;
}
protected override Freezable CreateInstanceCore() {
return new BindingBehavior();
}
}
此行为需要引用您可能熟悉的Blend SDK中的System.Windows.Interactivity。
假设您删除了string
媒体资源并且只保留AudioNotificationType
一个名为AudtioNotification
的媒体资源,其用法应类似于:
<YourView x:Name="View">
<YourControl x:Name="Control" AudioNotification="{Binding Notification, ElementName=View}>
<i:Interaction.Behaviors>
<BindingBehavior
In="{Binding AudioNotification, ElementName=Control, Mode=OneWay}"
Out="{Binding YourVmProperty, Mode=OneWayToSource, Converter=YourConverter}" />
</i:Interaction.Behaviors>
</YourControl>
</YourView>
您可以将行为放在正确名称范围内的任何元素上,以解析元素名称并将视图模型作为数据上下文。
答案 1 :(得分:2)
这看起来可能是添加抽象层的有用时间。我知道。伊克。但忍受我。
如果您有一个桥接对象,可以绑定一些可以处理变更通知的事物,那么该怎么办?它甚至不需要那么复杂。只是实现INotifyPropertyChanged的东西,然后有一个属性(或属性)释放变更通知。这样,您的ViewModel,您的View和您的控件都可以绑定到此桥对象上的相同属性,当其中一个更改桥对象的属性时,所有其他人都将知道是时候更改了。只要所有对象都是双向绑定的,一切都应该很好地同步。
这基本上就是你在BaseViewUserControl上所做的,但是将行为封装在一个单独的对象中可能会为你带来灵活性。