0

I wrote a really simple ViewModel Locator :

public static readonly DependencyProperty LocateForProperty =
    DependencyProperty.RegisterAttached("LocateFor", 
    typeof (Object), 
    typeof (ViewModelLocator), 
    new PropertyMetadata(null, OnLocateForChanged));

public static object GetLocateFor(DependencyObject dependencyObject)
{
    return dependencyObject.GetValue(LocateForProperty);
}

public static void SetLocateFor(DependencyObject dependencyObject, Object value)
{
    dependencyObject.SetValue(LocateForProperty, value);
}

private static void OnLocateForChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
{
    if (dependencyObject != null)
    {
        var viewModelName = dependencyObject.GetType().AssemblyQualifiedName;
        if (! string.IsNullOrEmpty(viewModelName))
        {
            viewModelName = viewModelName.Replace("View", "ViewModel");
            var viewModel = DependencyLocator.GetInstance(Type.GetType(viewModelName));
            if (viewModel != null)
            {
                ((FrameworkElement) dependencyObject).DataContext = viewModel;
            }
        }
    }
}

And use it like so :

<vm:ViewModelLocator.LocateFor>
    <Binding RelativeSource="{RelativeSource Self}" />
</vm:ViewModelLocator.LocateFor>

It works perfect at runtime but I have lost design time location. I suspect it is because I am using the relative binding, I tried changing it to x:Static but that doesn't work. Are there any suggested changes I could make that would give me back Blendability?

EDIT : About DependencyLocator, it is a static wrapper around Ninject:

using System;
using System.Collections.Generic;

/// <summary>
/// Defines an object that wraps around specific DI containers.
/// </summary>
public static class DependencyLocator
{
    /// <summary>
    /// Gets or Sets the function used for locating a single instance of a specifc type.
    /// </summary>
    public static Func<Type, object> GetInstance;

    /// <summary>
    /// Gets or Sets the function used for locating all instance of a specific type.
    /// </summary>
    public static Func<Type, IEnumerable<object>> GetAllInstances;

    /// <summary>
    /// Gets the implementation of the provided service.
    /// </summary>
    public static T Get<T>()
    {
        return (T)GetInstance(typeof(T));
    }

    /// <summary>
    /// Gets all implementations of the provided service.
    /// </summary>
    public static IEnumerable<T> GetAll<T>()
    {
        return (IEnumerable<T>) GetAllInstances(typeof (T));
    }
}

Which gets populated in an app_start class in app.xaml. It was designed like this for design time purposes and if I make a simple view model locator class using an instanced object defined in the View's xaml it does indeed get the correct values from DependencyLocator, it is only since I switched to Dependency Properties that this issue came up.

deanvmc
  • 5,957
  • 6
  • 38
  • 68
  • What do you have in DependencyLocator.GetInstance ? are you using a DI/IoC framework in there?...if so, which one? – Colin Smith Aug 25 '12 at 18:44
  • @colinsmith I added an edit for you. – deanvmc Aug 25 '12 at 19:09
  • At a guess I'd imagine your Ninject bootstrapper/Kernel isn't getting called at DesignTimes, so there ain't any types set up that it can inject. Were do you call your BootStrapper/kernel at runtime? – Colin Smith Aug 25 '12 at 19:33
  • @colinsmith It worked when I used a ViewModelBinder class that was created in xaml and used INPC instead of dependency properties, same principles. The ninject kernel is populated in a class call appstart which is created in app.xaml. I can verify I am getting ViewModels back from it, just not using the attached properties above. – deanvmc Aug 25 '12 at 19:45
  • I think these lines are your problem....you should be getting using GetValue with LocateFor to get the value that your Binding set ... and do the GetType to get the qualified name on that.... var viewModelName = dependencyObject.GetType().AssemblyQualifiedName; if (! string.IsNullOrEmpty(viewModelName)) – Colin Smith Aug 25 '12 at 19:46

1 Answers1

0

This code is just clarifying what you are trying to achieve:

// Use this to trigger break into debugger when debugging another instance of Visual Studio in order to debug the behaviour at design time.
System.Diagnostics.Debugger.Break();

if (dependencyObject != null)
{
    var view = GetLocateFor( dependencyObject ); // or instead of this access the changed value through the eventargs

    var viewModelName = view.GetType().AssemblyQualifiedName;

    if (! string.IsNullOrEmpty(viewModelName))
    {
        viewModelName = viewModelName.Replace("View", "ViewModel");
        var viewModel = DependencyLocator.GetInstance(Type.GetType(viewModelName));
        if (viewModel != null)
        {
           ((FrameworkElement) view ).DataContext = viewModel;
        }
    }
}
Colin Smith
  • 12,375
  • 4
  • 39
  • 47
  • I see what you are trying to do with this but it didn't work. – deanvmc Aug 25 '12 at 19:56
  • Have a look at my changes....tell me if I'm interpreting what you are trying to do wrongly....also does the OnLocateForChanged definitely get called a runtime, and you've traced through it ?....check the object/type that the "var view" variable is set to. – Colin Smith Aug 25 '12 at 20:00
  • No, you are correct in your changes, I am grabbing the view, then it's name, then replacing the View with ViewModel and getting it from the IoC container. Yes the OnLocateForChanged is hit at runtime (debug confirmed). – deanvmc Aug 25 '12 at 20:06
  • Tried with the eventArgs.NewValue() too, again it works at runtime but not at design time. – deanvmc Aug 25 '12 at 20:19
  • Try setting the designtime versions of the DataContext instead... http://karlshifflett.wordpress.com/2009/10/28/ddesigninstance-ddesigndata-in-visual-studio-2010-beta2/ – Colin Smith Aug 25 '12 at 20:20
  • That would defeat the purpose of trying to locate the ViewModel via IoC. – deanvmc Aug 25 '12 at 20:28
  • Ok, try this ... http://blog.lab49.com/archives/244 ... that should let you debug a separate instance of Visual Studio which is displaying your View at design time, and you then should be able to debug through your attached property at design time. Put in a System.Diagnostics.Debugger.Break() at the start of OnLocateForChanged. – Colin Smith Aug 25 '12 at 20:28