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?