我正在使用Caliburn.Micro框架将正在开发的应用程序更改为MVVM模式。
由于我已经习惯了这一点,首先,我使用IConductor
接口进行导航,方法是在MainViewModel上继承Conductor<object>
,然后使用ActivateItem方法导航屏幕。
我没有使用容器,而是每次实例化一个新的ViewModel。
例如,为了导航到FirstViewModel,我使用了ActivateItem(new FirstViewModel());
ViewModelels在资源上很轻,所以这种实现并不明显。但是,我发现ViewModel实例没有被释放,我已经开始使用计时器来检查实例是否仍在运行,并在后台起球。
从那时起,我就开始尝试各种实现来控制的管理方式。我想要的是能够决定是引用一个已经实例化的ViewModel还是实例化一个新的。此外,我还想决定是释放ViewModel还是继续运行它,以便以后重新连接到它。
因此,在阅读文档之后,我在BootStrapperBase中实现了一个BootStrapperBase
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。
protected override void OnDeactivate(bool close)
{
Dispose();
Console.WriteLine("deactivated");
}
public void Dispose()
{
base.TryClose();
}
来自Caliburn.Micro的Caliburn.Micro难道不足以管理ViewModels,还是应该研究一种不同的方法?
我知道,我似乎问了多个问题,但所有这些问题都是关于一个主要的问题,即管理视图模型。
阅读我偶然发现的Lifecycle
概念的文档,我认为这是管理我的问题的概念,但我没有找到进一步的解释。
关于Caliburn.Micro的文档并没有给出太多的例子,我发现很难理解如何在没有示例的情况下正确地使用这个框架。
发布于 2020-05-01 16:35:45
RegisterSingleton in SimpleContainer将完成这项工作..。
因此,如果您想根据您的选择实例化,您可以使用一个助手来检查类型的构造函数及其默认参数:(对反射的一些知识)。
但是,如果您发现这太复杂了,请参见第一个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 =instanceviewModel
稍后,如果需要,卡利伯恩会自动创建视图实例.
发布于 2020-05-02 12:15:03
你看IConductor
是对的,它是卡利伯恩希望我们用来管理组件生命周期的工具。为了完整起见,还有ActivateWith
、DeactivateWith
和ConductWith
扩展方法来链接Screen
生命周期,而不需要Conductor
的干预,但我倾向于避开这些方法。不过,我可能会在一个奇异的单元测试场景中使用它们。
正如文档中所提到的,失活可能有多种含义。让我们将TabControl
与Conductor<IScreen>.Collection.OneActive
结合起来,作为一个示例。
由于这种灵活性(多种可能性),卡利伯恩不会强迫你采取任何一种行为。当然,这意味着你必须自己打适当的电话。
第一种情况很简单,分配一个新的ActiveItem
会自动取消前一种情况。
第二种情况要求显式关闭选项卡。然而,这将触发卡利伯恩分配一个新的ActiveItem
。您可以使用默认策略,也可以实现自己的策略,或者一旦关闭该项,就可以确保该项不再是活动项。在这种情况下,卡利伯恩不需要寻找其他地方。
在这个上下文中,值得注意的扩展方法是在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
是卡利伯恩允许优雅关闭的方式,例如“您确定要关闭该项目吗?”CloseItemCoreAsync
是下一个在源文件中实现的,可以随意使用看。两个分支中使用的ScreenExtensions.TryDeactivateAsync
最终都将转发到DeactivateAsync
上的屏幕本身,后者负责清理。
回到用例,当您指示从一个项导航到另一个项时,可以选择切换回内存中的现有实例,我建议您使用Conductor<IScreen>.Collection.OneActive
。然后,您可以查询它的Items
集合,以确定某个实例是否已经存在,以便激活它或创建一个新实例。
总之,激活和失活最好是通过导体来完成。
如果您需要显式处理,可以将您的样本更改为下面的示例。
protected override void OnDeactivate(bool close)
{
if (close)
{
Dispose();
}
}
public void Dispose()
{
Console.WriteLine("disposed");
}
但是,在Dispose
中调用Dispose
是不必要的,并将导致OnDeactivate
和TryClose
之间的无限循环。Dispose
模式仅用于清理非托管资源,如文件句柄、引用MSDN。
更新
使用Conductor.Collection.OneActive不是关闭ViewModel,而是使用ActivateItem(IoC.Get());再次创建ViewModel,因为我看到它是如何再次运行构造函数的。我漏掉了什么东西。
就我个人而言,我是成功之坑的坚定拥护者,当一个设计良好的框架(如卡利本)公开静态服务定位器时,我总是会感到有些失望。当我们陷入困境时,我们很容易被诱惑到阴暗面。
如前所述:
然后,您可以查询它的
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>()
过滤器。
https://stackoverflow.com/questions/61540884
复制相似问题