繁体   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