d:DesignInstance with an interface type

馋奶兔 提交于 2019-11-27 06:34:35

问题


I'm binding an UI to an interface (which is implemented by several presenters, not accessible from the UI assembly).

I really like d:DesignInstance in designer because it (kind of) makes xaml strongly typed using R#.

Sadly, d:DesignInstance does not support interface types: "Cannot create an instance of an interface."

The first thing I thought is: Ok, no problem, let's create a custom markup extension which takes a System.Type as parameter, and which ProvideValue method returns a fake instance of it (dummy implementation of this interface, generated by dynamic IL emission).

This works quite well, bindings are resolved at design-time (I can see that in the design panel since my markup extension fills the object properties with a lorem-ipsum)

BUT the nicest R# feature dont work: Resharper does not recognize datacontext type, and just gives a message "Cannot resolve property '{0}' in data context of type 'object'"

Does someone know how to fix this ?

(any alternative which would allow me to let R# know about an interface datacontext type would be great)

Thanks !

ps: I also tried to create another markup extension which returns the generated runtime type in order to give it to DesignInstance: "{d:DesignInstance Type={utilsUi:InstanceType commons:User}}" => Gives the error "Object of type 'InstanceType' cannot be converted to type 'System.Type'" ... seems that DesignInstance does not support inner markup extensions :(


回答1:


I have just investigated more or less the same question... Actually, what I did is to follow the principles of MvvMLight. Precisely, I used a ViewModelLocator (which will be more or less static) so that the "right" ViewModel is injected at runtime or at design time. The magic lies in the function ViewModelBase.IsInDesignModeStatic provided by the MvvMLight framework. Finally, my ViewModelLocator class looks like

public class ViewModelLocator
    {
        private static readonly IKernel _kernel;

        static ViewModelLocator()
        {
            _kernel = new StandardKernel();
            if (ViewModelBase.IsInDesignModeStatic)
            {
                 _kernel.Bind<IBasicVM>().To<DesignBasicVm>();
            }
            else
            {
                _kernel.Bind<IBasicVM>().To<BasicVm>();
            }
        }

        public IBasicVM BasicVm { get { return _kernel.Get<IBasicVM>(); } }
    }

You may ignore the Ninject _kernel but you may need it (or a similar Ioc) if you are building your ViewModels with an IoC.

The App.xaml declares the ViewModelLocator as a ressource

  <Application.Resources>
    <ResourceDictionary>
      <viewModel:ViewModelLocator x:Key="ViewModelLocator" />
    </ResourceDictionary>
  </Application.Resources>

The MainWindow.DataContext property is bound to the BasicVM member of the ViewModelLocator. The Text property is bound the the GetContent member of the interface IBasicVM which is recognized statically by R# (at least R# 7.1 with VS2012)

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        DataContext="{Binding BasicVm, Source={StaticResource ViewModelLocator}}"
        >
    <Grid>
        <TextBlock  Text="{Binding GetContent}"/>
    </Grid>
</Window>

You may check this repository which I have created as a template.




回答2:


I've found out how it's possible to overcome this illogical Visual Studio XAML designer error.

Instead of

<SomeControl d:DataContext={d:DesignInstance commons:IUser}>
    <!--element content here-->
</SomeControl>

You can write

<SomeControl>
    <d:SomeControl.DataContext>
        <x:Type Type="commons:IUser" />
    </d:SomeControl.DataContext>
    <!--element content here-->
</SomeControl>

Yes, this solution doesn't look as cool but definitely it isn't much worse.

Type tag allows specifying interfaces, nested types (e.g. n:A+B) and even generics!

In case of generics just add a backtick and number of type arguments to your type: <x:Type Type="commons:User`1" />.

BTW all of these also works with styles, <d:Style.DataContext> doesn't produce errors!




回答3:


You can take advantage of IsDesignTimeCreatable in this case like so:

d:DataContext="{d:DesignInstance commons:User, IsDesignTimeCreatable=False}"

which essentially instructs the designer to only use the type for intellisense and error highlighting instead of actually trying to instantiate a design instance.




回答4:


@Olivier: This is one of the best scenarios where you can make use of "Adapter Design Pattern"

Note: I have no idea on Resharper, I am a C# & .NET developer. but I understand there is compatibility problem based on your explanation. below is the possible solution that you can try.

  1. Write one C# class that implement the Interface type you need.
  2. Implement the interface in the class however do not write the entire implementation on your own. instead call the other class methods that implement the interface within the implemented methods.
  3. start using the class that you have written.
  4. the class that you have written to get the compatibility is known as Adapter.

sample code:

class Program
{
    static void Main(string[] args)
    {
        // create an object of your adapter class and consume the features.
        AdapterInterfaceUI obj = new AdapterInterfaceUI();

        // Even though you have written an adapter it still performs the operation in base class
        // which has the interface implementation and returns the value.
        // NOTE : you are consuming the interface but the object type is under your control as you are the owner of the adapter class that you have written.
        Console.WriteLine(obj.DisplayUI());
        Console.ReadKey();
    }
}


#region code that might be implemented in the component used.
public interface IinterfaceUI
{
    string DisplayUI();
}

public class ActualUI : IinterfaceUI
{
    //Factory pattern that would be implemented in the component that you are consuming.
    public static IinterfaceUI GetInterfaceObject()
    { 
        return new ActualUI();
    }

    public string DisplayUI()
    {
        return "Interface implemented in ActualUI";
    }
}
#endregion


#region The adapter class that you may need to implement in resharper or c# which ever works for you.
public class AdapterInterfaceUI : ActualUI, IinterfaceUI
{
    public string DisplayUI()
    {
        return base.DisplayUI();
    }
}
#endregion

I think this solution will help you out.



来源:https://stackoverflow.com/questions/18814615/ddesigninstance-with-an-interface-type

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!