简体   繁体   中英

Dependency Injection / StructureMap with controls / UserControls

Currently my Application use UserControl s to represents pages where I can navigate through. Now I want to extend the existing functionality with Dependency Injection like used in this repository ( https://github.com/jpreecedev/WPFMVVMWithStructureMap ).

So my question is, how to deal with Control s used inside of an UserControl (which represents a page in my situation), to use the Dependency Injection pattern with them. If I have the following Control :

public class CustomControl : Control
{
    /// <summary>
    /// Enable or disable the sidebar feature
    /// </summary>
    public bool SidebarEnabled
    {
        get { return (bool)GetValue(SidebarEnabledProperty); }
        set { SetValue(SidebarEnabledProperty, value); }
    }

    /// <summary>
    /// The <see cref="SidebarEnabled"/> DependencyProperty.
    /// </summary>
    public static readonly DependencyProperty SidebarEnabledProperty = DependencyProperty.Register("SidebarEnabled", typeof(bool), typeof(CustomControl), new PropertyMetadata(false));

    public CustomControl()
    {
        DefaultStyleKey = typeof(CustomControl);
    }
}

Example UserControlPage

<basic:SecureUserControlPage 
    xmlns:Foo="http://foo.com/">
    <Grid>
        <Foo:CustomControl SidebarEnabled="{Binding ToPropertyFromSecureUserControlPageViewModel}"/>
    </Grid>
</basic:SecureUserControlPage >

How can I set the SidebarEnabled property by Injection ? Should I bind the property in the xaml code (to the ViewModel of the actual page) or is it also possible to use interfaces in code behind (of the CustomControl ) and set the properties there?

First solution?

The ICustomControlBackend interface is instantiated in the constructor of the Control and set the property .

public interface ICustomControlBackend
{
    bool SidebarEnabled { get; }
    void SomeFancyOperation();
}

public class CustomerABackend : ICustomControlBackend
{
    public bool SidebarEnabled => false;

    public void SomeFancyOperation()
    {
        MessageBox.Show("Hello World");
    }
}

public class CustomerBBackend : ICustomControlBackend
{
    public bool SidebarEnabled => true;
    public void SomeFancyOperation()
    {
        //ignore
    }
}

public class CustomControl : Control
{
    /// <summary>
    /// Enable or disable the sidebar feature
    /// </summary>
    public bool SidebarEnabled
    {
        get { return (bool)GetValue(SidebarEnabledProperty); }
        set { SetValue(SidebarEnabledProperty, value); }
    }

    /// <summary>
    /// The <see cref="SidebarEnabled"/> DependencyProperty.
    /// </summary>
    public static readonly DependencyProperty SidebarEnabledProperty = DependencyProperty.Register("SidebarEnabled", typeof(bool), typeof(CustomControl), new PropertyMetadata(false));

    private readonly ICustomControlBackend _ControlBackend;

    public CustomControl()
    {
        DefaultStyleKey = typeof(CustomControl);

        _ControlBackend = BootstrapperBase.Container.GetInstance<ICustomControlBackend>();
        SidebarEnabled = _ControlBackend.SidebarEnabled;
    }

    public void Foo()
    {
        _ControlBackend.SomeFancyOperation();
    }
}

Second solution?

The ICustomControlBackend interface is instantiated in the constructor of the UserControl and bound to it's property . This property is then bound to the property of the CustomControl .

public interface ICustomControlBackend
{
    bool SidebarEnabled { get; }
    void SomeFancyOperation();
}

public class CustomerABackend : ICustomControlBackend
{
    public bool SidebarEnabled => false;

    public void SomeFancyOperation()
    {
        MessageBox.Show("Hello World");
    }
}

public class CustomerBBackend : ICustomControlBackend
{
    public bool SidebarEnabled => true;
    public void SomeFancyOperation()
    {
        //ignore
    }
}

public class CustomControl : Control
{
    /// <summary>
    /// Enable or disable the sidebar feature
    /// </summary>
    public bool SidebarEnabled
    {
        get { return (bool)GetValue(SidebarEnabledProperty); }
        set { SetValue(SidebarEnabledProperty, value); }
    }

    /// <summary>
    /// The <see cref="SidebarEnabled"/> DependencyProperty.
    /// </summary>
    public static readonly DependencyProperty SidebarEnabledProperty = DependencyProperty.Register("SidebarEnabled", typeof(bool), typeof(CustomControl), new PropertyMetadata(false));


    public CustomControl()
    {
        DefaultStyleKey = typeof(CustomControl);
    }

    public void Foo()
    {

    }
}


public partial class CustomControlView
{
    public bool IsSidebarEnabled { get; }

    private readonly ICustomControlBackend _ControlBackend;

    public CustomControlView()
    {            
        InitializeComponent();

        _ControlBackend = BootstrapperBase.Container.GetInstance<ICustomControlBackend>();
        IsSidebarEnabled = _ControlBackend.SidebarEnabled;
    }
}

<basic:SecureUserControlPage 
    x:Class="LoremIpsum.CustomControlView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:basic="clr-namespace:Foo.Basic"
    xmlns:loremIpsum="clr-namespace:LoremIpsum">
    <Grid>
        <loremIpsum:CustomControl SidebarEnabled="{Binding IsSidebarEnabled}"/>
    </Grid>
</basic:SecureUserControlPage >

The ICustomControlBackend implementation would be done in the application code and not in the library (where CustomControl and CustomControlView is implemented). So I could change the behavior of my CustomControl via Dependency Injection . Is this the way I should go, or is there a better solution to solve it?

It's quite rare for injection of controls as a dependency to be worthwhile. It's not terribly useful in a desktop app.

Consider what views will be dependent on to inject. Data will come via a viewmodel and model which are not part of the view and should be loosely coupled via binding. Styling and templating can come via resource dictionaries which are not part of the view and can be merged. If you use viewmodel first template based view creation then exactly what ends up as a sub view can be defined in a resource dictionary.

The sort of scenario where you might have totally different UI you might want to inject would be a software package sold to multiple clients. If one requires substantially different UI to another then I would use a different project and hence dll for the pieces of UI which differ. Deliver a different dll entirely to client A from client B and they each get their version of X from their dll. No di necessary and you only give client A code they paid for, client B code they paid for.

Similarly, if you want look and feel to change somewhat then you can use templating and deliver different resource dictionaries. Client A gets a resource dictionary with blue controls. Client B gets a resource dictionary with red controls. More commonly this would be iconography or language resources for branding and localisation.

To read documentation on say PRISM you might think building an application totally dynamically by discovering dll was how most apps ought to work. I've done that, worked on very big projects. Once these were mature it became obvious there was no point in the ultimate flexibility of composition we had implemented. Nobody really wanted that and all it really added was complication.

The answer to how you set properties in this context would often be in a resource dictionary but binding is also a possibility.

Switching out functionality in viewmodels is another story and one where you may well want to be looking at interfaces. Even then, it's often most appropriate to just the data supplied to the viewmodel rather than the viewmodels themselves.

This is my practical experience from a number of projects anyhow.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM