Я пишу пользовательское ItemsControl
(контейнер документа с вкладками), куда каждый объект (вкладка) может удалить себя из UI, когда пользователь закрывает его. Однако я не могу удалить его непосредственно из 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());
}
}
Но я не действительно доволен им, я уверен, что должен быть лучший путь. Любые предложения ценились бы!
Хорошо, я нашел решение ...
Если ItemsSource
привязан к данным, я либо инициирую событие (для использования с выделенным кодом), либо вызываю команда (для использования с ViewModel) для удаления элемента из коллекции ItemsSource
.
Если это не привязка к данным, я вызываю событие, чтобы запросить у пользователя подтверждение, и удаляю контейнер непосредственно из Items
public static readonly DependencyProperty CloseTabCommandProperty =
DependencyProperty.Register (
"CloseTabCommand",
typeof (ICommand),
typeof (TabDocumentContainer),
новый UIPropertyMetadata (null));
public ICommand CloseTabCommand
{
получить {возврат (ICommand) GetValue (CloseTabCommandProperty); }
установить {SetValue (CloseTabCommandProperty, значение); }
}
открытое событие EventHandler RequestCloseTab;
открытое событие EventHandler TabClosing;
внутренняя пустота CloseTab (TabDocumentContainerItem tabDocumentContainerItem)
{
if (ItemsSource! = null) // Привязка данных
{
объектный элемент = ItemContainerGenerator.ItemFromContainer (tabDocumentContainerItem);
if (item == null || item == DependencyProperty.UnsetValue)
{
возвращение;
}
если (RequestCloseTab! = ноль)
{
var args = новый RequestCloseTabEventArgs (элемент);
RequestCloseTab (это, аргументы);
}
иначе, если (CloseTabCommand! = null)
{
если (CloseTabCommand.CanExecute (элемент))
{
CloseTabCommand.Выполнить (элемент);
}
}
}
else // Не привязка к данным
{
если (TabClosing! = ноль)
{
var args = new TabClosingEventArgs (tabDocumentContainerItem);
TabClosing (это, аргументы);
если (args.Cancel)
возвращение;
}
Items.Remove (tabDocumentContainerItem);
}
}
ItemCollection
, возвращенный ItemsControl.Items
выиграл ' t позволяет вам вызывать Remove напрямую, но он реализует IEditableCollectionView
и позволяет вам вызывать метод Remove в этом интерфейсе.
Это будет работать, только если представление коллекции, привязанное к ItemsSource
, реализует сам IEditableCollectionView
. Представление коллекции по умолчанию будет для большинства изменяемых коллекций, но не для объектов, реализующих ICollection
, но не IList
.
IEditableCollectionView items = tabControl.Items; //Cast to interface
if (items.CanRemove)
{
items.Remove(tabControl.SelectedItem);
}
Как вы обнаружили, ваш ItemsControl
не обладает внутренними знаниями о связываемых элементах - эти типы предоставляются потребителями вашего элемента управления. И вы не можете изменять коллекцию напрямую, поскольку она может быть связана с данными.
Хитрость заключается в том, чтобы каждый элемент был обернут пользовательским классом (контейнером элементов) по вашему выбору. В вашем ItemsControl
вы можете обеспечить это в методе GetContainerForItemOverride
.
Отсюда вы можете определить свойства вашего пользовательского контейнера элементов, которые вы затем свяжете с вашим шаблоном по умолчанию. Например, у вас может быть свойство под названием Состояние
, которое изменяется между Докированным
, Плавающим
и Закрытым
. Ваш шаблон будет использовать это свойство, чтобы определить, как - и нужно ли - показывать элемент.
Таким образом, на самом деле вы не будете изменять базовый источник данных. Вместо этого вы измените специфический для элемента управления слой поверх базовых элементов данных, который даст вам информацию, необходимую для реализации вашего элемента управления.
Практика проектирования требует, чтобы вы действительно знали, что такое ваш ItemsSource
и иметь возможность удалить его оттуда напрямую. Привязка, конечно же, автоматически обновляет вид.
Однако, если вы абсолютно намерены использовать какую-то общую функциональность для удаления, приведите свой ItemsSource
к ICollection
или ICollection
, а затем вызовите Удаление
звучит как лучший / более надежный способ, чем использование динамических функций .NET.