WPF Caliburn.Micro和TabControl出现UserControls问题 [英] WPF Caliburn.Micro and TabControl with UserControls issue

查看:142
本文介绍了WPF Caliburn.Micro和TabControl出现UserControls问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我很确定这已经在某个地方得到了回答,但是我似乎终生找不到它.

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

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

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

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

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

我可以轻松地使用以下方式将tabcontrol设置为usercontrol 但是然后它不绑定到视图模型,而只绑定到视图.

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

因此,我在VM中使用了Conductor,并在ActivateItem中使用了.这就是开始变得奇怪/令人沮丧的地方.应用程序从选择的Tab0开始,但选择Tab2(最后一个标签)的内容.单击任何其他选项卡,为该选项卡加载正确的ViewModel.单击返回到Tab0,并在其中加载正确的内容.

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.

如何停止此操作?另外,如果切换选项卡不再重新初始化视图模型,清除已经输入的字段,我真的很喜欢.

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.

查看:

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

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

推荐答案

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

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

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

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.

然后,子视图模型:

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是一个虚拟属性,并且在没有密封的类的构造函数中调用虚拟方法是很大的禁忌.

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

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

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 IMainScreenTabItem and then:

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

MainView.xaml所在的位置:

<TabControl Name="Items"/>

它就可以了.如果您的子视图模型具有多个依赖项(例如数据库访问,记录器,验证机制等),这也是非常方便的解决方案,现在您可以让IoC进行所有繁重的工作,而无需通过实例化它们手.

And it just works. It's also very nice 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.

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

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 MainViewModel constructor by either passing a custom IComparer<IMainScreenTabItem> or adding some property you can OrderBy or select to the IMainScreenTabItem interface. The default selected item will be the first one in the Items list.

其他选择是使MainViewModel带有三个参数:

Other option is to make the MainViewModel take three parameters:

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个子视图模型(并且您可以轻松获得更多)时,它很快就会变得混乱起来.

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

关于清理"部分.由IoC创建的视图模型会转换为常规生命周期:最多初始化一次(OnInitialize),然后在每次离开OnDeactivate(bool)导航时将其停用,并在导航至(). OnDeactivate中的bool参数指示视图模型是刚刚停用还是完全关闭"(例如,当您关闭对话框窗口并导航时).如果您完全关闭视图模型,则下次显示时将重新初始化它.

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 bool parameter in OnDeactivate indicates 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.

这意味着在OnActivate调用之间将保留所有绑定数据,并且您必须在OnDeactivate中显式清除它.而且,如果您对子视图模型保持强大的引用,那么即使调用OnDeactivate(true),数据也仍会在下一次初始化时存在-这是因为IoC注入的视图模型是一次创建的(除非您以Func<YourViewModel>的形式注入工厂函数),然后根据需要进行初始化/激活/停用.

That means that any bound data will be retained between OnActivate calls 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.

关于引导程序,我不太确定您使用的是哪种IoC容器.我的示例使用了 SimpleInjector ,但是您可以通过例如Autofac:

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:

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

这篇关于WPF Caliburn.Micro和TabControl出现UserControls问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