【问题标题】:Subscribe PropertyChanged events of window in C++/CLI在 C++/CLI 中订阅窗口的 PropertyChanged 事件
【发布时间】:2012-12-22 11:02:35
【问题描述】:

我只是尝试使用 C++/CLI 订阅 WPF 属性更改事件。我没想到这会变得困难。

首先我尝试订阅某个窗口的特定属性(IsMouseDirectlyOver),最后通过以下代码成功:

void MyClass::DependencyPropertyChanged(Object^ sender, DependencyPropertyChangedEventArgs args)
{
   Debug::WriteLine("DependencyPropertyChanged: "+sender->ToString()+", "+args.Property->Name);
}

window->IsMouseDirectlyOverChanged += gcnew DependencyPropertyChangedEventHandler(this, &MyClass::DependencyPropertyChanged);

然后我尝试订阅对象的任何属性更改(这对我来说最重要,因为我的最终代码必须能够通过属性名称处理属性更改)。我完全失败了。

我尝试了各种方法,但都没有奏效。我找不到任何 C++/CLI 示例,但根据文档和 C# 示例,以下对我来说似乎是最明智的代码:

window->PropertyChanged += gcnew PropertyChangedEventHandler(this, &MyClass::PropertyChanged);

void MyClass::PropertyChanged(Object^ sender, PropertyChangedEventArgs^ args)
{
   ...
}

但是编译器通过错误 C2039 告诉我“PropertyChangedEvent”不是“System::Windows::Window”的元素。

我怎样才能实现我想要的?

【问题讨论】:

  • WPF Window 类根本没有实现INotifyPropertyChanged,因此没有 PropertyChanged 事件。
  • 您真的想在窗口中收到关于任何属性更改的通知吗?
  • 是的。但不仅仅是一个窗口。它也应该适用于任何其他类型的可视化树对象。
  • 嗯...不,我并不完全正确。我需要通过 name 订阅属性更改。这实际上并不意味着我需要订阅所有属性。上述机制似乎是唯一允许按名称访问的机制(在事件处理程序方法中使用名称切换/大小写)。如果有一种方法可以通过名称专门订阅一个或多个属性,这也可以解决我的问题。

标签: wpf c++-cli dependency-properties visual-c++-2010 propertychanged


【解决方案1】:

在cmets中提到,你的代码不起作用,因为Window上没有PropertyChanged事件,就这么简单。

您可以改为覆盖the OnPropertyChanged() method,它存在于Window 上。在您的覆盖中,您可以做任何您想做的事情,包括提高 PropertyChanged(不要忘记先创建该事件)。

【讨论】:

  • 我不能覆盖窗口上的方法,因为窗口是预定义的。我无法改变它。我想知道像“Snoop”这样的工具是如何实现的。它监视任何类型的 WPF 对象(甚至是 Windows)的所有属性的变化。我刚试了一下... snoope 似乎能够订阅任何类型对象的所有属性。如果每个 WPF 对象都没有实现 INotifyPropertyChanged,Snoop 使用哪种机制?
  • @user1421332 为什么不简单地看看Snoop's sources
  • 我会这样做的。但是,如果我有足够的 .NET/WPF 知识来知道我在寻找什么,我可能就不需要问了。
  • 似乎 Snoop 使用绑定。我以前从未使用过 Bindings,所以目前我并不完全了解它是如何工作的,以及它是否能解决我的问题。哦,天哪,这真的越来越复杂了。
【解决方案2】:

我查看了窥探源。我对其进行了修改并编写了一个非常非常基本的示例:

String^ ownerPropertyName = "IsActive";
DependencyObject^ propertyOwner = window;

DependencyPropertyDescriptor^ ownerPropertyDescriptor = DependencyPropertyDescriptor::FromName(ownerPropertyName, propertyOwner->GetType(), propertyOwner->GetType());
DependencyProperty^ ownerProperty = ownerPropertyDescriptor->DependencyProperty;
Type^ ownerPropertyType = ownerProperty->PropertyType;

DependencyProperty^ myProperty = DependencyProperty::Register(ownerPropertyName, ownerPropertyType, GetType(), gcnew PropertyMetadata(gcnew PropertyChangedCallback(&MyClass::BoundPropertyChangedCallback)));

