WPF Caliburn.Micro 和 TabControl 与 UserControls 问题

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20883868/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 10:22:10  来源:igfitidea点击:

WPF Caliburn.Micro and TabControl with UserControls issue

c#wpfmvvmtabcontrolcaliburn.micro

提问by Para

I'm pretty sure this has been answered somewhere, but I can't seem to find it for the life of me.

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

I'm trying to use a TabControl to switch between UserControls (each tab is different, so not using Items)

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

Here's the breakdown: I have my mainview, and 3 usercontrols. Mainview has a tab control - each tab should display a different user control.

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

I could easily just set the tabcontrol contect to the usercontrol using But then it isn't bound to the viewmodel, only the view.

我可以轻松地将 tabcontrol contect 设置为 usercontrol 使用 But then it is not bound to the viewmodel, only the view.

So I'm using Conductor in my VM, and ActivateItem. Here's where it starts to get weird / frustrating. Application starts with Tab0 selected, but Tab2 (last tab) content. Click on any other tab, loads the correct ViewModel for that tab. Click back to Tab0, loads the correct content there as well.

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

How do I get this to stop? Also, I'd really like it if switching tabs doesn't re-initialize the viewmodel again, clearing out fields that have already been entered.

我如何才能停止这种情况?另外,如果切换选项卡不会再次重新初始化视图模型,清除已经输入的字段,我真的很喜欢它。

Anyways, here's some of my source, I'm going to just drop this here and work on something else before I break my mouse.

无论如何,这是我的一些来源,我将把它放在这里并在我弄坏鼠标之前做其他事情。

View:

看法:

<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>

and the 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);
    }
}

回答by Patryk ?wiek

May I suggest a tad different route?

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

It's something that I have been successfully doing in master-details scenarios. Let's say you have a collection of child view models. I'll prepare a marker interface for all those items, of course you can add properties/methods you see fit if there are such methods that span all child view models:

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

public interface IMainScreenTabItem : IScreen
{
}

You can be quite sure that you want all your child models to be Screens (or, in case of nested scenarios, Conductors). It makes them have the full initialization/activation/deactivation cycle available.

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

Then, the child view models:

然后,子视图模型:

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";
    }
}

DisplayNamewill be displayed as a header text. It's a good practice to make those classes sealed, because DisplayNameis a virtual property, and it's a big no-no to call virtual methods in a constructor of a class that's not sealed.

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

Then, you can add corresponding views and set your IoC container of choice registrations - you have to register your all child view models as classes implementing the IMainScreenTabItemand then:

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

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

Where the MainView.xamlis just:

哪里MainView.xaml只是:

<TabControl Name="Items"/>

And it just works. It's also verynice and convenient solution if your child view models take multiple dependencies (e.g. database access, logger, validation mechanism etc), now you can have the IoC do all the heavy lifting instead of instantiating them by hand.

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

One thing here though: the tabs will be placed in the same order the classes are injected. If you want to have a control over the ordering, you can order them in MainViewModelconstructor by either passing a custom IComparer<IMainScreenTabItem>or adding some property you can OrderByor select to the IMainScreenTabIteminterface. The default selected item will be the first one in the Itemslist.

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

Other option is to make the MainViewModeltake three parameters:

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

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

Although when you have more than 2 - 3 child view models (and you can easily get more), it's going to get messy quick.

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

About the 'clearing' part. The view models created by IoC confrom to the regular life-cycle: they're initialized at most once (OnInitialize), then deactivated each time they are navigated away from OnDeactivate(bool)and activated when they're navigated to (OnActivate). The boolparameter in OnDeactivateindicates whether the view model is just deactivated or completely 'closed' (e.g. when you close the dialog window and navigate away). If you completely close the view model, it will be re-initialized next time it's shown.

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

That means that any bound data will be retained between OnActivatecalls and you'd have to explicitly clear it in OnDeactivate. What's more, if you keep the strong reference to your child view models, then even after you call OnDeactivate(true), the data will still be there on next initialization - that's because IoC injected view models are created once(unless you inject the factory function in a form of Func<YourViewModel>), and then initialized/activated/deactivated on demand.

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



EDIT

编辑

About the bootstrapper, I'm not quite sure what kind of IoC container you're using. My sample uses SimpleInjector, but you can do the same just as easily with e.g. Autofac:

关于引导程序,我不太确定您使用的是哪种 IoC 容器。我的示例使用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);
    }
}

Note the viewModelsregistration in Configure.

注意 中的viewModels注册Configure