带有UserControls的WPF Caliburn.Micro和TabControl问题

我很确定这已经在某处得到了解答,但我似乎无法在我的生活中找到它。

我正在尝试使用TabControl在UserControls之间切换(每个选项卡都不同,因此不使用Items)

这是细分:我有我的主视图和3个用户控件。 Mainview有一个选项卡控件 – 每个选项卡应显示不同的用户控件。

我可以很容易地将tabcontrol对象设置为usercontrol但是它没有绑定到viewmodel,只绑定了视图。

所以我在我的VM和ActivateItem中使用Conductor。 这是它开始变得奇怪/令人沮丧的地方。 应用程序从选中Tab0开始,但Tab2(最后一个选项卡)内容。 单击任何其他选项卡,为该选项卡加载正确的ViewModel。 单击返回Tab0,同时加载正确的内容。

我如何让它停下来? 另外,如果切换标签没有再次重新初始化视图模型,清除已经输入的字段,我真的很喜欢它。

无论如何,这是我的一些来源,我将在这里放弃它并在我打破鼠标之前处理其他事情。

视图:

                 

和ViewModel:

 class MainViewModel : Conductor { RemoteInfoViewModel remoteInfo = new RemoteInfoViewModel(); RemoteToolsViewModel remoteTools = new RemoteToolsViewModel(); CHRemoteViewModel chRemote = new CHRemoteViewModel(); public MainViewModel() { ActivateItem(remoteInfo); } public void LoadRemoteInfo() { ActivateItem(remoteInfo); } public void LoadRemoteTools() { ActivateItem(remoteTools); } public void LoadCHRemote() { ActivateItem(chRemote); } } 

我可以建议一条不同的路线吗?

这是我在主要细节场景中成功完成的事情。 假设您有一组子视图模型。 我将为所有这些项目准备一个标记界面,当然,如果有这样的方法跨越所有子视图模型,您可以添加您认为合适的属性/方法:

 public interface IMainScreenTabItem : IScreen { } 

您可以确定您希望所有子模型都是Screen (或者,如果是嵌套方案, Conductor )。 它使它们具有完整的初始化/激活/停用循环。

然后,子视图模型:

 public sealed class ChRemoteViewModel : Screen, IMainScreenTabItem { public ChRemoteViewModel() { DisplayName = "CH Remote"; } } public sealed class PcInfoViewModel : Screen, IMainScreenTabItem { public PcInfoViewModel() { DisplayName = "PC Info"; } } public sealed class RemoteToolsViewModel : Screen, IMainScreenTabItem { public RemoteToolsViewModel() { DisplayName = "Remote Tools"; } } 

DisplayName将显示为标题文本。 将这些类密封起来是一个很好的做法,因为DisplayName是一个虚拟属性,并且在一个未密封的类的构造函数中调用虚方法是一个很大的禁忌。

然后,您可以添加相应的视图并设置您选择注册的IoC容器 – 您必须将所有子视图模型注册为实现IMainScreenTabItem类,然后:

 public class MainViewModel : Conductor.Collection.OneActive { public MainViewModel(IEnumerable tabs) { Items.AddRange(tabs); } } 

MainView.xaml只是:

  

它只是有效。 如果您的子视图模型具有多个依赖关系(例如数据库访问,记录器,validation机制等),那么它也是非常好用且方便的解决方案,现在您可以让IoC完成所有繁重工作,而不是手动实例化它们。

但有一件事:选项卡将按照注入类的顺序放置。 如果要控制排序,可以通过传递自定义IComparer或添加一些属性OrderBy或选择IMainScreenTabItem接口,在MainViewModel构造函数中对它们进行排序。 默认选定的项目将是“ Items列表中的第一个。

其他选项是使MainViewModel采用三个参数:

 public MainViewModel(ChRemoteViewModel chRemoteViewModel, PcInfoViewModel pcInfo, RemoteToolsViewModel remoteTools) { // Add the view models above to the `Items` collection in any order you see fit } 

虽然当你拥有超过2-3个子视图模型(并且你可以轻松获得更多)时,它会很快变得混乱。

关于’清算’部分。 IoC创建的视图模型与常规生命周期相关:它们最多初始化一次( OnInitialize ),然后每次从OnDeactivate(bool)导航时停用,并在导航到( OnActivate )时激活。 OnDeactivatebool参数指示视图模型是刚刚停用还是完全“关闭”(例如,当您关闭对话窗口并离开时)。 如果完全关闭视图模型,它将在下次显示时重新初始化。

这意味着在OnActivate调用之间将保留任何绑定数据,您必须在OnDeactivate明确清除它。 更重要的是,如果您保持对子视图模型的强引用,那么即使在您调用OnDeactivate(true) ,数据仍将在下次初始化时出现 – 这是因为IoC注入视图模型创建一次 (除非您注入工厂)以Func的forms运行,然后根据需要初始化/激活/停用。


编辑

关于bootstrapper,我不太清楚你正在使用什么样的IoC容器。 我的示例使用SimpleInjector ,但您可以使用例如Autofac轻松完成相同的操作:

 public class AppBootstrapper : Bootstrapper { private Container container; ///  /// Override to configure the framework and setup your IoC container. ///  protected override void Configure() { container = new Container(); container.Register(); container.Register(); var viewModels = Assembly.GetExecutingAssembly() .DefinedTypes.Where(x => x.GetInterface(typeof(IMainScreenTabItem).Name) != null && !x.IsAbstract && x.IsClass); container.RegisterAll(typeof(IMainScreenTabItem), viewModels); container.Verify(); } ///  /// Override this to provide an IoC specific implementation. ///  /// The service to locate.The key to locate. ///  /// The located service. ///  protected override object GetInstance(Type service, string key) { if (service == null) { var typeName = Assembly.GetExecutingAssembly().DefinedTypes.Where(x => x.Name.Contains(key)).Select(x => x.AssemblyQualifiedName).Single(); service = Type.GetType(typeName); } return container.GetInstance(service); } protected override IEnumerable GetAllInstances(Type service) { return container.GetAllInstances(service); } protected override void BuildUp(object instance) { container.InjectProperties(instance); } } 

请注意ConfigureviewModels注册。