I am building a cockpit, i have lot of user control (different types of switch) and i try to integrate them in a window
I am using caliburn and Ninject and try to keep MVVM.
So i have a problem, i have to integrate the different switch dynamically into the Grid of window and i dont know if i could keep MVVM
So in my solution i am using the name of the grid to place the different UserControls at different positions and i break MVVM
How i could do that with MVVM? I have read i could use ContentControl to bind the different ViewModels from a list, but i dont see how to do that.. some help will be welcome
Bootstrapper.cs:
using Caliburn.Micro;
using Ninject;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using TestNinjectCaliburn.ViewModels;
using EventAggregator = TestNinjectCaliburn.Events.EventAggregator;
using IEventAggregator = TestNinjectCaliburn.Events.IEventAggregator;
namespace TestNinjectCaliburn
{
public class Bootstrapper : BootstrapperBase
{
private IKernel kernel;
public Bootstrapper()
{
Initialize();
}
protected override void Configure()
{
kernel = new StandardKernel();
kernel.Bind<IEventAggregator>().To<EventAggregator>().InSingletonScope();
kernel.Bind<IWindowManager>().To<WindowManager>().InSingletonScope();
kernel.Bind<MainWindowViewModel>().ToSelf().InSingletonScope();
MessageBinder.SpecialValues.Add("$pressedkey", (context) =>
{
// NOTE: IMPORTANT - you MUST add the dictionary key as lowercase as CM
// does a ToLower on the param string you add in the action message, in fact ideally
// all your param messages should be lowercase just in case. I don't really like this
// behaviour but that's how it is!
var keyArgs = context.EventArgs as KeyEventArgs;
if (keyArgs != null)
return keyArgs.Key;
return null;
});
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<MainWindowViewModel>();
}
protected override object GetInstance(Type service, string key)
{
return kernel.Get(service);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return kernel.GetAll(service);
}
}
}
MainWindowViewModel.cs which calls SecondViewModel.cs
using Caliburn.Micro;
using IEventAggregator = TestNinjectCaliburn.Events.IEventAggregator;
using Ninject.Syntax;
namespace TestNinjectCaliburn.ViewModels
{
public class MainWindowViewModel
{
private readonly IWindowManager windowmanager;
private readonly SecondViewModel[] secondviewmodel;
private readonly IEventAggregator eventAggregator;
public MainWindowViewModel(IWindowManager windowmanager, IEventAggregator eventAggregator, IResolutionRoot resolutionRoot, SecondViewModel secondviewmodel)
{
this.eventAggregator = eventAggregator;
this.windowmanager = windowmanager;
this.secondviewmodel = new SecondViewModel[1];
this.secondviewmodel[0] = new SecondViewModel(eventAggregator, resolutionRoot);
}
public void Launch()
{
windowmanager.ShowWindow(secondviewmodel[0]);
}
}
}
SecondViewModel.cs:
using Caliburn.Micro;
using Ninject;
using Ninject.Parameters;
using Ninject.Syntax;
using System;
using System.Linq;
using System.Reflection;
using System.Windows.Controls;
using TestNinjectCaliburn.Views;
using IEventAggregator = TestNinjectCaliburn.Events.IEventAggregator;
namespace TestNinjectCaliburn.ViewModels
{
public class SecondViewModel:Screen
{
private readonly IEventAggregator eventAggregator;
private readonly IResolutionRoot resolutionRoot;
private UserControl usercontrol;
public SecondView secondView;
public SecondViewModel(IEventAggregator eventAggregator, IResolutionRoot resolutionRoot)
{
this.eventAggregator = eventAggregator;
this.resolutionRoot = resolutionRoot;
}
protected override void OnViewReady(object view)
{
secondView = view as SecondView;
}
public Type[] Typelist;
//here i break MVVM ************************
protected override void OnViewAttached(object secondview, object context)
{
Element[] elts = {
new Element()
{
viewmodel = Type.GetType("TestNinjectCaliburn.Gauges." + "SwitchOffOn_ViewModel"),
Top = 100,
Left = 100
},
new Element()
{
viewmodel = Type.GetType("TestNinjectCaliburn.Gauges." + "SwitchOffOn_ViewModel"),
Top = 100,
Left = 200
},
new Element()
{
viewmodel = Type.GetType("TestNinjectCaliburn.Gauges." + "SwitchOffOn_ViewModel"),
Top = 100,
Left = 300
},
new Element()
{
viewmodel = Type.GetType("TestNinjectCaliburn.Gauges." + "SwitchOffOn_ViewModel"),
Top = 100,
Left = 400
},
};
var MainGrid = (secondview as SecondView).MainGrid;
for (int i = 0; i < elts.Length; i++)
{
Ninject.Parameters.Parameter[] param = {
new ConstructorArgument("left", elts[i].Left , true),
new ConstructorArgument("top", elts[i].Top, true)
};
// Replace the Activator.CreateInstance
var viewmodel = resolutionRoot.TryGet(elts[i].viewmodel, param);
var view = ViewLocator.LocateForModel(viewmodel, null, null);
ViewModelBinder.Bind(viewmodel, view, null);
MainGrid.Children.Add(view);
}
}
}
public class Element
{
public Type viewmodel;
public double Top;
public double Left;
}
}
An example of one of usercontrols i want to set:
SwitchOffOn_ViewModel.cs
using System;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using IEventAggregator = TestNinjectCaliburn.Events.IEventAggregator;
namespace TestNinjectCaliburn.Gauges
{
public class SwitchOffOn_ViewModel : TemplateSwitch
{
private readonly IEventAggregator eventAggregator;
public SwitchOffOn_ViewModel(IEventAggregator eventAggregator, double left, double top)
{
this.eventAggregator = eventAggregator;
this.eventAggregator.Subscribe(this);
//Tag = tag;
var folder = Environment.CurrentDirectory + "\\Images\\Elements\\";
SwitchImage = new string[] { folder + "switch_n0.png", folder + "switch_n2.png" };
NbImages = SwitchImage.Length;
SwitchIndex = 0;
//double left = 200, top = 0;
UCLeft = left;
UCTop = top;
InitialSize = 40;
scaleX = InitialSize / (new BitmapImage(new Uri(SwitchImage[0])).PixelWidth / 2d);
angle = 0d;
}
#region Mouse Events
public void MouseEnter(MouseEventArgs e)
{
ToolTip = (e.OriginalSource as UserControl).Margin.ToString();
}
#endregion
}
}
SwitchOffOn_View.xaml:
<UserControl
x:Class="TestNinjectCaliburn.Gauges.SwitchOffOn_View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d" d:DesignHeight="150" d:DesignWidth="70"
x:Name="usercontrol" Height="150" Width="70" RenderTransformOrigin="0.5,0.5" Tag="{Binding Tag, Mode=OneTime}" ToolTip="{Binding ToolTip}"
VerticalAlignment = "Top" HorizontalAlignment = "Left" ClipToBounds="True"
cal:Message.Attach="[Event MouseEnter] = [Action MouseEnter($eventArgs)]">
<UserControl.Margin>
<MultiBinding Converter="{StaticResource MyMultiConverterMargin}">
<Binding Path="UCLeft" UpdateSourceTrigger="PropertyChanged"></Binding>
<Binding Path="UCTop" UpdateSourceTrigger="PropertyChanged"></Binding>
</MultiBinding>
</UserControl.Margin>
<UserControl.LayoutTransform>
<TransformGroup>
<RotateTransform x:Name="rotation" Angle="{Binding angle}"/>
<ScaleTransform x:Name="scale" ScaleX="{Binding scaleX}" ScaleY="{Binding ElementName=scale, Path=ScaleX}"/>
</TransformGroup>
</UserControl.LayoutTransform>
<Grid RenderTransformOrigin="0.5,0.5">
<Image x:Name="SwitchUp" Source="{Binding SwitchImage[1], Mode=OneTime}"
Width="{Binding ElementName=usercontrol, Path=Width}"
Height="{Binding ElementName=usercontrol, Path=Height}"
HorizontalAlignment="Center" VerticalAlignment="Center" >
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding SwitchIndex}" Value="1">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Image x:Name="SwitchDown" Source="{Binding SwitchImage[0], Mode=OneTime}"
Width="{Binding ElementName=usercontrol, Path=Width}"
Height="{Binding ElementName=usercontrol, Path=Height}"
HorizontalAlignment="Center" VerticalAlignment="Center" >
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding SwitchIndex}" Value="0">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Rectangle x:Name="UpperRec" Visibility="Visible" Margin="5,5,0,0"
cal:Message.Attach="[Event MouseLeftButtonDown] = [Action MouseLeftButtonDown('true')]"
Width="{Binding ElementName=usercontrol, Path=Width, Converter={StaticResource MyConverterSize}, ConverterParameter=1 10}"
Height="{Binding ElementName=usercontrol, Path=Height, Converter={StaticResource MyConverterSize}, ConverterParameter=2 10}"
HorizontalAlignment="Left" VerticalAlignment="Top" Style="{StaticResource IsModeEdit}" >
</Rectangle>
<Rectangle x:Name="LowerRec" Visibility="Visible" Margin="0 0 5 5"
cal:Message.Attach="[Event MouseLeftButtonDown] = [Action MouseLeftButtonDown('false')]"
Width="{Binding ElementName=UpperRec, Path=Width}"
Height="{Binding ElementName=UpperRec, Path=Height}"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Style="{StaticResource IsModeEdit}" >
</Rectangle>
<Rectangle x:Name="DesignFrame"
Visibility="{Binding Frame, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource BoolToVisConverter}}"
Width="{Binding ElementName=usercontrol, Path=Width, UpdateSourceTrigger=PropertyChanged}"
Height="{Binding ElementName=usercontrol, Path=Height, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Style="{StaticResource IsModeSelected}" >
</Rectangle>
</Grid>
</UserControl>
MainWindowView.xaml:
<Window x:Class="TestNinjectCaliburn.Views.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindowView" Height="250" Width="400">
<Grid>
<Button x:Name = "Launch" Content ="Launch Test Ninject and Caliburn" HorizontalAlignment="Left" Margin="115,25,0,0" VerticalAlignment="Top" Width="209"/>
</Grid>
</Window>
SecondView.xaml:
<Window x:Class="TestNinjectCaliburn.Views.SecondView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestNinjectCaliburn.Views"
mc:Ignorable="d"
Title="SecondView" Height="400" Width="800">
<Grid x:Name="MainGrid">
</Grid>
</Window>
the result i would have (keeping MVVM):
Following your answer,
i have created BaseViewModel for all my usercontrols
namespace TestNinjectCaliburn.Gauges
{
public abstract class BaseViewModel:TemplateSwitch
{
}
}
i have added the BaseViewModel to my ViewModels
public class SwitchOn_Off_On_ViewModel : BaseViewModel
public class SwitchOffOn_ViewModel : BaseViewModel
i have added in SecondViewModel.cs:
the definition of the collection of ViewModel:
private ObservableCollection<BaseViewModel> _myCockpitViewModels = new ObservableCollection<BaseViewModel>();
public ObservableCollection<BaseViewModel> MyCockpitViewModels
{
get { return _myCockpitViewModels; }
set
{
_myCockpitViewModels = value;
NotifyOfPropertyChange(() => MyCockpitViewModels);
}
}
and i have loaded the list:
for (int i = 0; i < elts.Length; i++)
{
//if (!typelist[i].ToString().Replace("_", "").Contains("Switch")) continue;
Ninject.Parameters.Parameter[] param = {
new ConstructorArgument("left", elts[i].Left , true),
new ConstructorArgument("top", elts[i].Top, true)
};
// Replace the Activator.CreateInstance new Ninject.Parameters.Parameter[0]
var viewmodel = resolutionRoot.TryGet(elts[i].viewmodel, param);
var view = ViewLocator.LocateForModel(viewmodel, null, null);
ViewModelBinder.Bind(viewmodel, view, null);
MyCockpitViewModels.Add((BaseViewModel)viewmodel);
now i have that in my SecondView.xaml:
<Viewbox x:Name="MainGrid" >
<ItemsControl ItemsSource="{Binding MyCockpitViewModels}" Width="1725" Height="800">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type vm:SwitchOffOn_ViewModel}">
<vm:SwitchOffOn_View />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SwitchOn_Off_On_ViewModel}">
<vm:SwitchOn_Off_On_View />
</DataTemplate>
</ItemsControl.Resources>
<!-- Replace panel with a canvas -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- Set position of each element in the canvas -->
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Canvas.Left" Value="{Binding UCLeft}" />
<Setter Property="Canvas.Top" Value="{Binding UCTop}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Viewbox>
hum if i replace the definition of DataTemplate by
<ItemsControl ItemsSource="{Binding MyCockpitViewModels}" Width="1725" Height="800">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl cal:View.Model="{Binding .}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
i dont need to define each control in datatemplate, caliburn seems to do the job..