0

I have a custom behavior that allows me to bind a MapControl extent (bounding box) to the ViewModel; it works by calculating the extent of the map whenever the LoadingStatusChanged event of the map fires and setting the value of a DependencyProperty. This works fine -- when the map is panned or zoomed, the bound property in the ViewModel gets updated.

But I would also like the ability to set the extent of the map from the ViewModel and have the map pan/zoom accordingly. I can call TrySetViewBoundsAsync when the dependency property changes, but the problem is knowing if the dependency property change originated from calculating the extent of the map, or from setting the property in the ViewModel. If it is from calculating the extent, it will change the extent of the map, which will trigger the changed event again, and go off in an infinite loop.

I got around this by adding an IsFromEvent property and setting it to true whenever the extent is calculated in reaction to the LoadingStatusChanged event. This prevents the infinite loop but this approach seems...off. It strikes me as hackish and not terribly thread safe.

So...is there a better way to do this? Can the DP be set somehow without causing the PropertyChanged event to fire? Or am I just overthinking this?

public class ExtentBehavior : DependencyObject, IBehavior
{
    public DependencyObject AssociatedObject { get; private set; }

    public void Attach(Windows.UI.Xaml.DependencyObject associatedObject)
    {
        AssociatedObject = associatedObject;

        var mapControl = associatedObject as MapControl;
        mapControl.LoadingStatusChanged += MapLoadingStatusChanged;
    }

    public static readonly DependencyProperty ExtentProperty =
        DependencyProperty.Register("Extent", typeof(MapControl), typeof(ExtentBehavior), new PropertyMetadata(null, OnExtentPropertyChanged));

    public GeoboundingBox Extent
    {
        get { return GetValue(ExtentProperty) as GeoboundingBox; }
        set { SetValue(ExtentProperty, value); }
    }

    private async static void OnExtentPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        var behavior = dependencyObject as ExtentBehavior;
        var mapControl = behavior.AssociatedObject as MapControl;

        if (!behavior.IsFromEvent)
        {
            var result = await mapControl.TrySetViewBoundsAsync(behavior.Extent, null, MapAnimationKind.Default);
        }

        behavior.IsFromEvent = false;
    }

    private bool IsFromEvent { get; set; }

    private void MapLoadingStatusChanged(MapControl sender, object args)
    {
        if (sender.LoadingStatus == MapLoadingStatus.Loaded)
        {
            IsFromEvent = true;
            Extent = GetBounds(sender);
        }
    }

    private GeoboundingBox GetBounds(MapControl mapControl)
    {
        // code omitted
    }
}
Paul Abbott
  • 7,065
  • 3
  • 27
  • 45
  • you can maybe try to use `StackTrace` and check which method called... but I think it might make the code look more "hacky" but should solve any threading issues. http://stackoverflow.com/questions/171970/how-can-i-find-the-method-that-called-the-current-method – Barnstokkr Feb 05 '15 at 06:01
  • Uh, that was a think that took me a lot of time last year... I wrapped the Map in a UserControl and propagated the properties forward. That way I was able to set flags like "fromViewModel" or "fromUserInteraction". Was good old Silverlight times. If it sounds like a suitable solution for you, I can check if I can provide code (over the weekend). – Kai Brummund Feb 05 '15 at 07:58
  • If you're worried about being thread-safe, you can check whether `IsFromEvent` is set **and** you're still in the UI thread (using `Dispatcher.CheckAccess` for instance). Hackish, but safer. – Kevin Gosse Feb 05 '15 at 09:19

1 Answers1

0

There is no way to ask who changed the value of a dependency property. That being said, there is a way; you can walk back up the call stack/trace. XAML developers have done this for years trying to dynamically raise propertychanged without a literal string. Your solution is 1,000% more elegant than using reflection like this.

Sometimes we have to solve problems with solutions - and sometimes solutions are not part of the base framework. Your solution might feel a little off but the fact that it solves the problem is a credit in and of itself. Also, I don't think "thread safe" is a relevant question in this case as all of XAML is on the UI thread. Make sense?

Best of luck.

Jerry Nixon
  • 31,313
  • 14
  • 117
  • 233