سؤال

I am trying to dynamically create and load controls into a data window.

I have tabs across the top which are different types of reports. I want to be able to create new reports without having to remember to add them to the tab control. I am trying to do this using a factory, using reflection to identify views that implement a certain interface. Once the controls are instantiated (code below) I want to wrap them in a TabItem and add them to my tab control. Here's the Factory:

class ReportHandlerFactory : IReportHandlerFactory
{
    private static IList<IReportControl> ReportHandlers;

    public IEnumerable<IReportControl> GetReportHandlers()
    {
        if (null == ReportHandlers)
        {
            ReportHandlers = LoadHandlers() ?? new List<IReportControl>();

            if (ReportHandlers.Count < 1)
            {
                ReportHandlers.Add(new DefaultReportControl());
            }
        }

        return ReportHandlers;
    }

    private static IList<IReportControl> LoadHandlers()
    {
        return (from t in Assembly.GetExecutingAssembly().GetTypes()
                where t.GetInterfaces().Contains(typeof(IReportControl))
                      && !t.IsAbstract
                      && !(t.IsEquivalentTo(typeof(DefaultReportControl)))
                select (IReportControl)Activator.CreateInstance(t)
                   ).ToList<IReportControl>();
    }

    public class DefaultReportControl : TabItem, IReportControl
    {
        public DefaultReportControl() : base()
        {
            Header = "Error";
            Content = "No Reports Found.";
        }

        public new string Header
        {
            get { return base.Header.ToString(); }
            private set { base.Header = value; }
        }

        public IReportHandler ReportHandler
        {
            get { throw new Exception("No Handler Available for Default Report Control."); }
        }
    }

Here is my MainViewDataWindow:

<mvvm:DataWindow x:Class="Petersco.Reports.Views.MainWindowView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:winForms="clr-namespace:Microsoft.Reporting.WinForms;assembly=Microsoft.ReportViewer.WinForms"
    xmlns:mvvm="clr-namespace:Catel.Windows;assembly=Catel.MVVM"
    xmlns:views="clr-namespace:Petersco.Reports.Views"
    xmlns:catel="http://catel.codeplex.com"
    ShowInTaskbar="True" ResizeMode="CanResize" Icon="../Images/Icons/favicon.ico"
    Title="PCL Reports" MinHeight="768" MinWidth="1024">
<Grid Loaded="Grid_Loaded">

    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="1*"/>
    </Grid.RowDefinitions>

    <TabControl Grid.Row="0" Margin="10,10,10,0" SelectedItem="{Binding SelectedReportTabItem}" 
                ItemsSource="{Binding ReportTabItems}" />
    <Button Grid.Row="1" Margin="0,5,10,10" Width="75"
                Content="Run Report" Command="{Binding RunReport}"
                HorizontalAlignment="Right" />
    <WindowsFormsHost Grid.Row="2" Margin="10,0,10,10">
        <winForms:ReportViewer x:Name="_reportViewer"/>
    </WindowsFormsHost>
</Grid>

Here is the ViewModel:

public class MainWindowViewModel : ViewModelBase
{
    private readonly IReportHandlerFactory _reportHandlerFactory;

    public MainWindowViewModel(IMessageMediator messageMediator,
                               IReportHandlerFactory reportHandlerFactory) : base(messageMediator)
    {
        Argument.IsNotNull(() => reportHandlerFactory);
        _reportHandlerFactory = reportHandlerFactory;

        ReportTabItems = new ObservableCollection<TabItem>(
            _reportHandlerFactory.GetReportHandlers().Select(x =>
                                                             new TabItem
                                                             {
                                                                 Header = x.Header,
                                                                 Content = x
                                                             }
                )
            );
        SelectedReportTabItem = ReportTabItems[0];
        RunReport = new Command(ExecuteRunReport, CanExecuteRunReport);
    }

