Any way to un-register a WPF dependency property?

后端 未结 6 1279
天命终不由人
天命终不由人 2020-12-16 20:14

I\'m running into an unusual problem in my unit tests. The class I\'m testing creates a dependency property dynamically at runtime and the type of that dependency property c

相关标签:
6条回答
  • 2020-12-16 20:19

    I don't think you can un-register a dependency property but you can redefine it by overriding the metadata like this:

    MyDependencyProperty.OverrideMetadata(typeof(MyNewType), 
                         new PropertyMetadata());
    
    0 讨论(0)
  • 2020-12-16 20:21

    I had similar issue just yesterday when trying to test my own DependencyProperty creating class. I came across this question, and noticed there was no real solution to unregister dependency properties. So I did some digging using Red Gate .NET Reflector to see what I could come up with.

    Looking at the DependencyProperty.Register overloads, they all seemed to point to DependencyProperty.RegisterCommon. That method has two portions:

    First to check if the property is already registered

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

    Second, Registering the DependencyProperty

    DependencyProperty dp = 
      new DependencyProperty(name, propertyType, ownerType, 
        defaultMetadata, validateValueCallback);
    
    defaultMetadata.Seal(dp, null);
    //...Yada yada...
    lock (Synchronized)
    {
      PropertyFromName[key] = dp;
    }
    

    Both pieces center around DependencyProperty.PropertyFromName, a HashTable. I also noticed the DependencyProperty.RegisteredPropertyList, an ItemStructList<DependencyProperty> but have not seen where it is used. However, for safety, I figured I'd try to remove from that as well if possible.

    So I wound up with the following code that allowed me to "unregister" a dependency property.

    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);
    }
    

    It worked well enough for me to run my tests without getting an "AlreadyRegistered" exception. However, I strongly recommend that you do not use this in any sort of production code. There is likely a reason that MSFT chose not to have a formal way to unregister a dependency property, and attempting to go against it is just asking for trouble.

    0 讨论(0)
  • 2020-12-16 20:26

    Had an issue with a ContentPresenter with different Datatemplates where one of them had a DependencyProperty with a PropertyChangedCallback When changing ContentPresenters content to another DataTemplate the callback remained.

    In the UserControls Unloaded event i called:

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

    That worked for me

    0 讨论(0)
  • 2020-12-16 20:29

    I was facing scenario where I created a custom control that inherits from Selector which is meant to have two ItemsSource properties, HorizontalItemsSource and VerticalItemsSource.

    I don't even use the ItemsControl property, and don't want the user to be able to access it.

    So I read statenjason's great answer, and it gave me a huge POV on how to remove a DP.
    However, my problem was, that since I declared the ItemsSourceProperty member and the ItemsSource as Private Shadows (private new in C#), I couldn't load it at design time since using MyControlType.ItemsSourceProperty would refer to the shadowed variable.
    Also, when using the loop mentioned in is enswer above (foreach DictionaryEntry etc.), I had an exception thrown saying that the collection has changed during iteration.

    Therefore I came up with a slightly different approach where the DependencyProperty is hardcodedly refered at runtime, and the collection is copied to array so it's not changed (VB.NET, sorry):

    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)
    

    Important note: the above code is all surrounded in the shared constructor of the custom control, and I don't have to check wether it's registered, because I know that a sub-class of Selcetor DOES provide that ItemsSource dp.

    0 讨论(0)
  • 2020-12-16 20:36

    If everything else fails, you can create a new AppDomain for every Test.

    0 讨论(0)
  • 2020-12-16 20:41

    If we register name for a Label like this :

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

    We can easily unregister the name by using :

    this.UnregisterName(myLabel.Name);
    
    0 讨论(0)
提交回复
热议问题