paul
paul

Reputation: 13516

Is it possible to load a control by name using binding?

In my application I need a collection of different I/O devices e.g. Serial, OPC, USB devices etc. I have used the MEF framework to provide a way of handling the collection and allowing new devices to be added.

My project looks something like this:

Atf.Model     'contains the model
Atf.Gui       'the views and view-models
Atf.Devices   'contains implementations of IDevice and exports them

Many devices need configuration so the device interface exposes a path to a custom control and the corresponding view-model which handle the editing of the device configuration. I am trying to stick to the MVVM pattern and want to separate the view and model as far as possible. At the same time I want to keep the coupling to the device collection as loose as possible.

In Atf.Gui I have a control which displays all the discovered devices and displays the activated devices. When an activated device is selected I want to display its' editor dynamically.

How might I do this? Here are some (crazy) ideas:

Idea-1 Simply load a UserControl in my view-model using the path in the device object. This would break the MVVM separation and render that part 'untestable'

public System.Windows.Controls.UserControl ConfigureControl 
{
   get 
   {
       // code to load the UserControl using the path property
   }
}

Idea-2 Have the device expose only a view-model and use mapping (defined in the device repository) to pick up the view. Not sure how this would be done.

<myeditorcontainer>
    <ContainerControl Content="{Binding CurrentlySelectedDeviceViewModel}"/>
</myeditorcontainer>

Idea-3 In my view, load a control using binding. Not sure if this is possible at all.

<myeditorcontainer>
    <UserControl Path="{Binding CurrentlySelectedDeviceViewPath}" 
                 DataContext="{Binding CurrentlySelectedDeviceViewModel}"/>
</myeditorcontainer>

Upvotes: 0

Views: 67

Answers (2)

paul
paul

Reputation: 13516

This is what I did in the end and it seems to work ok. The key features are that the view-model has the same name as the view but with 'Model' appended and that the view-models derive from a particular class - DeviceSettingsBase.

The XAML:

<ContentControl Content="{Binding ConfigControl}"/>

The View-Model:

private RequiredDeviceViewModel rdvm;

public ContentControl ConfigControl
{
    get
    {
        // get assembly which contains this object
        Assembly assy = Assembly.GetAssembly(rdvm.Device.GetType());
        // get View-Type using the name defined in the Device
        Type viewType = assy.GetType(rdvm.Device.ConfigureControlResourceName, true);
        // get ViewModel-Type using the viewname + Model (may be null)
        Type viewModelType = assy.GetType(rdvm.Device.ConfigureControlResourceName + "Model", false);

        // instantiate the view control
        ContentControl view = (ContentControl)Activator.CreateInstance(viewType);
        // instantiate viewModel - if type not null
        if (viewModelType != null)
        {
            object viewModel = (object)Activator.CreateInstance(viewModelType, new object[] { rdvm.RequiredDevice.Config.ConfigString });
            view.DataContext = viewModel;
            // all device viewModels must derive from DeviceSettingsBase
            CurrentConfigViewModel = viewModel as DeviceSettingsBase;
            if (CurrentConfigViewModel != null)
            {
                CurrentConfigViewModel.IsEditable = IsEditable;
                CurrentConfigViewModel.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(vmb_PropertyChanged);
                CurrentConfigViewModel.SettingsChanged += new SettingsChangedHandler(vmb_SettingsChanged);
            }
        }
        return view;
    }
}

Upvotes: 0

Andy
Andy

Reputation: 30418

I think that Idea 2 is the way to go.

Assuming that ContainerControl derives from ContentControl, you should be able to specify a DataTemplate for each type of view model that you'll show, and then the correct UI will be rendered based on the type of the view model. For example:

<myeditorcontainer>
    <ContainerControl Content="{Binding CurrentlySelectedDeviceViewModel}">
        <ContainerControl.Resources>
            <DataTemplate DataType="{x:Type ViewModel1Type}">
                <!-- controls for ViewModel1's UI -->
            </DataTemplate>
            <DataTemplate DataType="{x:Type ViewModel2Type}">
                <!-- controls for ViewModel2's UI -->
            </DataTemplate>
        </ContainerControl.Resources>
    </ContainerControl>
</myeditorcontainer>

If you need more flexibility than the type of the view model in order to decide which template to use, you can specify a ContentTemplateSelector. It will return the correct template to use based on whatever criteria you want.

Upvotes: 1

Related Questions