首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >ViewModels在Caliburn.Micro中的管理

ViewModels在Caliburn.Micro中的管理
EN

Stack Overflow用户
提问于 2020-05-01 10:38:50
回答 2查看 1.1K关注 0票数 2

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

由于我已经习惯了这一点,首先,我使用IConductor接口进行导航,方法是在MainViewModel上继承Conductor<object>,然后使用ActivateItem方法导航屏幕。

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

例如,为了导航到FirstViewModel,我使用了ActivateItem(new FirstViewModel());

ViewModelels在资源上很轻,所以这种实现并不明显。但是,我发现ViewModel实例没有被释放,我已经开始使用计时器来检查实例是否仍在运行,并在后台起球。

从那时起,我就开始尝试各种实现来控制的管理方式。我想要的是能够决定是引用一个已经实例化的ViewModel还是实例化一个新的。此外,我还想决定是释放ViewModel还是继续运行它,以便以后重新连接到它。

因此,在阅读文档之后,我在BootStrapperBase中实现了一个BootStrapperBase

代码语言:javascript
运行
复制
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时释放它。例如,我已经在切换到另一个OnDeactivate时触发的FirstViewModel上放置了一个ViewModel,但是我不知道我应该在那里放置什么代码来处理这个实例。我已经尝试过这个设置,实现了IDisposable接口,但是我收到了一个System.StackOverflowException。

代码语言:javascript
运行
复制
protected override void OnDeactivate(bool close)
        {

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

来自Caliburn.Micro的Caliburn.Micro难道不足以管理ViewModels,还是应该研究一种不同的方法?

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

阅读我偶然发现的Lifecycle概念的文档,我认为这是管理我的问题的概念,但我没有找到进一步的解释。

关于Caliburn.Micro的文档并没有给出太多的例子,我发现很难理解如何在没有示例的情况下正确地使用这个框架。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-05-01 16:35:45

RegisterSingleton in SimpleContainer将完成这项工作..。

因此,如果您想根据您的选择实例化,您可以使用一个助手来检查类型的构造函数及其默认参数:(对反射的一些知识)。

但是,如果您发现这太复杂了,请参见第一个Activator.Createinstance

代码语言:javascript
运行
复制
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 =instanceviewModel

稍后,如果需要,卡利伯恩会自动创建视图实例.

票数 0
EN

Stack Overflow用户

发布于 2020-05-02 12:15:03

你看IConductor是对的,它是卡利伯恩希望我们用来管理组件生命周期的工具。为了完整起见,还有ActivateWithDeactivateWithConductWith扩展方法来链接Screen生命周期,而不需要Conductor的干预,但我倾向于避开这些方法。不过,我可能会在一个奇异的单元测试场景中使用它们。

正如文档中所提到的,失活可能有多种含义。让我们将TabControlConductor<IScreen>.Collection.OneActive结合起来,作为一个示例。

  • 我们可以从一个标签切换到另一个标签。我们不想关闭我们开始的选项卡,我们只是想关闭它。
  • 我们可以关闭当前选项卡,切换到(激活)在前一个索引(卡利伯恩的默认)。

由于这种灵活性(多种可能性),卡利伯恩不会强迫你采取任何一种行为。当然,这意味着你必须自己打适当的电话。

第一种情况很简单,分配一个新的ActiveItem会自动取消前一种情况。

第二种情况要求显式关闭选项卡。然而,这将触发卡利伯恩分配一个新的ActiveItem。您可以使用默认策略,也可以实现自己的策略,或者一旦关闭该项,就可以确保该项不再是活动项。在这种情况下,卡利伯恩不需要寻找其他地方。

在这个上下文中,值得注意的扩展方法是在ScreenExtensions.cs中定义的。

关闭项的最简单方法是带有可选await conductor.TryCloseAsync(item)CancellationToken。此方法仅转发给conductor.DeactivateItemAsync(item, true, CancellationToken.None);

Conductor<IScreen>.Collection.OneActive的情况下,接下来给出实现

代码语言:javascript
运行
复制
/// <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是下一个在源文件中实现的,可以随意使用。两个分支中使用的ScreenExtensions.TryDeactivateAsync最终都将转发到DeactivateAsync上的屏幕本身,后者负责清理。

回到用例,当您指示从一个项导航到另一个项时,可以选择切换回内存中的现有实例,我建议您使用Conductor<IScreen>.Collection.OneActive。然后,您可以查询它的Items集合,以确定某个实例是否已经存在,以便激活它或创建一个新实例。

总之,激活和失活最好是通过导体来完成。

如果您需要显式处理,可以将您的样本更改为下面的示例。

代码语言:javascript
运行
复制
protected override void OnDeactivate(bool close)
{
    if (close) 
    {
        Dispose();
    }
}

public void Dispose()
{
    Console.WriteLine("disposed");       
}

但是,在Dispose中调用Dispose是不必要的,并将导致OnDeactivateTryClose之间的无限循环。Dispose模式仅用于清理非托管资源,如文件句柄、引用MSDN

更新

使用Conductor.Collection.OneActive不是关闭ViewModel,而是使用ActivateItem(IoC.Get());再次创建ViewModel,因为我看到它是如何再次运行构造函数的。我漏掉了什么东西。

就我个人而言,我是成功之坑的坚定拥护者,当一个设计良好的框架(如卡利本)公开静态服务定位器时,我总是会感到有些失望。当我们陷入困境时,我们很容易被诱惑到阴暗面。

如前所述:

然后,您可以查询它的Items集合,以确定某个实例是否已经存在,以便激活它或创建一个新实例。

为了找出某个实例是否已经存在,我们需要一种方法来识别它。它可以基于类型,但为了简单起见,让我们使用int Id属性。假设Items集合中的所有(或部分)视图模型都使用了一个IHasEntity接口(它公开了Id道具),我们正在寻找Id == 3

在售票员的范围内,你所需要做的就是在以下几条线上做一些事情:

代码语言:javascript
运行
复制
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>()过滤器。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61540884

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档