从 XAML 设置 ViewModel 属性值

发布于 2024-11-14 20:21:31 字数 3400 浏览 2 评论 0原文

我有一个在 XAML 中声明的视图(见下文)。关联的视图模型是使用 MEF 自动创建的。我希望能够执行以下操作:

<local:MyControl Owner={x:Static local:Owners.ProjectOwner} />

所需的最终效果是将某些视图模型属性设置为等于 Owners.ProjectOwner。

我可以使用 hacky 代码隐藏来实现所需的结果,但更愿意通过绑定或某种类似的方式来实现。谁能建议一种方法来做到这一点?

更新

我放弃了写一个行为。但我并没有将所有的努力仅仅针对一个特定的案例,而是通用了我的解决方案,并将其包含在下面,以防有人感兴趣。它是一种混合行为 (System.Windows.Interactivity.dll),但也可以很容易地成为一种传统的附加行为。

using System;
using System.Windows;
using System.Windows.Interactivity;

namespace BlendBehaviors
{
    public class SetViewModelPropertyBehavior : Behavior<FrameworkElement>
    {
        public static readonly DependencyProperty PropertyNameProperty =
            DependencyProperty.Register("PropertyName", typeof(string), typeof(SetViewModelPropertyBehavior));

        public static readonly DependencyProperty PropertyValueProperty =
            DependencyProperty.Register("PropertyValue", typeof(object), typeof(SetViewModelPropertyBehavior));

        public SetViewModelPropertyBehavior()
        { }

        public string PropertyName
        {
            get { return (string)GetValue(PropertyNameProperty); }
            set { SetValue(PropertyNameProperty, value); }
        }

        public object PropertyValue
        {
            get { return GetValue(PropertyValueProperty); }
            set { SetValue(PropertyValueProperty, value); }
        }

        protected override void OnAttached()
        {
            base.OnAttached();
            var ao = AssociatedObject;
            SetViewModel(ao.DataContext);
            ao.DataContextChanged += FrameworkElement_DataContextChanged;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.DataContextChanged -= FrameworkElement_DataContextChanged;
        }

        private void FrameworkElement_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            SetViewModel(e.NewValue);
        }

        private void SetViewModel(object viewModel)
        {
            SetViewModelProperty(viewModel, PropertyName, PropertyValue);
        }

        private static void SetViewModelProperty(object viewModel, string propertyName, object propertyValue)
        {
            if (viewModel == null || propertyName == null) {
                return;
            }
            var info = viewModel.GetType().GetProperty(propertyName);
            if (info != null && CanAssignValue(propertyValue, info.PropertyType)) {
                info.SetValue(viewModel, propertyValue, null);
            }
        }

        private static bool CanAssignValue(object value, Type targetType)
        {
            if (value == null) {
                return !targetType.IsValueType || Nullable.GetUnderlyingType(targetType) != null;
            }
            return targetType.IsAssignableFrom(value.GetType());
        }
    }
}

然后像这样使用它:

<local:MyControl>
    <i:Interaction.Behaviors>
        <bb:SetViewModelPropertyBehavior PropertyName="Owner" PropertyValue="{x:Static local:Owners.ProjectOwner}" />
        <bb:SetViewModelPropertyBehavior PropertyName="AnotherProperty" PropertyValue="{StaticResource MyResourceKey}" />
    </i:Interaction.Behaviors>
</local:MyControl>

I have a view that is declared in XAML (see below). The associated view-model is created automatically using MEF. I want to be able to do something like this:

<local:MyControl Owner={x:Static local:Owners.ProjectOwner} />

The desired net effect is for some view-model property to be set equal to Owners.ProjectOwner.

I can achieve the required result using hacky code-behind but would rather do this through bindings or some similar manner. Can anyone suggest a way of doing this?

UPDATE

I resigned myself to writing a behaviour. But rather than put in all the effort solely for one specific case, I have genericised my solution and I include it below in case anyone's interested. It's a Blend behaviour (System.Windows.Interactivity.dll) but could just as easily be a conventional attached behaviour.

