有什么方法可以取消注册WPF依赖属性?

时间:2008-09-28 21:35:25

标签: .net wpf dependency-properties

我在单元测试中遇到了一个不寻常的问题。我正在测试的类在运行时动态创建依赖项属性,并且该依赖项属性的类型可以根据具体情况而变化。在编写单元测试时,我需要创建具有不同类型的依赖项属性,这会导致错误,因为您无法重新定义现有的依赖项属性。

那么有没有办法取消注册依赖项属性或更改现有依赖项属性的类型?

谢谢!


OverrideMetadata()只允许您更改默认值等少数内容,因此无效。 AppDomain方法是一个好主意,可能会起作用,但似乎比我真正想要深入研究单元测试更复杂。

我从未找到取消注册依赖项属性的方法,因此我进行了细分并仔细重新组织了单元测试以避免出现问题。我的测试覆盖率有所降低,但由于这个问题在真正的应用程序中永远不会发生,并且只有在单元测试期间才能使用它。

感谢您的帮助!

6 个答案:

答案 0 :(得分:8)

我在昨天尝试测试自己的DependencyProperty创建类时遇到了类似的问题。我遇到了这个问题,并注意到取消注册依赖项属性没有真正的解决方案。所以我使用Red Gate .NET Reflector进行了一些挖掘,看看我能想出什么。

查看DependencyProperty.Register重载,它们似乎都指向DependencyProperty.RegisterCommon。该方法有两个部分:

首先检查属性是否已经注册

FromNameKey key = new FromNameKey(name, ownerType);
lock (Synchronized)
{
  if (PropertyFromName.Contains(key))
  {
    throw new ArgumentException(SR.Get("PropertyAlreadyRegistered", 
      new object[] { name, ownerType.Name }));
  }
}

其次,注册DependencyProperty

DependencyProperty dp = 
  new DependencyProperty(name, propertyType, ownerType, 
    defaultMetadata, validateValueCallback);

defaultMetadata.Seal(dp, null);
//...Yada yada...
lock (Synchronized)
{
  PropertyFromName[key] = dp;
}

这两个部分都围绕着DependencyProperty.PropertyFromName,一个HashTable。我还注意到DependencyProperty.RegisteredPropertyListItemStructList<DependencyProperty>,但没有看到它的使用位置。但是,为了安全起见,我认为如果可能的话,我也会尝试删除它。

所以我最后得到了以下代码,允许我“取消注册”一个依赖属性。

private void RemoveDependency(DependencyProperty prop)
{
  var registeredPropertyField = typeof(DependencyProperty).
    GetField("RegisteredPropertyList", BindingFlags.NonPublic | BindingFlags.Static);
  object list = registeredPropertyField.GetValue(null);
  var genericMeth = list.GetType().GetMethod("Remove");
  try
  {
    genericMeth.Invoke(list, new[] { prop });
  }
  catch (TargetInvocationException)
  {
    Console.WriteLine("Does not exist in list");
  }

  var propertyFromNameField = typeof(DependencyProperty).
    GetField("PropertyFromName", BindingFlags.NonPublic | BindingFlags.Static);
  var propertyFromName = (Hashtable)propertyFromNameField.GetValue(null);

  object keyToRemove = null;
  foreach (DictionaryEntry item in propertyFromName)
  {
    if (item.Value == prop)
      keyToRemove = item.Key;
  }
  if (keyToRemove != null)
  propertyFromName.Remove(keyToRemove);
}

它足以让我在没有获得“AlreadyRegistered”异常的情况下运行我的测试。但是,我强烈建议您不要在任何类型的生产代码中使用它。 MSFT可能有理由选择不采用正式方式取消注册依赖项属性,并试图反对它只是在寻找麻烦。

答案 1 :(得分:2)

如果其他一切都失败了,您可以为每个测试创建一个新的AppDomain。

答案 2 :(得分:0)

我认为您不能取消注册依赖项属性,但您可以通过覆盖这样的元数据来重新定义它:

MyDependencyProperty.OverrideMetadata(typeof(MyNewType), 
                     new PropertyMetadata());

答案 3 :(得分:0)

如果我们为这样的标签注册名称:

Label myLabel = new Label();
this.RegisterName(myLabel.Name, myLabel);

我们可以使用以下方法轻松取消注册名称:

this.UnregisterName(myLabel.Name);

答案 4 :(得分:0)

我遇到的情况是,我创建了一个继承自Selector的自定义控件,该控件具有两个ItemsSource属性HorizontalItemsSourceVerticalItemsSource

我甚至不使用ItemsControl属性,也不希望用户能够访问它。

所以我读了statenjason's great answer,它给了我一个关于如何移除DP的巨大POV 但是,我的问题是,因为我在{C}中宣布ItemsSourceProperty成员和ItemsSourcePrivate Shadowsprivate new),所以我无法在设计时加载它使用MyControlType.ItemsSourceProperty会引用阴影变量 此外,当使用上面提到的循环(foreach DictionaryEntry等)时,我抛出一个异常,说集合在迭代过程中发生了变化。

因此我提出了一种稍微不同的方法,其中DependencyProperty在运行时被硬编码,并且该集合被复制到数组中以便它不会被更改(VB.NET,抱歉):

Dim dpType = GetType(DependencyProperty)
Dim bFlags = BindingFlags.NonPublic Or BindingFlags.Static

Dim FromName = 
  Function(name As String, ownerType As Type) DirectCast(dpType.GetMethod("FromName",
    bFlags).Invoke(Nothing, {name, ownerType}), DependencyProperty)

Dim PropertyFromName = DirectCast(dpType.GetField("PropertyFromName", bFlags).
  GetValue(Nothing), Hashtable)

Dim dp = FromName.Invoke("ItemsSource", GetType(DimensionalGrid))
Dim entries(PropertyFromName.Count - 1) As DictionaryEntry
PropertyFromName.CopyTo(entries, 0)
Dim entry = entries.Single(Function(e) e.Value Is dp)
PropertyFromName.Remove(entry.Key)

重要说明:以上代码全部包含在自定义控件的共享构造函数中,我不必检查它是否已注册,因为我知道Selcetor的子类是否提供ItemsSource dp。

答案 5 :(得分:0)

ContentPresenter存在一个问题,其中包含不同的Datatemplates,其中一个具有带有PropertyChangedCallback的DependencyProperty 将ContentPresenters内​​容更改为另一个DataTemplate时,回调仍然存在。

在UserControls Unloaded事件中,我调用了:

BindingOperations.ClearAllBindings(this);
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, new DispatcherOperationCallback(delegate { return null; }), null);

这对我有用