ItemsControl 内的 PRISM 视图注入

发布于 2024-08-15 06:42:46 字数 6478 浏览 2 评论 0 原文

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

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

插件在其特定的 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 Service 的模块集合,它是工作区视图的数据上下文,如下所示:

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

< strong>请注意绑定到 WorkspacePlugin 的“View”属性的 Content 控件以及将 Click.Command 绑定到“CloseCommand”的 Button。这是我最初陷入困境的地方,但在大多数情况下,这有效。该插件的视图加载到其他控件内,我仍然能够将关闭命令(以及稍后添加的其他命令)绑定到底层模型。

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

System.ArgumentException:值不在预期范围内。 在 MS.Internal.XcpImports.CheckHResult(UInt32 小时) 在 MS.Internal.XcpImports.SetValue(INativeCoreTypeWrapper obj,DependencyProperty 属性,DependencyObject doh) 在 MS.Internal.XcpImports.SetValue(INativeCoreTypeWrapper doh,DependencyProperty 属性,对象 obj) 在 System.Windows.DependencyObject.SetObjectValueToCore(DependencyProperty dp,对象值) 在 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(对象 o,DataContextChangedEventArgs e) 在 System.Windows.FrameworkElement.OnDataContextChanged(DataContextChangedEventArgs e) 在 System.Windows.FrameworkElement.OnTreeParentUpdated(DependencyObject newParent,布尔 bIsNewParentAlive) 在 System.Windows.DependencyObject.UpdateTreeParent(IManagedPeer oldParent,IManagedPeer newParent,布尔值 bIsNewParentAlive,布尔值 keepReferenceToParent) 在MS.Internal.FrameworkCallbacks.ManagedPeerTreeUpdate(IntPtr oldParentElement,IntPtr ParentElement,IntPtr childElement,字节bIsParentAlive,字节bKeepReferenceToParent)

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

我怎样才能解决这个问题或达到预期的结果而不出现错误消息?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

怪我鬧 2024-08-22 06:42:46

您可能不应该同时使用 Prism Region 和通过 ItemsSource 将视图绑定到 ListView。一般来说,人们会选择其中之一。我认为您可能会看到一些奇怪的行为,因为您两者都有。

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

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

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

}

一天后你应该就可以结束了。您不必从 shell 收集并提供插件集合到您想要显示它们的区域...您应该让您的模块将它们贡献给该区域本身。这将使您保持一定程度的抽象并使您的模块更加自主。

祝你好运。

You likely shouldn't be using both a Prism Region and also binding views to your ListView via ItemsSource. Generally people pick one or the other. I think you might be seeing some odd behavior because you have both.

I would suggest that your Modules contribute "Plugins" in their initialize method.

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

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

}

You should be able to call it a day after this. You shouldn't have to collect and provide a collection of plugins from your shell to the region you want to show them in... you should let your modules contribute them to the region themselves. This will allow you to keep up a certain amount of abstraction and let your modules be more autonomous.

Good luck.

芯好空 2024-08-22 06:42:46

我最终完成了以下工作:

我创建了一个 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 中。我有一些清理工作要做,但我认为效果很好。

感谢您的帮助。

I ended up getting this working doing the following:

I created a WorkspaceItemView view and ViewModel which look roughly like this:

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

}

The WorkspaceManager code to add a plug-in to the workspace looks like this:

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

}

This essentially creates a scoped region, adds the WorkspaceItemView to the workspace region and then resolves and adds the view of the actual plugin to the PluginViewRegion of the newly added WorkspaceItemView. I have a little bit of cleanup work to do but I think it works pretty well.

Thanks for all of your help.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文