簡體   English   中英

在 Caliburn.Micro 中管理 ViewModel

[英]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 期望我們用來管理組件生命周期的。 為了完整起見,還有ActivateWithDeactivateWithConductWith擴展方法來鏈接Screen生命周期而無需Conductor的干預,但我傾向於避開這些。 雖然我可能會在一個奇異的單元測試場景中使用它們。

如文檔中所述,停用可以具有多種含義。 讓我們將TabControlConductor<IScreen>.Collection.OneActive結合使用作為示例。

  • 我們可以從一個選項卡切換到另一個選項卡。 我們不想關閉我們開始的選項卡,我們只想停用它。
  • 我們可以關閉當前選項卡,切換到(激活)上一個索引處的選項卡(Caliburn 的默認設置)。

由於這種靈活性,即多種可能性,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中是不必要的,並且會導致OnDeactivateTryClose之間的無限循環。 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM