在ItemsControl中PRISM View Injection

时间:2009-12-16 20:54:15

标签: c# silverlight mvvm prism

更新(2009年12月17日):现在反映了我所取得的最新进展。

这是我和他的同事在Silverlight 3.0中使用Prism和MVVM开发的第一个应用程序。我正在为项目开发一个shell /框架,它将有一个可以添加到“工作区”的“插件”列表。

插件在其特定的PRISM IModule.Initialize()方法中注册了WorkSpaceManager类,如下所示:

workspace.RegisterPlugin(new PluginInfo() { Name = "MyPlugin", ViewType = typeof(MyPluginView), SettingsViewType = null });

RegisterPlugin()方法只是将PluginInfo对象添加到键入“Name”属性的字典中。然后,当我想在工作区中添加插件时,我会执行以下操作:

workspace.AddPluginToWorkspace("MyPlugin");

WorkspaceManager类的AddPluginToWorkspace方法如下所示:

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);
        }

    }

Workspace ViewModel只是公开了WorkspaceManager服务的模块集合,它是Workspace View的datacontext,如下所示:

<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>

请注意绑定到WorkspacePlugin的“View”属性的Content控件以及将Click.Command绑定到“CloseCommand”的Button。这是我最初被困的地方但是在大多数情况下,这是有效的。插件的视图被加载到其他控件中,我仍然可以将close命令(以及稍后要添加的其他命令)绑定到底层模型。