using System;
using System.Windows;
using System.Windows.Interactivity;

namespace BlendBehaviors
{
    public class SetViewModelPropertyBehavior : Behavior<FrameworkElement>
    {
        public static readonly DependencyProperty PropertyNameProperty =
            DependencyProperty.Register("PropertyName", typeof(string), typeof(SetViewModelPropertyBehavior));

        public static readonly DependencyProperty PropertyValueProperty =
            DependencyProperty.Register("PropertyValue", typeof(object), typeof(SetViewModelPropertyBehavior));

        public SetViewModelPropertyBehavior()
        { }

        public string PropertyName
        {
            get { return (string)GetValue(PropertyNameProperty); }
            set { SetValue(PropertyNameProperty, value); }
        }

        public object PropertyValue
        {
            get { return GetValue(PropertyValueProperty); }
            set { SetValue(PropertyValueProperty, value); }
        }

        protected override void OnAttached()
        {
            base.OnAttached();
            var ao = AssociatedObject;
            SetViewModel(ao.DataContext);
            ao.DataContextChanged += FrameworkElement_DataContextChanged;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.DataContextChanged -= FrameworkElement_DataContextChanged;
        }

        private void FrameworkElement_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            SetViewModel(e.NewValue);
        }

        private void SetViewModel(object viewModel)
        {
            SetViewModelProperty(viewModel, PropertyName, PropertyValue);
        }

        private static void SetViewModelProperty(object viewModel, string propertyName, object propertyValue)
        {
            if (viewModel == null || propertyName == null) {
                return;
            }
            var info = viewModel.GetType().GetProperty(propertyName);
            if (info != null && CanAssignValue(propertyValue, info.PropertyType)) {
                info.SetValue(viewModel, propertyValue, null);
            }
        }

        private static bool CanAssignValue(object value, Type targetType)
        {
            if (value == null) {
                return !targetType.IsValueType || Nullable.GetUnderlyingType(targetType) != null;
            }
            return targetType.IsAssignableFrom(value.GetType());
        }
    }
}

Then use it like this:

<local:MyControl>
    <i:Interaction.Behaviors>
        <bb:SetViewModelPropertyBehavior PropertyName="Owner" PropertyValue="{x:Static local:Owners.ProjectOwner}" />
        <bb:SetViewModelPropertyBehavior PropertyName="AnotherProperty" PropertyValue="{StaticResource MyResourceKey}" />
    </i:Interaction.Behaviors>
</local:MyControl>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

岁月打碎记忆 2024-11-21 20:21:31

任何 WPF 绑定的目标都必须是 DependencyProperty。源可以是 DependencyProperty、实现 INotifyPropertyChanged 的 CLR 对象,或者只是某个对象。可以通过更改 Binding.Mode 属性来交换目标和源。

但在这种情况下,绑定中的一项是静态解析的属性 (Owners.ProjectOwner)。因此,它不是 DependencyProperty。所以,它只能作为一个来源出现。因此,您将其绑定到的内容(目标)必须是 DependencyProperty。因此,它不能是视图模型上的属性(假设您尚未创建基于 DependencyObject 的视图模型,这将是 一个错误)。

因此,您不能直接将虚拟机上的属性绑定到静态属性。不过,您可以编写一个附加行为来为您完成此操作。

The target of any WPF binding must be a DependencyProperty. The source can be a DependencyProperty, a CLR object that implements INotifyPropertyChanged, or just some object. The target and source can be swapped around by altering the Binding.Mode property.

But in this case one of the items in your binding is a statically-resolved property (Owners.ProjectOwner). Therefore, it's not a DependencyProperty. Therefore, it can only appear as a source. Therefore, what you're binding it to (the target) must be a DependencyProperty. Therefore, it cannot be a property on your view model (assuming you've not created DependencyObject-based view models, which would be a mistake).

So, you cannot directly bind a property on your VM to the static property. You could write an attached behavior that does this for you though.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文