【问题标题】:WPF - Best way to remove an item from the ItemsSourceWPF - 从 ItemsSource 中删除项目的最佳方法
【发布时间】:2010-07-17 13:06:18
【问题描述】:

我正在编写一个自定义的ItemsControl(一个选项卡式文档容器),其中每个项目(选项卡)都可以在用户关闭它时从 UI 中移除。但是,我不能直接从ItemsControl.Items 集合中删除它,因为这些项目可以是数据绑定的。所以我必须从ItemsSource 中删除它,它可以是任何东西(ICollectionDataTableDataSourceProvider...)。

在我的应用程序上下文中,我知道 ItemsSource 的实际类型是什么,但我希望该控件更通用,以便以后可以重用。

所以我正在寻找一种在不知道其类型的情况下从数据源中删除项目的方法。我可以使用反射,但感觉很脏......到目前为止,我想出的最佳解决方案是使用dynamic

    internal void CloseTab(TabDocumentContainerItem tabDocumentContainerItem)
    {
        // TODO prompt user for confirmation (CancelEventHandler ?)

        var item = ItemContainerGenerator.ItemFromContainer(tabDocumentContainerItem);

        // TODO find a better way...
        try
        {
            dynamic items = ItemsSource;
            dynamic it = item;
            items.Remove(it);
        }
        catch(RuntimeBinderException ex)
        {
            Trace.TraceError("Oops... " + ex.ToString());
        }
    }

但我对此并不满意,我相信一定有更好的方法。任何建议将不胜感激!

【问题讨论】:

    标签: wpf itemscontrol itemssource


    【解决方案1】:

    ItemsControl.Items 返回的 ItemCollection 不允许您直接调用 Remove,但它实现了 IEditableCollectionView 并允许您在该接口中调用 Remove 方法。

    这仅在绑定到ItemsSource 的集合视图实现IEditableCollectionView 自身时才有效。默认集合视图适用于大多数可变集合,但不适用于实现 ICollection 但不是 IList 的对象。

    IEditableCollectionView items = tabControl.Items; //Cast to interface
    if (items.CanRemove)
    {
        items.Remove(tabControl.SelectedItem);
    }
    

    【讨论】:

    • 有趣的答案,我不知道那个界面。但是我认为我会坚持我找到的解决方案(请参阅我的答案),因为在我的情况下它更合适。此外,如果 ItemsSource 是 IEnumerable,CanRemove 将返回 false,但 ViewModel 可能有权访问实际集合并能够删除项目。
    • 很棒的解决方案兄弟。你刚刚为我修复了一个坏错误:avalondock.codeplex.com/workitem/13168
    【解决方案2】:

    好的,我找到了解决办法……

    • 如果 ItemsSource 是数据绑定的,我要么引发事件(用于代码隐藏)或调用命令(用于 ViewModel)从 ItemsSource 集合中删除项目。

    • 如果它不是数据绑定的,我会引发一个事件来提示用户确认,并直接从Items删除容器

      public static readonly DependencyProperty CloseTabCommandProperty =
          DependencyProperty.Register(
              "CloseTabCommand",
              typeof(ICommand),
              typeof(TabDocumentContainer),
              new UIPropertyMetadata(null));
      
      public ICommand CloseTabCommand
      {
          get { return (ICommand)GetValue(CloseTabCommandProperty); }
          set { SetValue(CloseTabCommandProperty, value); }
      }
      
      public event EventHandler<RequestCloseTabEventArgs> RequestCloseTab;
      public event EventHandler<TabClosingEventArgs> TabClosing;
      
      internal void CloseTab(TabDocumentContainerItem tabDocumentContainerItem)
      {
          if (ItemsSource != null) // Databound
          {
              object item = ItemContainerGenerator.ItemFromContainer(tabDocumentContainerItem);
              if (item == null || item == DependencyProperty.UnsetValue)
              {
                  return;
              }
              if (RequestCloseTab != null)
              {
                  var args = new RequestCloseTabEventArgs(item);
                  RequestCloseTab(this, args);
              }
              else if (CloseTabCommand != null)
              {
                  if (CloseTabCommand.CanExecute(item))
                  {
                      CloseTabCommand.Execute(item);
                  }
              }
          }
          else // Not databound
          {
              if (TabClosing != null)
              {
                  var args = new TabClosingEventArgs(tabDocumentContainerItem);
                  TabClosing(this, args);
                  if (args.Cancel)
                      return;
              }
              Items.Remove(tabDocumentContainerItem);
          }
      }
      

    【讨论】:

      【解决方案3】:

      正如您所发现的,您的ItemsControl 没有被绑定项目的内在知识——这些类型是由您的控件的消费者提供的。而且你不能直接修改集合,因为它可能是数据绑定的。

      诀窍是确保每个项目都由您选择的自定义类(项目容器)包装。您的ItemsControl 可以在GetContainerForItemOverride 方法中提供。

      从那里,您可以在自定义项目容器上定义属性,然后在默认模板中绑定到这些属性。例如,您可以有一个名为State 的属性,它在DockedFloatingClosed 之间变化。您的模板将使用此属性来确定如何 - 以及是否 - 显示项目。

      因此,您实际上根本不会更改底层数据源。相反,您将更改底层数据项之上的特定于控件的层,该层为您提供实现控件所需的信息。

      【讨论】:

      • 肯特,感谢您的回答。我已经创建了一个自定义容器并覆盖了 GetContainerForItemOverride。但事实是我真的想从基础集合中删除该项目,我不想只是隐藏它。也许我应该通过处理事件(代码隐藏)或绑定命令(ViewModel)将“关闭”的实现委托给用户
      • @Thomas:没问题。您能解释一下为什么需要删除该项目吗?也许过滤的集合视图就足够了?或者,当用户关闭它时,您的项目应该执行一个命令,而删除取决于使用代码。在我看来,如果您自己进行删除,那么您创建通用控件的愿望将无法实现。
      • 控件显示打开的文档列表(在这种情况下为 SQL 工作表)。它绑定到 ViewModel 公开的工作表列表。当我单击选项卡(TabDocumentContainerItem 模板的一部分)上的关闭按钮时,容器会在其父级(TabDocumentContainer)上调用 CloseTab,这会从工作表集合中删除文档。其实我找到了解决方案,我会在几分钟后发布
      【解决方案4】:

      设计实践要求您真正了解您的ItemsSource 是什么,并且能够直接从那里删除它。然后绑定会自动更新视图。

      但是,如果您绝对打算删除某种通用功能,请将您的 ItemsSource 转换为 ICollectionICollection&lt;T&gt; 然后调用 Remove 听起来比使用更好/更可靠的方法.NET 的动态特性。

      【讨论】:

      • 实际上,好的设计会确保ItemsControl 完全不知道ItemsSource 中的类型。
      • 如果我可以完全控制 ItemsSource 属性,我会同意...但我没有。它是 WPF 的一部分,类型为 object,并且可以是任何支持枚举的东西。并且我的控件旨在接受任何东西,我不想将自己限制在特定类型
      • @Thomas:顺便说一句,我的第二段的解决方案有什么问题?
      • 没什么错,我不是投票给你的人......关于你的第二段,不幸的是我不能这样做,因为我不知道实际的项目类型,并且@ 987654329@ 不继承自 ICollection
      猜你喜欢
      • 1970-01-01
      • 2010-09-17
      • 2018-08-12
      • 2011-08-08
      • 1970-01-01
      • 2014-01-04
      • 2010-09-21
      • 2016-08-09
      • 2020-08-03
      相关资源
      最近更新 更多