[英]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 期望我们用来管理组件生命周期的。 为了完整起见,还有ActivateWith
、 DeactivateWith
和ConductWith
扩展方法来链接Screen
生命周期而无需Conductor
的干预,但我倾向于避开这些。 虽然我可能会在一个奇异的单元测试场景中使用它们。
如文档中所述,停用可以具有多种含义。 让我们将TabControl
与Conductor<IScreen>.Collection.OneActive
结合使用作为示例。
由于这种灵活性,即多种可能性,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
中是不必要的,并且会导致OnDeactivate
和TryClose
之间的无限循环。 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.