[英]Managing ViewModels in Caliburn.Micro
我正在使用 Caliburn.Micro 框架將我正在開發的應用程序更改為 MVVM 模式。
當我習慣了這一點時,起初,我使用IConductor
接口進行導航,方法是在 MainViewModel 繼承Conductor<object>
,然后使用 ActivateItem 方法導航屏幕。
我沒有使用容器,而是每次都實例化一個新的 ViewModel。
例如,要導航到 FirstViewModel,我使用的是ActivateItem(new FirstViewModel());
ViewModelels 對資源很輕,所以這個實現並不明顯。 但是,我發現 ViewModel 實例沒有被釋放,我已經開始使用定時器來檢查實例是否仍在運行,在后台堆積。
從那以后,我嘗試了各種實現來控制 ViewModel 的管理方式。 我想要的是能夠決定我是引用一個已經實例化的 ViewModel 還是實例化一個新的。 另外,我想決定是處置 ViewModel 還是讓它繼續運行以便稍后重新連接。
所以,閱讀文檔,我在 BootStrapperBase 中實現了一個 SimpleContainer
public class Bootstrapper : BootstrapperBase
{
private SimpleContainer _container = new SimpleContainer();
public Bootstrapper()
{
Initialize();
}
protected override void Configure()
{
_container.Instance(_container);
_container
.Singleton<IWindowManager, WindowManager>()
.Singleton<IEventAggregator, EventAggregator>();
GetType().Assembly.GetTypes()
.Where(type => type.IsClass)
.Where(type => type.Name.EndsWith("ViewModel"))
.ToList()
.ForEach(viewModelType => _container.RegisterPerRequest(viewModelType, viewModelType.ToString(), viewModelType));
}
protected override object GetInstance(Type service, string key)
{
var instance = _container.GetInstance(service, key);
if (instance != null)
return instance;
throw new InvalidOperationException("Could not locate any instances.");
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<ShellViewModel>();
}
}
我認為IoC.Get<FirstViewModel>()
將實例化一個新的 ViewModel 或重用一個打開的 ViewModel,如果它已經實例化的話。 但是,它每次都實例化一個新的 ViewModel。
此外,我無法弄清楚在激活另一個 ViewModel 時如何處理 ViewModel。 例如,我在 FirstViewModel 上放置了一個 OnDeactivate,它在切換到另一個 ViewModel 時觸發,但我不知道應該放什么代碼來處理該實例。 我已經嘗試過這個安裝程序,實現了 IDisposable 接口,但我收到了 System.StackOverflowException。
protected override void OnDeactivate(bool close)
{
Dispose();
Console.WriteLine("deactivated");
}
public void Dispose()
{
base.TryClose();
}
Caliburn.Micro 中的 SimpleContainer 還不足以管理 ViewModel,還是我應該研究一種不同的方法?
我知道我似乎在問多個問題,但所有這些問題都是關於管理視圖模型的主要問題。
閱讀文檔時,我遇到了Lifecycle
概念,我認為這是可以管理我的問題的概念,但我沒有找到進一步的解釋。
Caliburn.Micro 上的文檔沒有給出很多示例,我發現如果沒有示例,我很難理解如何正確使用這個框架。
您對IConductor
的看法是正確的,這是 Caliburn 期望我們用來管理組件生命周期的。 為了完整起見,還有ActivateWith
、 DeactivateWith
和ConductWith
擴展方法來鏈接Screen
生命周期而無需Conductor
的干預,但我傾向於避開這些。 雖然我可能會在一個奇異的單元測試場景中使用它們。
如文檔中所述,停用可以具有多種含義。 讓我們將TabControl
與Conductor<IScreen>.Collection.OneActive
結合使用作為示例。
由於這種靈活性,即多種可能性,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
是 Caliburn 啟用優雅關閉的方式,例如“您確定要關閉項目嗎?” . CloseItemCoreAsync
接下來在源文件中實現,隨意看看。 在任一分支中使用的ScreenExtensions.TryDeactivateAsync
最終將轉發到屏幕本身上的DeactivateAsync
,它負責清理。
回到您的用例,當您指示從一個項目導航到下一個項目時,可以選擇切換回 memory 中的現有實例,我建議您使用Conductor<IScreen>.Collection.OneActive
。 然后,您可以查詢它的Items
集合以查明某個實例是否已經存在,以便激活它或創建一個新實例。
綜上所述,激活和去激活最好通過導體來完成。
如果您需要明確處置,您可以將您的樣品更改為以下樣品。
protected override void OnDeactivate(bool close)
{
if (close)
{
Dispose();
}
}
public void Dispose()
{
Console.WriteLine("disposed");
}
調用base.TryClose();
但是,在Dispose
中是不必要的,並且會導致OnDeactivate
和TryClose
之間的無限循環。 Dispose
模式僅用於清理非托管資源,例如文件句柄,參考MSDN 。
更新
使用 Conductor.Collection.OneActive 不會關閉 ViewModel,但是當我使用 ActivateItem(IoC.Get()); 時,會再次創建 ViewModel,因為我看到它如何再次運行構造函數。 我錯過了一些東西。
我個人是 成功坑的堅定支持者,當一個設計良好的框架(例如 Caliburn)公開 static 服務定位器時,我總是覺得有些失望。 當我們陷入困境時,我們很容易被誘惑到黑暗的一面。
如前所述:
然后,您可以查詢它的
Items
集合以查明某個實例是否已經存在,以便激活它或創建一個新實例。
要確定某個實例是否已經存在,我們需要一種方法來識別它。 它可以基於類型,但為了簡單起見,讓我們使用int Id
屬性。 假設Items
集合中的所有(或部分)視圖模型都裝飾有IHasEntity
接口(它公開了Id
屬性),我們正在尋找Id == 3
。
您需要在導體的 scope 內做以下事情:
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
.Collection.OneActive 並且不再需要.OfType<IHasEntity>()
過濾器。
SimpleContainer 中的 RegisterSingleton 將完成這項工作......
因此,如果您想根據您的選擇進行實例化,您可以使用一個助手來檢查類型的構造函數及其默認參數:(一些反射知識)在您可以調整代碼之后..
但如果您覺得這太復雜,請參閱第一個Activator.Createinstance 。
public static class HelperConstructor
{
public static T MyCreateInstance<T>()
where T : class
{
return (T) MyCreateInstance(typeof (T));
}
public static object MyCreateInstance(Type type)
{
var ctor = type
.GetConstructors()
.FirstOrDefault(c => c.GetParameters().Length > 0);
return ctor != null
? ctor.Invoke
(ctor.GetParameters()
.Select(p =>
p.HasDefaultValue? p.DefaultValue :
p.ParameterType.IsValueType && Nullable.GetUnderlyingType(p.ParameterType) == null
? Activator.CreateInstance(p.ParameterType)
: null
).ToArray()
)
: Activator.CreateInstance(type);
}
}
你通過給出一個類型來使用這個助手:
var instanceviewModel = HelperConstructor.MyCreateInstance(classType);
如果需要,稍后 caliburn 會自動創建視圖實例...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.