UPDATED (12/17/2009): Now reflects latest progress I've made.
This is the first application that myself and a co-worker are developing using Prism and MVVM in Silverlight 3.0. I am working on a shell/framework for the project that will have a list of "plugins" that can be added to a "workspace".
The plugins are registered with a WorkSpaceManager class during their particular PRISM IModule.Initialize() methods like so:
workspace.RegisterPlugin(new PluginInfo() { Name = "MyPlugin", ViewType = typeof(MyPluginView), SettingsViewType = null });
The RegisterPlugin() method simply adds the PluginInfo object to a dictionary keyed on the "Name" property. Then when I want to add a plugin to the workspace I do the following:
workspace.AddPluginToWorkspace("MyPlugin");
The AddPluginToWorkspace method of the WorkspaceManager class looks like this:
public void AddPluginToWorkspace(string pluginName)
{
if (AvailablePlugins.ContainsKey(pluginName))
{
PluginInfo pi = AvailablePlugins[pluginName];
WorkspacePlugin wsp = new WorkspacePlugin();
// Create the View
wsp.View = (Control)this.unityContainer.Resolve(pi.ViewType);
wsp.Name = pi.Name;
// Wire up the CloseCommand to WorkspaceManager's PluginClosing handler
wsp.CloseCommand = new DelegateCommand<WorkspacePlugin>(this.PluginClosing);
// Add the plugin to the active plugins (modules) collection
this.modules.Add(wsp);
// FIX: This should notify anyone listening that the ActivePlugins have changed. When enabled, this causes the same error that will be mentioned further on when attempting to close a plugin.
//this.eventAggregator.GetEvent<ActivePluginsChanged>().Publish(wsp);
}
}
The Workspace ViewModel simply exposes the WorkspaceManager Service's modules collection which is the datacontext of the Workspace View as shown here:
<Grid x:Name="LayoutRoot"
Background="White">
<ListBox x:Name="ModuleListBox"
Grid.Row="1"
rgn:RegionManager.RegionName="Workspace"
Background="Yellow"
ItemsSource="{Binding Plugins}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Template>
<ControlTemplate>
<Grid x:Name="ListBoxGrid">
<ItemsPresenter></ItemsPresenter>
</Grid>
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black"
BorderThickness="2"
Margin="0"
Padding="0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="5"></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width=".05*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Content="X"
HorizontalAlignment="Right"
Grid.Column="1"
cmd:Click.Command="{Binding CloseCommand}"
cmd:Click.CommandParameter="{Binding}"></Button>
</Grid>
<Border BorderBrush="Black"
BorderThickness="2"
Margin="0"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Grid.Row="1">
<tk:Viewbox Stretch="Uniform"
StretchDirection="Both">
<ContentControl Content="{Binding View}"></ContentControl>
</tk:Viewbox>
</Border>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Notice the Content control that is bound to the "View" property of the WorkspacePlugin and the Button that has the Click.Command bound to the "CloseCommand". This is where I was stuck initially but for the most part, this works. The plugin's view is loaded inside the other controls and I'm still able to bind the close command (And other commands to be added at a later time) to an underlying model.
The problem now is that whenever I click the close button and the WorkspacePlugin is removed from the modules collection, a property changed event is fired on the ViewModel to let the listbox know to update I get the following error (This also happens if I uncomment the line below the "FIX" comment above:
System.ArgumentException: Value does not fall within the expected range. at MS.Internal.XcpImports.CheckHResult(UInt32 hr) at MS.Internal.XcpImports.SetValue(INativeCoreTypeWrapper obj, DependencyProperty property, DependencyObject doh) at MS.Internal.XcpImports.SetValue(INativeCoreTypeWrapper doh, DependencyProperty property, Object obj) at System.Windows.DependencyObject.SetObjectValueToCore(DependencyProperty dp, Object value) at System.Windows.DependencyObject.RefreshExpression(DependencyProperty dp) at System.Windows.Data.BindingExpression.RefreshExpression() at System.Windows.Data.BindingExpression.SendDataToTarget() at System.Windows.Data.BindingExpression.SourceAquired() at System.Windows.Data.BindingExpression.DataContextChanged(Object o, DataContextChangedEventArgs e) at System.Windows.FrameworkElement.OnDataContextChanged(DataContextChangedEventArgs e) at System.Windows.FrameworkElement.OnTreeParentUpdated(DependencyObject newParent, Boolean bIsNewParentAlive) at System.Windows.DependencyObject.UpdateTreeParent(IManagedPeer oldParent, IManagedPeer newParent, Boolean bIsNewParentAlive, Boolean keepReferenceToParent) at MS.Internal.FrameworkCallbacks.ManagedPeerTreeUpdate(IntPtr oldParentElement, IntPtr parentElement, IntPtr childElement, Byte bIsParentAlive, Byte bKeepReferenceToParent)
From what I gather by looking online, this typically means that a visual element that was already added to the visual tree is trying to be added again. This kind of makes since if I only have 1 plugin displayed and close it, it disappears and there is no error. I am fairly certain that the error is due to the WorkspacePlugin.View property being a visual control and the binding update is attempting to re-add it to the visual tree.
How can I work around this or achieve the desired result without the error message?