首页 文章

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

提问于
浏览
12

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

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

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

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

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

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

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

视图:

<TabControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row ="1">
        <TabItem Header="PC Information">
            <Grid>
                <ContentControl x:Name="LoadRemoteInfo" cal:View.Model="{Binding ActiveItem}"/>
            </Grid>
        </TabItem>
        <TabItem Header="Remote Tools">
            <Grid>
                <ContentControl x:Name="LoadRemoteTools" cal:View.Model="{Binding ActiveItem}"/>
            </Grid>
        </TabItem>
        <TabItem Header="CHRemote">
            <Grid>
                <ContentControl x:Name="LoadCHRemote" cal:View.Model="{Binding ActiveItem}"/>
            </Grid>
        </TabItem>

    </TabControl>

和ViewModel:

class MainViewModel : Conductor<object>
{
    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);
    }
}

1 回答

  • 27

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

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

    public interface IMainScreenTabItem : IScreen
    {
    }
    

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

    然后,子视图模型:

    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 将显示为 Headers 文本 . 这些类是密封的,这是一个很好的做法,因为 DisplayName 是一个虚拟属性,并且它没有被密封 .

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

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

    MainView.xaml 只是:

    <TabControl Name="Items"/>
    

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

    但有一件事:选项卡将按照注入类的顺序放置 . 如果您希望控制排序,可以通过传递自定义 IComparer<IMainScreenTabItem> 或添加一些属性 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个儿童视图模型(并且你可以轻松获得更多)时,它会很快变得混乱 .

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

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


    编辑

    关于bootstrapper,我'm not quite sure what kind of IoC container you'正在使用 . 我的示例使用SimpleInjector,但您可以使用例如Autofac:

    public class AppBootstrapper : Bootstrapper<MainViewModel>
    {
        private Container container;
    
        /// <summary>
        /// Override to configure the framework and setup your IoC container.
        /// </summary>
        protected override void Configure()
        {
            container = new Container();
            container.Register<IWindowManager, WindowManager>();
            container.Register<IEventAggregator, EventAggregator>();
            var viewModels =
                Assembly.GetExecutingAssembly()
                    .DefinedTypes.Where(x => x.GetInterface(typeof(IMainScreenTabItem).Name) != null && !x.IsAbstract && x.IsClass);
            container.RegisterAll(typeof(IMainScreenTabItem), viewModels);
            container.Verify();
        }
    
        /// <summary>
        /// Override this to provide an IoC specific implementation.
        /// </summary>
        /// <param name="service">The service to locate.</param><param name="key">The key to locate.</param>
        /// <returns>
        /// The located service.
        /// </returns>
        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<object> GetAllInstances(Type service)
        {
            return container.GetAllInstances(service);
        }
    
        protected override void BuildUp(object instance)
        {
            container.InjectProperties(instance);
        }
    }
    

    请注意 Configure 中的 viewModels 注册 .

相关问题