【问题标题】:PRISM View Injection inside ItemsControlItemsControl 中的 PRISM 视图注入
【发布时间】:2009-12-16 20:54:14
【问题描述】:

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

这是我和同事在 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 服务的模块集合,它是 Workspace View 的数据上下文,如下所示:

<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。这是我最初被卡住的地方,但在大多数情况下,这是可行的。插件的视图加载在其他控件中,我仍然能够将关闭命令(以及稍后添加的其他命令)绑定到底层模型。

现在的问题是,每当我单击关闭按钮并从模块集合中删除 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 属性是一个可视控件,并且绑定更新正在尝试将其重新添加到可视树中。

我怎样才能解决这个问题或在没有错误消息的情况下达到预期的结果?

【问题讨论】:

  • 我不明白您为什么要使用 Prism 区域并绑定到 ItemsSource?为什么不直接说 regionManager.RegisterViewWithRegion("Workspace", typeof(MyPlugin)) 让你的模块贡献插件呢?这里有些奇怪。
  • 实际上这可能是问题所在...我将把它作为答案,看看会发生什么:)
  • 这是使用区域管理器缓冲您的事情之一(而不是绑定到视图集合)。您仍然在混合一些不必要的东西。如果我有时间的话,我今晚会试着为你写一个样本。

标签: c# silverlight mvvm prism


【解决方案1】:

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

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

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

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

}

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

祝你好运。

【讨论】:

  • 这就是我最初的方式,但我遇到了几个问题。 1. 我认为绑定到 ViewModel 是更好的做法 2. 我需要用一些 ui 元素(边框、调整大小的句柄和带有关闭按钮的窗口标题栏)围绕每个插件,这些元素可以将按钮绑定到viewmodel 命令以及绑定到命令以显示设置窗口的第二个按钮。我不知道该怎么做。
  • 啊...我可以帮你。尽可能简单地重做您的问题的代码并且没有绑定(这不是必需的和多余的),我将引导您完成自定义模板的其余部分。发布代码链接会更有帮助。
  • 请再次阅读该问题,因为我已根据解决问题的进展对其进行了更新。我部分解决了它,但这样做遇到了障碍。感谢您的帮助。
【解决方案2】:

我最终完成了以下工作:

我创建了一个大致如下所示的 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。我有一些清理工作要做,但我认为它工作得很好。

感谢您的所有帮助。

【讨论】:

    猜你喜欢
    • 2012-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-07
    • 2019-04-07
    • 1970-01-01
    • 2013-02-23
    相关资源
    最近更新 更多