繁体   English   中英

WPF Caliburn.Micro 和 TabControl 与 UserControls 问题

[英]WPF Caliburn.Micro and TabControl with UserControls issue

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

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

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

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

所以我在我的 VM 和 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>

和视图模型:

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

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

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

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

MainView.xaml只是:

<TabControl Name="Items"/>

它只是有效。 如果您的子视图模型具有多个依赖关系(例如数据库访问,记录器,验证机制等),那么它也是非常好的和方便的解决方案,现在您可以让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个儿童视图模型(并且你可以轻松获得更多)时,它会很快变得混乱。

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

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


编辑

关于bootstrapper,我不太清楚你正在使用什么样的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);
    }
}

请注意ConfigureviewModels注册。

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

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

只需像这样注册实现:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM