在Caliburn.Micro中管理ViewModel

时间:2020-05-01 10:38:50

标签: c# wpf mvvm caliburn.micro

我正在使用Caliburn.Micro Framework将正在开发的应用程序更改为MVVM模式。

我已经习惯了,起初,我使用IConductor接口进行导航,方法是在MainViewModel上继承Conductor<object>,然后使用ActivateItem方法导航屏幕。

我没有使用容器,而是每次都实例化一个新的ViewModel。

例如,要导航至FirstViewModel,我使用的是ActivateItem(new FirstViewModel());

ViewModelels的资源有限,因此该实现并不引人注目。但是,我发现ViewModel实例没有被处置,并且我开始使用Timers检查该实例是否仍在运行,并在后台堆积。

从那时起,我一直在尝试各种实现来控制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上放置了一个OnDeactivate,该OnDeactivate在切换到另一个ViewModel时触发,但是我不知道应该放置什么代码来处理该实例。 我已经尝试过此安装程序,实现了IDisposable接口,但收到了System.StackOverflowException。

protected override void OnDeactivate(bool close)
        {

            Dispose();
            Console.WriteLine("deactivated");
        }
public void Dispose()
        {
            base.TryClose();
        }

Caliburn的SimpleContainer还不够吗?Micro不足以管理ViewModel,还是应该研究其他方法?

我知道似乎我在问多个问题,但是所有这些问题都是关于管理视图模型的主要问题。

在阅读文档时,我遇到了Lifecycle概念,我认为这是可以解决我的问题的概念,但是我没有找到进一步的解释。

Caliburn.Micro上的文档没有提供很多示例,而且我发现很难理解如何在没有示例的情况下正确使用此框架。

2 个答案:

答案 0 :(得分:1)

您正确看待IConductor,这正是Caliburn希望我们用来管理组件生命周期的东西。为了完整起见,也有ActivateWithDeactivateWithConductWith扩展方法可以在没有Screen干预的情况下链接Conductor的生命周期,但是我倾向于转向从那些。虽然我可能会在异国情调的单元测试场景中使用它们。

如文档中所述,停用可以具有多种含义。让我们以TabControl为例,结合Conductor<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的情况下,接下来给出implementation

/// <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在源文件中接下来实现,请随时使用look。任一分支中使用的ScreenExtensions.TryDeactivateAsync最终将转发到screen本身上的DeactivateAsync,它负责清理。

回到您的用例,当您指示从一项导航到另一项时,可以选择切换回内存中的现有实例,我建议您使用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,因为我看到了它如何再次运行构造函数。我想念一些东西。

我个人是the pit of success的坚定支持者,当精心设计的框架(例如Caliburn)公开静态服务定位符时,我总是感到有些失望。当我们陷入困境时,我们很容易被吸引到黑暗的一面。

如上所述:

然后您可以查询其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>()过滤器。

答案 1 :(得分:0)

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);
  }
}

您通过输入Type来使用此帮助程序

var instanceviewModel = HelperConstructor.MyCreateInstance(classType);

以后,caliburn会根据需要自动创建视图的实例...

相关问题