Reputation: 98
In my application I use multiple Bing Maps WPF controls showing a layer of pushpins. I use MVVM and the maps are placed in a View that can be opened and closed by the user. When the view is closed the Maps are removed from the visual tree and then properly disposed.
However after closing the views they appear to be keeped in memory. After examination with a Memory Profiler it appears the Maps somehow keep a reference to the view and thus it is not removed.
I made a simple test application to demonstrate the leak:
public partial class Window1 : Window
{
private Map map;
public Window1()
{
InitializeComponent();
map = new Map();
map.CredentialsProvider = new ApplicationIdCredentialsProvider("apikey");
map.AnimationLevel = AnimationLevel.None;
map.SetView(new Location(2, 2), 10);
this.Content = map;
}
protected override void OnClosed(EventArgs e)
{
this.Content = null;
map.Dispose();
base.OnClosed(e);
}
}
The window is opened using Window1.ShowDialog();
from a secondary window.
The following Image shows the reference map for the first Window after opening and closing several others, all of which call map.Dispose();
)
Is this indeed a bug in the map and do you know of a way to force the map to really remove all strong references? I tried disabling several off the map options, e.g. turning off animations, touch translations, etc.
Edit: I did some research in the decompiled source of the control. It appears the reference is created in the AnimationDriver class and is caused by the use of a PropertyDescriptor which as you might know will cause a strong reference. I will search for a solution to remove the PropertyDescriptor and will update the question if I found a solution.
Upvotes: 1
Views: 1288
Reputation: 26
Using the following method works, but it has some side effects. For instance if you have multiple maps and calls the method for one of them, the other maps will have animations disabled (zooming, scrolling).
TypeDescriptor.Refresh(map)
The method that solved the problem for me, was to access the AnimationDrivers directly using reflection and then unsubscribe the OnAnimationProgressChanged handler from the DependencyPropertyDescriptor.
public static class BingMapsFix
{
public static void UnhookAnimationDrivers(Map map)
{
Type type = typeof(MapCore);
object zoomAndPanAnimationDriver = type.GetField("_ZoomAndPanAnimationDriver", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetField).GetValue(map);
object modeSwitchAnationDriver = type.GetField("_ModeSwitchAnationDriver", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetField).GetValue(map);
UnhookAnimationDriver(zoomAndPanAnimationDriver);
UnhookAnimationDriver(modeSwitchAnationDriver);
}
private static void UnhookAnimationDriver(object animationDriver)
{
Type type = animationDriver.GetType();
var f = type.GetField("AnimationProgressProperty", BindingFlags.Static | BindingFlags.Public | BindingFlags.GetField).GetValue(animationDriver);
DependencyProperty dp = (DependencyProperty)f;
var m = type.GetMethod("OnAnimationProgressChanged", BindingFlags.Instance | BindingFlags.NonPublic);
EventHandler eh = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), animationDriver, m);
DependencyPropertyDescriptor.FromProperty(dp, type).RemoveValueChanged(animationDriver, eh);
}
}
Upvotes: 1
Reputation: 11
Calling the following removes the Animation Driver references:
TypeDescriptor.Refresh(map)
I have found that I still get some references caused by the MapConfiguration object. I have managed to remove those as well using reflection:
using Microsoft.Maps.MapControl.WPF;
using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Media;
public static class BingMapsKiller
{
public static void Kill(Map map)
{
try
{
TypeDescriptor.Refresh(map);
map.Dispose();
var configType = typeof(Microsoft.Maps.MapControl.WPF.Core.MapConfiguration);
var configuration = configType.GetField("configuration", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic).GetValue(null);
var requestQueue = configuration.GetFieldValue("requestQueue");
var values = (System.Collections.IEnumerable)requestQueue.GetPropertyValue("Values");
foreach (System.Collections.IEnumerable requests in values)
foreach (var request in requests.OfType<object>().ToList())
{
var target = request.GetPropertyValue("Callback").GetPropertyValue("Target");
if (target == map)
requests.ExecuteMethod("Remove", request);
else if (target is DependencyObject)
{
var d = (DependencyObject)target;
if (d.HasParentOf(map))
requests.ExecuteMethod("Remove", request);
}
}
}
catch { }
}
private static Object GetFieldValue(this Object obj, String fieldName)
{
var type = obj.GetType();
return type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).GetValue(obj);
}
private static Object GetPropertyValue(this Object obj, String fieldName)
{
var type = obj.GetType();
return type.GetProperty(fieldName, BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | BindingFlags.Instance).GetValue(obj);
}
private static Object ExecuteMethod(this Object obj, String methodName, params object[] parameters)
{
var type = obj.GetType();
return type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).Invoke(obj, parameters);
}
private static Boolean HasParentOf(this DependencyObject obj, DependencyObject parent)
{
if (obj == null)
return false;
if (obj == parent)
return true;
return VisualTreeHelper.GetParent(obj).HasParentOf(parent);
}
}
Upvotes: 1