2010-07-17 3 views
5

Я пишу пользовательский ItemsControl (контейнер с вкладками), где каждый элемент (вкладка) может удаляться из пользовательского интерфейса, когда пользователь его закрывает. Однако я не могу удалить его непосредственно из коллекции ItemsControl.Items, поскольку элементы могут быть привязаны к базе данных. Поэтому я должен удалить его с ItemsSource, который может быть любым (ICollection, DataTable, DataSourceProvider ...).WPF - Лучший способ удалить элемент из ItemsSource

В контексте моего приложения я знаю, что будет с фактическим типом 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()); 
     } 
    } 

Но я не очень счастлива с ним, я уверен, что должно быть лучший путь. Мы ценим любые предложения !

ответ

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

Практика проектирования диктует, что вы действительно должны знать, что ваш ItemsSource есть и уметь удалить его прямо там. Затем привязка автоматически обновляет представление.

Однако, если вы абсолютно умысел на каком-то общую функциональность для удаления, отбрасывая ваш ItemsSource к ICollection или ICollection<T>, а затем вызов Remove звучит как лучше/более надежный способ, чтобы пойти, чем использовать динамические возможности .NET.

+0

На самом деле, хороший дизайн будет гарантировать, что 'ItemsControl' не имеет знание типов в «ItemsSource» вообще. –

+0

Я бы согласился, если бы у меня был полный контроль над этим свойством ItemsSource ... но я этого не делаю. Это часть WPF, имеет тип 'object' и может быть любым, что поддерживает перечисление. И мой контроль призван принять что угодно, я не хочу ограничивать себя определенным типом –

+1

Down-vote why ?? – Noldorin

-1

Как вы нашли, ваш ItemsControl не обладает внутренним знанием связанных предметов - эти типы предоставляются потребителями вашего контроля. И вы не можете изменять коллекцию напрямую, потому что она может быть привязана к данным.

Хитрость заключается в том, чтобы каждый предмет был обернут специальным классом (контейнером предметов) по вашему выбору. Ваш ItemsControl вы можете указать это в методе GetContainerForItemOverride.

Оттуда вы можете определить свойства в контейнере пользовательского элемента, с которым вы затем привязываетесь в шаблоне по умолчанию. Например, у вас может быть свойство с именем State, которое изменяется от Docked, Floating и Closed. Ваш шаблон будет использовать это свойство, чтобы определить, как - и есть ли - показывать этот элемент.

Таким образом, вы фактически не будете изменять базовый источник данных. Вместо этого вы измените слой, специфичный для управления, поверх базовых элементов данных, которые предоставят вам информацию, необходимую для реализации вашего контроля.

+0

Кент, спасибо за ваш ответ. Я уже создал пользовательский контейнер и переопределял GetContainerForItemOverride. Но факт в том, что я действительно хочу удалить элемент из базовой коллекции, я не хочу просто скрыть его. Возможно, мне следует просто делегировать реализацию «закрыть» пользователю, обработав событие (позади кода) или связав команду (ViewModel) –

+0

@Thomas: никаких проблем. Можете ли вы объяснить, почему вам нужно удалить элемент? Возможно, фильтрация коллекции будет достаточной? Или, возможно, ваш элемент должен выполнить команду, когда пользователь ее закрывает, а удаление - до потребительского кода. Мне кажется, что ваше желание создать общий контроль не может работать, если вы сделаете удаление самостоятельно. –

+0

Элемент управления отображает список открытых документов (в этом случае SQL-таблицы). Он привязан к списку рабочих листов, выставленных ViewModel. Когда я нажимаю кнопку закрытия на вкладке (часть шаблона TabDocumentContainerItem), контейнер вызывает CloseTab у своего родителя (TabDocumentContainer), который удаляет документ из коллекции рабочего листа. На самом деле я нашел решение, я отправлю его через несколько минут –

9

ItemCollection, возвращенный ItemsControl.Items, не позволит вам напрямую звонить, но он реализует IEditableCollectionView и позволяет вам вызвать метод Remove в этом интерфейсе.

Это будет работать, только если вид коллекции связан с ItemsSource, реализует IEditableCollectionView. Просмотр коллекции по умолчанию будет использоваться для большинства изменчивых коллекций, хотя не для объектов, которые реализуют ICollection, но не IList.

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

Интересный ответ, я не знал об этом интерфейсе. Однако я думаю, что буду придерживаться решения, которое я нашел (см. Мой ответ), потому что в моем случае он более адекватен. Кроме того, если ItemSource является IEnumerable, CanRemove вернет false, но ViewModel может иметь доступ к фактической коллекции и иметь возможность удалить элемент. –

+0

Удивительное решение bro. Вы просто исправили ошибку для меня: http://avalondock.codeplex.com/workitem/13168 – basarat