    private bool CanExecuteRunReport()
    {
        if (SelectedReportTabItem == null)
        {
            return false;
        }
        return SelectedReportTabItem != null && GetReportHandler().ReportHandler.CanExecuteConstruct();
    }

    private void ExecuteRunReport()
    {
        if (SelectedReportTabItem == null)
        {
            return;
        }
        WaitCursor.Show();
        GetReportHandler().ReportHandler.Construct(ReportControl);
        ReportControl.RefreshReport();
    }

    private IReportControl GetReportHandler()
    {
        return SelectedReportTabItem.Content as IReportControl;
    }

    public Command RunReport { get; set; }
    public ReportViewer ReportControl { get; set; }
    public TabItem SelectedReportTabItem { get; set; }
    public ObservableCollection<TabItem> ReportTabItems { get; set; } 
}

The problem is when views are instantiated in this way, none of the Catel magic happens with initializing the ViewModel. Perhaps I'm not approaching this in the correct way, but is there a facility/helper in Catel for loading/initializing views/viewmodels programmatically?

هل كانت مفيدة؟

المحلول

The magic in Catel happens via the UserControlLogic class. This is a class that can be used by all UserControls and makes sure that once the view gets loaded, the magic happens.

If you want the views to support the Catel magic, make sure to derive from Catel.Windows.Controls.UserControl or create an instance of the UserControlLogic in your user controls yourself.

I think the best you can do is create a "TabItemWrapper" class that derives from Catel.Windows.UserControl (so you get all the magic) and you can put the content in there. Note though that view models are by default resolved by naming conventions, so even for dynamically created views you can follow the naming conventions.

btw. Creating views in your view model isn't really MVVM. Creating views, etc can be done in services (which you can mock) or in the code-behind (yes, code-behind).

نصائح أخرى

So I tried doing something like this:

    private IList<IReportControl> LoadHandlers()
    {
        var controls = (from t in Assembly.GetExecutingAssembly().GetTypes()
                        where t.GetInterfaces().Contains(typeof(IReportControl))
                              && !t.IsAbstract
                              && !(t.IsEquivalentTo(typeof(DefaultReportControl)))
                        select t).ToList();

        var result = new List<IReportControl>();
        foreach (var ctrl in controls)
        {
            var serviceType = ctrl.GetInterfaces().FirstOrDefault(x => x.Name.Equals(typeof(IView<>).Name));
            _serviceLocator.RegisterType(serviceType, ctrl);
            result.Add(_serviceLocator.ResolveType(serviceType) as IReportControl);
        }

        return result;
    }

Hoping that perhaps the Catel ServiceLocator would perform it's magic when instantiating the UserControl (by magic I mean initializing and setting the ViewModel), but this does not work either. Will I have to do this process by hand?

Ok, so far this is the only solution that appears to do what I want, please let me know if there is a cleaner and/or recommended way of doing this.

I have added a constructor to all of my Views that are based of UserControl and that will be TabItems:

public MyViewConstructor(IViewModel vmb) : base(vmb)
{
}

Next I have the following to dynamically construct the controls:

private IList<IReportControl> LoadHandlers()
{
    var controls = (from t in Assembly.GetExecutingAssembly().GetTypes()
                    where t.GetInterfaces().Contains(typeof(IReportControl))
                          && !t.IsAbstract
                          && !(t.IsEquivalentTo(typeof(DefaultReportControl)))
                    select t).ToList();
    var result = new List<IReportControl>();
    foreach (var ctrl in controls)
    {
        var serviceType = ctrl.GetInterfaces().FirstOrDefault(x => x.Name.Equals(typeof(IView<>).Name));
        var viewModelType = serviceType.GetGenericArguments().FirstOrDefault();  
        var vmFactory = _serviceLocator.ResolveType<IViewModelFactory>();
        var vm = vmFactory.CreateViewModel(viewModelType, null);
        var reportControl = Activator.CreateInstance(ctrl, new object[] {vm});
        result.Add(reportControl as IReportControl);
    }

    return result;
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top