WPF - лучший способ удалить элемент из ItemsSource - PullRequest
5 голосов
/ 17 июля 2010

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

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

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

Ответы [ 4 ]

9 голосов
/ 17 июля 2010

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

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

IEditableCollectionView items = tabControl.Items; //Cast to interface
if (items.CanRemove)
{
    items.Remove(tabControl.SelectedItem);
}
2 голосов
/ 17 июля 2010

ОК, я нашел решение ...

  • Если 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);
        }
    }
    
0 голосов
/ 17 июля 2010

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

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

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

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

0 голосов
/ 17 июля 2010

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

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

...