【问题标题】:WPF Caliburn.Micro and TabControl with UserControls issueWPF Caliburn.Micro 和 TabControl 与 UserControls 问题
【发布时间】:2014-01-19 22:55:35
【问题描述】:

我很确定这已经在某个地方得到了回答,但我似乎一辈子都找不到它。

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

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

我可以轻松地将 tabcontrol contect 设置为 usercontrol 使用 但它并没有绑定到视图模型,只绑定到视图。

所以我在我的虚拟机中使用了 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);
    }
}

【问题讨论】:

    标签: c# wpf mvvm tabcontrol caliburn.micro


    【解决方案1】:

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

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

    public interface IMainScreenTabItem : IScreen
    {
    }
    

    您可以确定您希望所有子模型都是Screens(或者,在嵌套场景的情况下,Conductors)。它使它们具有完整的初始化/激活/停用周期。

    然后,子视图模型:

    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<IMainScreenTabItem>.Collection.OneActive
    {
        public MainViewModel(IEnumerable<IMainScreenTabItem> tabs)
        {
            Items.AddRange(tabs);
        }
    }
    

    MainView.xaml 只是:

    <TabControl Name="Items"/>
    

    而且它确实有效。如果您的子视图模型具有多个依赖项(例如数据库访问、记录器、验证机制等),这也是非常很好且方便的解决方案,现在您可以让 IoC 完成所有繁重的工作,而不是通过实例化它们手。

    这里有一点:选项卡的放置顺序与注入类的顺序相同。如果您想控制排序,您可以在MainViewModel 构造函数中通过传递自定义IComparer&lt;IMainScreenTabItem&gt; 或添加一些您可以OrderBy 或选择IMainScreenTabItem 接口的属性对它们进行排序。默认选中的项目将是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) 时停用并在导航到时激活 (@987654343 @)。 OnDeactivate 中的 bool 参数指示视图模型是刚刚停用还是完全“关闭”(例如,当您关闭对话框窗口并导航离开时)。如果您完全关闭视图模型,它将在下次显示时重新初始化。

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


    编辑

    关于引导程序,我不太确定您使用的是哪种 IoC 容器。我的示例使用SimpleInjector,但您也可以轻松地使用例如自动法:

    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 注册。

    【讨论】:

    • 非常好的文章,认真的。我现在要实现它,我会告诉你结果如何。
    • 好吧,我还在学习 caliburn / mvvm,所以我在这里不知所措 - 我需要在我的引导程序中做一些事情,只是不确定是什么。你能指出我正确的方向吗?我尝试关注caliburnmicro.codeplex.com/…,但没有运气。这让我觉得自己像个十足的菜鸟,哈哈。
    • @JustinMangum 添加了一个包含解决您问题的示例的编辑。如果您正在使用例如MEF,我真的无法为您提供太多帮助,因为它涉及非常特定于 IoC 的方法,而且我没有使用 MEF 的经验 :)
    • SimpleInjector 工作正常。我必须仔细阅读 - 我之前也没有使用过 MEF,而且似乎无法让它喜欢我。说真的,谢谢你的帮助。以前,该应用程序是功能性的,只是我不喜欢,我想学习如何正确地做到这一点。就像我说的,对 MVVM 来说还是新手(很长时间以来一直想学习它),所以这真的帮助了我,因为引导程序是一大步。
    • SimpleInjector 代码示例从 v3.0 起不再有效。以下是解决方法:c-sharpcorner.com/blogs/…
    【解决方案2】:

    只是为了补充 Patryk Ćwiek 的绝妙答案!

    如果您已经在使用 Caliburn Mirco 并且不想添加更多依赖项,您可以使用他们的 SimpleContainer 代替 SimpleInjector 或 AutoFac。

    只需像这样注册实现:

    container.AllTypesOf<IMainScreenTabItem>(Assembly.GetExecutingAssembly());
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-09-11
      • 1970-01-01
      • 2010-12-26
      • 1970-01-01
      • 2011-12-08
      • 2012-09-07
      • 1970-01-01
      相关资源
      最近更新 更多