Binding^ myPropertyToOwnerPropertyBinding = gcnew Binding(ownerPropertyName);
myPropertyToOwnerPropertyBinding->Mode = BindingMode::OneWay;
myPropertyToOwnerPropertyBinding->Source = propertyOwner;
BindingOperations::SetBinding(this, myProperty, myPropertyToOwnerPropertyBinding);

还有:

static void BoundPropertyChangedCallback(DependencyObject^ me, DependencyPropertyChangedEventArgs args)
{
   Debug::WriteLine("BoundPropertyChangedCallback: "+args.OldValue+", "+args.NewValue+", "+args.Property->Name);
}

对我来说看起来很复杂。我不知道那些绑定的东西是否真的有必要。事实上,这甚至可以订阅没有事件的属性(比如 IsMouseOver),并且可以对没有实现 INotifyPropertyChanged 的​​对象(比如 Window)进行操作。而且它不需要任何属性的开关/案例。

【讨论】:

  • 我很确定我已经在一些博客文章中阅读过这个解决方案,但我不记得在哪里。无论如何,这似乎是正确的做法,至少当 Snoop 这样做时。
【解决方案3】:

PropertyDescriptor 类(或派生的DependencyPropertyDescriptor)提供了一种通过AddValueChanged 方法添加属性更改处理程序的机制:

DependencyPropertyDescriptor^ propertyDescriptor = DependencyPropertyDescriptor::FromName(
    "ActualWidth", component->GetType(), component->GetType());

propertyDescriptor->AddValueChanged(component, gcnew EventHandler(ActualWidthChanged));
...

static void ActualWidthChanged(Object^ component, EventArgs^ e)
{
    ...
}

不幸的是,处理程序没有通过更改的属性,所以我想您必须为要监视的所有属性添加不同的处理程序。


编辑:您可能会实现如下所示的代码,该代码使用匿名委托将属性名称传递给适当的处理程序。但是请注意,这是 C#,据我了解,这不能在 C++/CLI 中完成,因为它不支持托管 lambda。也许你可以将这样的帮助类包装在一个单独的程序集中,并从你的 C++/CLI 代码中使用它。

public delegate void PropertyChangedHandler(object component, string propertyName);

public static class DependencyPropertyDescriptorExt
{
    public static void AddPropertyChangedHandler(
        this object component, string propertyName, PropertyChangedHandler handler)
    {
        var propertyDescriptor = DependencyPropertyDescriptor.FromName(
            propertyName, component.GetType(), component.GetType());
        propertyDescriptor.AddValueChanged(component, (o, e) => handler(o, propertyName));
    }
}

现在您可以像这样编写和使用这样的 PropertyChangedHandler:

this.AddPropertyChangedHandler("ActualHeight", PropertyChanged);
...

private void PropertyChanged(object component, string propertyName)
{
    ...
}

【讨论】:

  • 这行得通。但是正如你所说,我无法区分源属性是一个遗憾。当然,我可以为每个属性添加一个单独的处理程序,但我希望避免必要的 switch/case 狂欢(或类似的)。如果处理程序也不知道源属性,那么使用属性名称实例化 DependencyPropertyDescriptor 的能力就没有多大价值。
  • 这里看到很多C#代码,问题是C++/CLI。虽然在大多数情况下翻译很简单,但您需要一个显式状态对象而不是匿名委托——C++ 委托语法尚不能用于 .NET。
  • @user1421332 你有什么特别的理由让你为 C++ 烦恼吗?如果你使用 C#,编写 WPF 会容易得多。
  • 没有关于原始问题的建议了吗? ;-)
  • 在阅读this article 之后,我很确定这种“匿名委托”解决方案不能在 C++/CLI 中轻松完成,因为它不支持托管 lambda。您不能在 C++/CLI lambda 中使用托管类型,因此不能在匿名方法主体中使用 PropertyChangedHandler^ 之类的东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-23
  • 2011-02-05
  • 2011-12-11
  • 1970-01-01
  • 1970-01-01
  • 2011-04-30
相关资源
最近更新 更多