Вы были правы, глядя на IConductor
, это то, что Caliburn ожидает от нас для управления жизненным циклом компонентов. Для полноты изложения существуют также методы расширения ActivateWith
, DeactivateWith
и ConductWith
, которые связывают жизненные циклы Screen
без вмешательства Conductor
, но я склонен избегать их. Хотя я мог бы использовать их в сценарии модульного тестирования exoti c.
Как упоминалось в документации, деактивация может иметь несколько значений. Давайте использовать TabControl
в качестве примера в сочетании с Conductor<IScreen>.Collection.OneActive
.
- Мы могли бы переключаться с одной вкладки на другую. Мы не хотим закрывать вкладку, с которой мы начали, мы просто хотим ее деактивировать.
- Мы могли бы закрыть текущую вкладку, переключившись на (активировав) ту, которая была в предыдущем индексе (по умолчанию Caliburn).
Благодаря этой гибкости, ie множеству возможностей, Caliburn не навязывает вам ни одно из этих действий. Конечно, это означает, что вы должны сделать соответствующие вызовы самостоятельно.
Первый случай прост, назначение нового ActiveItem
автоматически деактивирует предыдущий.
Второй случай требует от вас закройте вкладку явно. Это, однако, заставит Caliburn назначить новый ActiveItem
. Вы можете использовать стратегию по умолчанию, или реализовать свою собственную, или вы можете убедиться, что элемент больше не является активным после его закрытия. В этом случае Caliburn не нужно искать в другом месте.
Примечательные методы расширения в этом контексте определены в ScreenExtensions.cs .
Самый простой способ закрыть Элемент await conductor.TryCloseAsync(item)
с дополнительным CancellationToken
. Этот метод просто перенаправляет на conductor.DeactivateItemAsync(item, true, CancellationToken.None);
.
. В случае Conductor<IScreen>.Collection.OneActive
реализация дается далее.
/// <summary>
/// Deactivates the specified item.
/// </summary>
/// <param name="item">The item to close.</param>
/// <param name="close">Indicates whether or not to close the item after deactivating it.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public override async Task DeactivateItemAsync(T item, bool close, CancellationToken cancellationToken = default)
{
if (item == null)
return;
if (!close)
await ScreenExtensions.TryDeactivateAsync(item, false, cancellationToken);
else
{
var closeResult = await CloseStrategy.ExecuteAsync(new[] { item }, CancellationToken.None);
if (closeResult.CloseCanOccur)
await CloseItemCoreAsync(item, cancellationToken);
}
}
Это все довольно понятно ты знаешь где искать. Флаг close
- это разница между деактивацией и закрытием элемента. CloseStrategy
- это способ Калибурна включить постепенное отключение, например «Вы уверены, что хотите закрыть элемент?» . CloseItemCoreAsync
реализовано следующим в исходном файле, не стесняйтесь иметь look . ScreenExtensions.TryDeactivateAsync
, используемый в любой из ветвей, в конечном итоге будет перенаправлен на DeactivateAsync
на самом экране , который отвечает за очистку.
Вернемся к вашему варианту использования, как вы указали при навигации от одного элемента к другому, с возможностью возврата к существующему экземпляру в памяти, я советую вам использовать Conductor<IScreen>.Collection.OneActive
. Затем вы можете запросить его коллекцию Items
, чтобы выяснить, существует ли определенный экземпляр, чтобы активировать его или создать новый.
Подводя итог, лучше всего активировать и деактивировать через проводники.
Если вам нужна явная утилизация, вы можете изменить свой образец на приведенный ниже.
protected override void OnDeactivate(bool close)
{
if (close)
{
Dispose();
}
}
public void Dispose()
{
Console.WriteLine("disposed");
}
Однако звонить по номеру base.TryClose();
в Dispose
не нужно, и это приведет к бесконечному oop между OnDeactivate
и TryClose
. Шаблон Dispose
необходим только для очистки неуправляемых ресурсов, таких как дескрипторы файлов, ref MSDN .
Обновление
Использование Conductor.Collection.OneActive не закрывает ViewModel, но затем, когда я использую ActivateItem (Io C .Get ());, ViewModel создается снова, потому что я вижу, как он снова запускает конструктор. Я что-то упускаю.
Лично я являюсь сильным сторонником пропасти успеха , я всегда нахожу это несколько разочаровывающим, когда хорошо спроектированная структура, такая как Caliburn, выставляет Stati c Сервисный локатор. Когда мы застреваем, мы легко склоняемся к темной стороне.
Как уже упоминалось:
Затем вы можете запросить его коллекцию Items
, чтобы выяснить, существует ли уже определенный экземпляр. существует, чтобы активировать его или создать новый.
Чтобы узнать , если определенный экземпляр уже существует , нам нужен способ его идентификации. Он может быть основан на типе, но для простоты давайте используем свойство int Id
. Скажем, все (или некоторые) из моделей представлений в коллекции Items
украшены интерфейсом IHasEntity
(который предоставляет опору Id
), и мы ищем Id == 3
.
Все Вы должны сделать из контекста проводника что-то в следующих строках:
var match = Items.OfType<IHasEntity>().FirstOrDefault(vm => vm.Id == 3);
if (match != null) // Activate it
{
ActiveItem = match;
}
else // Create a new instance
{
var entity = await _repo.GetId(3);
ActiveItem = new MyViewModel(entity);
}
Заключительная мысль: если бы все ваши модели представлений реализовали общую абстракцию IHasEntity
, вы могли бы определить свой проводник как Conductor<IHasEntity>.Collection.OneActive
и .OfType<IHasEntity>()
фильтр больше не нужен.