现在的问题是每当我点击关闭按钮并从模块集合中删除WorkspacePlugin时,会在ViewModel上触发属性更改事件以让列表框知道更新我得到了跟随错误(如果我取消注释上面“FIX”注释下面的行,也会发生这种情况:

  

System.ArgumentException:值不在预期范围内。      在MS.Internal.XcpImports.CheckHResult(UInt32 hr)      在MS.Internal.XcpImports.SetValue(INativeCoreTypeWrapper obj,DependencyProperty属性,DependencyObject doh)      在MS.Internal.XcpImports.SetValue(INativeCoreTypeWrapper doh,DependencyProperty属性,Object obj)      在System.Windows.DependencyObject.SetObjectValueToCore(DependencyProperty dp,Object value)      在System.Windows.DependencyObject.RefreshExpression(DependencyProperty dp)      在System.Windows.Data.BindingExpression.RefreshExpression()      在System.Windows.Data.BindingExpression.SendDataToTarget()      在System.Windows.Data.BindingExpression.SourceAquired()      在System.Windows.Data.BindingExpression.DataContextChanged(Object o,DataContextChangedEventArgs e)      在System.Windows.FrameworkElement.OnDataContextChanged(DataContextChangedEventArgs e)      在System.Windows.FrameworkElement.OnTreeParentUpdated(DependencyObject newParent,Boolean bIsNewParentAlive)      在System.Windows.DependencyObject.UpdateTreeParent(IManagedPeer oldParent,IManagedPeer newParent,Boolean bIsNewParentAlive,Boolean keepReferenceToParent)      在MS.Internal.FrameworkCallbacks.ManagedPeerTreeUpdate(IntPtr oldParentElement,IntPtr parentElement,IntPtr childElement,Byte bIsParentAlive,Byte bKeepReferenceToParent)

通过在线查看我收集的内容,这通常意味着已经添加到可视树中的可视元素正在尝试再次添加。这种情况因为如果我只显示1个插件并关闭它,它就会消失并且没有错误。我很确定该错误是由于WorkspacePlugin.View属性是一个可视控件而绑定更新正在尝试将其重新添加到可视树中。

如何在没有错误消息的情况下解决此问题或获得所需结果?

2 个答案:

答案 0 :(得分:0)

您可能不应该同时使用Prism Region以及通过ItemsSource将视图绑定到ListView。一般人们选择其中一个。我想你可能会看到一些奇怪的行为,因为你有两个。

我建议您的模块在其初始化方法中提供“插件”。

public MyModule : IModule
{
     IRegionManager _mgr;
     public MyModule(IRegionManager mgr)
     {
          _mgr = mgr;
     }

     public void Initialize()
     {
          _mgr.RegisterViewWithRegion("Workspace", typeof(MyPlugin));
     }

}

你应该可以在此之后的第二天打电话。您不应该从shell收集并提供插件集合到您想要显示它们的区域...您应该让您的模块自己将它们贡献给该区域。这将使您能够保持一定程度的抽象,让您的模块更加自主。

祝你好运。

答案 1 :(得分:0)

我最终得到了以下工作:

我创建了一个WorkspaceItemView视图和ViewModel,它看起来大致如下:

<UserControl>
<Grid  x:Name="ResizeGrid"
       MouseEnter="Plugin_MouseEnter"
       MouseLeave="Plugin_MouseLeave">
    <Grid.RowDefinitions>
        <RowDefinition Height="20" />
        <RowDefinition Height="*" />
        <RowDefinition Height="5" />
    </Grid.RowDefinitions>

    <Border x:Name="border"
            BorderBrush="Black"
            BorderThickness="2"
            Padding="0"
            Margin="-1,-1,-1,-1">
    </Border>

    <Grid x:Name="grid"
          Grid.Row="0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width=".05*" />
        </Grid.ColumnDefinitions>
        <Thumb HorizontalAlignment="Stretch"
               Background="Green"
               DragDelta="Thumb_DragDelta"
               />
        <Button Content="X"
                HorizontalAlignment="Right"
                Grid.Column="1"
                cmd:Click.Command="{Binding CloseCommand}"
                cmd:Click.CommandParameter="{Binding PluginID}" />
    </Grid>

    <Border BorderBrush="Black"
            BorderThickness="2"
            Margin="0"
            VerticalAlignment="Center"
            HorizontalAlignment="Center"
            Grid.Row="1">
        <tk:Viewbox Stretch="Uniform"
                    StretchDirection="Both">
            <ContentControl rgn:RegionManager.RegionName="PluginViewRegion" />

        </tk:Viewbox>
    </Border>
    <Thumb x:Name="SizeGrip"
           Grid.Row="1"
           VerticalAlignment="Bottom"
           HorizontalAlignment="Right"
           Width="10"
           Height="10"
           Margin="0,0,-7,-7"
           Style="{StaticResource SizeGrip}"
           DragDelta="SizeGrip_DragDelta"
           DragStarted="SizeGrip_DragStarted"
           DragCompleted="SizeGrip_DragCompleted" />

</Grid>
</UserControl>  

public class WorkspaceItemViewModel : INotifyPropertyChanged
{
    private IWorkspaceManager workspaceManager;
    private IRegionManager regionManager;
    private Guid pluginID;

    public WorkspaceItemViewModel(IWorkspaceManager workspaceManager, IRegionManager regionManager)
    {
        this.workspaceManager = workspaceManager;
        this.regionManager = regionManager;
    }

    public DelegateCommand<object> CloseCommand 
    {
        get
        {
            return workspaceManager.CloseCommand;
        }    
    }

    public DelegateCommand<object> SelectCommand
    {
        get
        {
            return workspaceManager.SelectCommand;
        }
    }

    public object CloseCommandParameter
    {
        get
        {
            return this;
        }
    }

    public Guid PluginID
    {
        get
        {
            return this.pluginID;
        }
        set
        {
            this.pluginID = value;
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs("PluginID"));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

}

将插件添加到工作区的WorkspaceManager代码如下所示:

public void AddPluginToWorkspace(string pluginName)
    {
        PluginInfo pi = AvailablePlugins[pluginName];
        WorkspacePlugin wsp = new WorkspacePlugin();
        wsp.Name = pi.Name;
        wsp.CloseCommand = new DelegateCommand<object>(this.PluginClosing);
        wsp.SelectCommand = new DelegateCommand<object>(this.PluginSelected);
        wsp.id = System.Guid.NewGuid();
        this.modules.Add(wsp.id, wsp);

        var view = this.unityContainer.Resolve(pluginWindowType);
        if (view is IWorkspacePlugin)
        {
            var iwsp = view as IWorkspacePlugin;
            if (iwsp != null)
            {
                iwsp.PluginID = wsp.id;
            }
        }
        else
        {
            throw new ArgumentException("Plugin view containers must implement IWorkspacePlugin.");
        }

        var workspaceRegion = regionManager.Regions["Workspace"];
        var pluginRegion = workspaceRegion.Add(view, wsp.id.ToString(), true);
        this.unityContainer.RegisterInstance<IRegionManager>(wsp.id.ToString(), pluginRegion);
        pluginRegion.Regions["PluginViewRegion"].Context = view;
        pluginRegion.Regions["PluginViewRegion"].Add(this.unityContainer.Resolve(pi.ViewType));

        this.eventAggregator.GetEvent<ActivePluginsChanged>().Publish(wsp);

}

这实质上创建了一个范围区域,将WorkspaceItemView添加到工作区区域,然后解析并将实际插件的视图添加到新添加的WorkspaceItemView的PluginViewRegion中。我有一些清理工作要做,但我认为它运作良好。

感谢您的所有帮助。