berti
berti

Reputation: 127

How to dynamically load UserControls to TabControl

I have several UserControls (in different DLLs) that I am inserting as TabItems into a TabControl of my main Application (EXE). This is the XAML I use to load it and add it to the TabControl:

<TabControl x:Name="mainTabControl">
    <TabItem Style="{StaticResource VertAlign}" Header="firstDll">
        <view1:ControlOfTheFirstDll />
    </TabItem>
    <TabItem Style="{StaticResource VertAlign}" Header="secondDll">
        <view2:ControlOfTheSecondDll />
    </TabItem>
</TabControl>

Now, I changed my program so that I load each DLL (Data1.dll, Data2.dll) dynamically. With the Startup of my program, an instance of the following class Plugin is created and can be accessed from within my ViewModel.

class Plugin
{
    [ImportMany]
    public IEnumerable<IDataProvider> DataProvider { get; set; }

    public Plugin()
    {
        try
        {
            AggregateCatalog aggregatecatalogue = new AggregateCatalog();               
            aggregatecatalogue.Catalogs.Add(new AssemblyCatalog(Assembly.LoadFile("...\\Data1.dll")));
            aggregatecatalogue.Catalogs.Add(new AssemblyCatalog(Assembly.LoadFile("...\\Data2.dll")));
            aggregatecatalogue.Catalogs.Add(new AssemblyCatalog(Assembly.GetAssembly(typeof(IDataProvider))));

            CompositionContainer container = new CompositionContainer(aggregatecatalogue);
            container.ComposeParts(this);
        }
        catch (FileNotFoundException fnfex)
        {
            Console.WriteLine(fnfex.Message);
        }
        catch (CompositionException cex)
        {
            Console.WriteLine(cex.Message);
        }
    }
}

Somehow, I need to load the UserControls, which are based in Data1.dll and Data2.dll, dynamically. Something like:

<TabControl x:Name="mainTabControl">
    <TabItem Style="{StaticResource VertAlign}" Header="firstDll">
        <ContentControl Content="{Binding PluginInstance.Control.???}" />
    </TabItem>
    <TabItem Style="{StaticResource VertAlign}" Header="secondDll">
        <ContentControl Content="{Binding PluginInstance.Control.???}" />
    </TabItem>
</TabControl>

However, I can't seem to get the binding on the control working.

Upvotes: 1

Views: 1035

Answers (2)

mickeymicks
mickeymicks

Reputation: 669

i just recently created something similar but it does not exactly match your implementation as i'm doing it through simple reflection (and the app is not MVVM at all). here it is, i hope you can pick up something useful.

a. all the the dlls have a class that implement IPlugin interface

public interface IPlugin 
{
    string Name { get; } 
    UserControl PluginControl { get; } //or a tab item 
}

//a sample dll would have 
public class SamplePlugin : IPlugin 
{
    public string Name { get { return "sample"; } } 
    public PluginControl 
    {
        get { 
            return AnInstanceOfControlHere; //create this somewhere 
        }
    }
}

b. in my host application, scan all the dlls and reflect all classes that implements IPlugin, then instantiate.

List<IPlugin> _LISTOFALLPLUGINS = new List<IPlugin>(); 
public void LoadPlugins()
{
    if (Directory.Exists(YOURFOLDERPATH_HERE)) 
    {
        string[] pluginfiles = Directory.GetFiles(YOURFOLDERPATH_HERE, "*.dll", SearchOption.AllDirectories); 

        var pluginbasetype = typeof(IPlugin);

        foreach (string pluginpath in pluginfiles)
        {
            Assembly assembly = Assembly.LoadFile(pluginpath);
            List<Type> plugins = assembly.GetTypes().Where(t => pluginbasetype.IsAssignableFrom(t)).ToList();

            foreach (Type plugintype in plugins)
            {
                if (!plugintype.IsAbstract)
                {
                    IPlugin plugin = assembly.CreateInstance(plugintype.FullName) as IPlugin;

                    _LISTOFALLPLUGINS.Add(plugin);
                }
            }
        }
    }
}

c. then simply load the controls

public void ShowPlugins() 
{
    foreach(IPlugin plugin in _LISTOFALLPLUGINS) 
    {
        TabItem ti = new TabItem();
        ti.Header = plugin.Name;

        UserControl uc = app.PluginControl;
        if (uc != null)
        {
            ti.Content = uc;
        }

        MYTAB.Items.Add(ti); 
    }
}

note: this is probably not what you're looking for especially with regards to just using xaml, but i find that this is quite a simple plugin-style components that's easy to implement. aside from UI, you can incorporate complex operations as well in the plugin dll. this was based on something i read many years back, though. can't find the link.

*if not you, then i do hope someone else can find it useful :)

Upvotes: 1

N.J
N.J

Reputation: 1230

You can try like this since your usercontrol in another dll. create a new project with two interface in it

public interface IMetaData
{
    string Name
    {
        get;
    }
}

public interface IView
{ }

and reference this project to your Main application and data1.dll add IView to your usercontrol like this.

[Export(typeof(IView)), PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.Any)]
[ExportMetadata("Name", "uc1")]
public partial class myusercontrol : UserControl,IView
{
    public ServiceContractList()
    {
        InitializeComponent();
    }
}

and in your view model

class Plugin
{
[ImportMany]
public IEnumerable<IDataProvider> DataProvider { get; set; }

//add this
[ImportMany(typeof(IView), AllowRecomposition = true)]
    public IEnumerable<Lazy<IView,IMetaData>> Plugins
    {
        get;
        set;
    }

public Plugin()
{
    try
    {
        AggregateCatalog aggregatecatalogue = new AggregateCatalog();               
        aggregatecatalogue.Catalogs.Add(new AssemblyCatalog(Assembly.LoadFile("...\\Data1.dll")));
        aggregatecatalogue.Catalogs.Add(new AssemblyCatalog(Assembly.LoadFile("...\\Data2.dll")));
        aggregatecatalogue.Catalogs.Add(new AssemblyCatalog(Assembly.GetAssembly(typeof(IDataProvider))));

        CompositionContainer container = new CompositionContainer(aggregatecatalogue);
        container.ComposeParts(this);
    }
    catch (FileNotFoundException fnfex)
    {
        Console.WriteLine(fnfex.Message);
    }
    catch (CompositionException cex)
    {
        Console.WriteLine(cex.Message);
    }
}

}

create a property in the view model of type object

private object _PlugIn;
    public object PlugIn
    {
        get
        {
            return _PlugIn;
        }
        set
        {
            if (value != _PlugIn)
            {
                _PlugIn = value;
                OnPropertyChanged("PlugIn");
            }
        }
    }

bind this to your ContentControl

 <TabControl x:Name="mainTabControl">
<TabItem Style="{StaticResource VertAlign}" Header="firstDll">
    <ContentControl Content="{Binding PlugIn}" />
</TabItem>


access your usercontrol in the viewmodel like this

var pluginContainer = Plugins.First(x => x.Metadata.Name == "uc1");
if (pluginContainer != null)
{
  PlugIn=pluginContainer.Value;
 OnPropertyChanged("PlugIn"); 
}

Upvotes: 1

Related Questions