WPF Caliburn.Micro 和 TabControl - 更改选项卡,不更改模型

WPF Caliburn.Micro and TabControl - changing tab, not changing model

我是 WPF&MVVM&Caliburn 的新手,请您多多包涵 :)

我在将 TabControl 与动态创建的模型绑定时遇到问题。 Tabcontrol 正在正确创建,但更改选项卡不会切换用于绑定的 viewmodel "view"(我使用的是 viewmodel 优先方法)

我根据这个问题做了我的解决方案:WPF Caliburn.Micro and TabControl with UserControls issue

这是我的模型定义:

public interface IMainScreenTabItem : IScreen
{
}

public class MainViewTestTabsViewModel : Conductor<IMainScreenTabItem>.Collection.OneActive
{
    public MainViewTestTabsViewModel(IEnumerable<IMainScreenTabItem> tabs)
    {
        Items.Add(new ViewTabModel("Foo1"));
        Items.Add(new ViewTabModel("Foo2"));
        Items.AddRange(tabs);
    }
}

public sealed class ViewTabModel : Screen, IMainScreenTabItem
{
    public ViewTabModel(string displayName)
    {
        DisplayName = displayName;
    }
}

这是视图 MainViewTestTabsView:

<UserControl  x:Class="TestWpfApp.Views.MainViewTestTabsView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:TestWpfApp.Views"
    xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
    xmlns:viewModels="clr-namespace:TestWpfApp.ViewModels"
    xmlns:cal="http://www.caliburnproject.org"
    mc:Ignorable="d" Width="500" Height="500">
<Grid>
    <TabControl Name="Items">
        <TabControl.ContentTemplate>
            <DataTemplate>
                <StackPanel>
                    <Label  cal:Bind.Model="{Binding}" x:Name="DisplayName" Height="200" Width="200" />
                </StackPanel>
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>
</Grid>

我想要实现的是让 TabControl 有很多选项卡。每个选项卡都有相同的 "view"(在 DataTemplate 中声明)但是要绑定此视图我想使用不同的 viewModels(具体来说 - 相同的模型 class [ViewTabModel ] 但具有不同的数据)

选项卡的大小以及数据应在运行时声明,数据应在 ViewTabModel 模型中。

在下面的示例中 - 我有两个选项卡,但更改它们不会更改标签(我一直都有:"Foo1" 标签,即使我单击 "Foo2" 选项卡)

我使用 caliburn.micro 作为框架 - 使用 autofac bootstrap(如果重要的话) 我使用 propertyChanged.Fody (https://github.com/Fody/PropertyChanged) 来省略视图模型中所有属性更改的内容。

我做错了什么?

===更新===

附加最小复制解决方案:

https://wetransfer.com/downloads/0b909bfd31a588dda99655f366eddad420170801192103/1d094a

请帮忙! :)

=== 更新 2 ===

我的问题有什么不清楚的地方吗?:)仍然没有评论,没有悬赏活动。

=== 更新 3 ===

我已经发布了完整的视图页面(xaml)和完整的模型代码(仅此而已)

我也在发帖 AppBoostraper.cs 和 AppWindowManager.cs(但我想这与这里无关)

AppBoostrapper.cs

using Autofac;
using TestWpfApp.ViewModels;

namespace TestWpfApp {
    using System;
    using System.Collections.Generic;
    using Caliburn.Micro;

    public class AppBootstrapper : CaliburnMetroAutofacBootstrapper<MainViewTestTabsViewModel>
    {
        protected override void ConfigureContainer(ContainerBuilder builder)
        {
            builder.RegisterType<AppWindowManager>().As<IWindowManager>().SingleInstance();
            var assembly = typeof(ShellViewModel).Assembly;
            builder.RegisterAssemblyTypes(assembly)
                .Where(item => item.Name.EndsWith("ViewModel") && item.IsAbstract == false)
                .AsSelf()
                .SingleInstance();
        }
    }
}

正在继承CaliburnMetroAutofacContainer (https://github.com/ziyasal/Caliburn.Metro)

AppWindowsManager.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Caliburn.Metro.Core;
using MahApps.Metro.Controls;

namespace TestWpfApp
{
    public class AppWindowManager : MetroWindowManager
    {
        public override MetroWindow CreateCustomWindow(object view, bool windowIsView)
        {
            if (windowIsView)
            {
                return view as ShellView;
            }

            return new ShellView
            {
                Content = view
            };
        }
    }
}

=== 更新 4 === Apprently,改变控制从:

cal:Bind.Model="{Binding}" x:Name="DisplayName"

至:

Content="{Binding DisplayName}"

完成了工作。虽然我不太清楚为什么?

现在我想做完全一样的事情。只有这一次我希望我的观点受到约束。所以 ViewModel 是完全一样的。但是这次:

MainViewTestTabsView

<UserControl  x:Class="TestWpfApp.Views.MainViewTestTabsView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:TestWpfApp.Views"
    xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
    xmlns:viewModels="clr-namespace:TestWpfApp.ViewModels"
    xmlns:cal="http://www.caliburnproject.org"
    mc:Ignorable="d" Width="500" Height="500">
<Grid>
    <TabControl Name="Items">
        <TabControl.ContentTemplate>
            <DataTemplate>
                <StackPanel>
                    <local:ViewTab cal:Bind.Model="{Binding}" />
                </StackPanel>
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>
</Grid>

ViewTab 视图是:

<UserControl  x:Class="TestWpfApp.Views.ViewTab"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:TestWpfApp.Views"
    xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
    xmlns:viewModels="clr-namespace:TestWpfApp.ViewModels"
    xmlns:cal="http://www.caliburnproject.org"
    mc:Ignorable="d" Width="300" Height="300">
<Grid>
    <StackPanel>
        <Label x:Name="DisplayName"></Label>
    </StackPanel>
</Grid>

===更新5 =>快乐决赛=== 我被告知我应该坚持 ViewModel 优先约定(正如我声明我正在使用的那样),我的尝试是先以某种方式查看。所以我将其更改为:

<ContentControl cal:View.Model="{Binding ActiveItem}" />

但是那时什么都没有呈现

如果我这样声明:

<ContentControl cal:View.Model="{Binding}" />

只有消息说:“找不到视图:[my_namspece].ViewTabModel 这很奇怪,让我开始思考。也许我不遵守惯例。这是真的......

我的模型被称为:

ViewTabModel

而应该是:

ViewTabViewModel

视图完全一样。应该叫:

ViewTabView.xaml

之后,这样的构造:

<ContentControl cal:View.Model="{Binding}" />

工作正常!!感谢 arcticwhite 和 grek40 引导我找到这个解决方案

好的... 我已经与 Caliburn.Micro 合作过,所以我可以说我有一些经验,不是专业人士,但我设法让它发挥作用。

你的MainViewTestTabsViewModel.cs:

 public interface IMainScreenTabItem : IScreen
    {
    }

    public class MainViewTestTabsViewModel : Conductor<IMainScreenTabItem>.Collection.OneActive
    {
        public MainViewTestTabsViewModel(IEnumerable<IMainScreenTabItem> tabs)
        {

            Items.Add(new ViewTabModel() {DisplayName = "Test"});
            Items.Add(new ViewTabModel() { DisplayName = "Test2" });
            Items.Add(new ViewTabModel() { DisplayName = "Test3" });
            Items.AddRange(tabs);
        }
    }

    public class ViewTabModel : Screen, IMainScreenTabItem
    {
        public ViewTabModel()
        {

        }
    }

还有你的MainViewTestTabsView.xaml

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:TestWpfApp.ViewModels"
    xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
   xmlns:viewModels="clr-namespace:TestWpfApp.Views"
    xmlns:cal="http://www.caliburnproject.org"
    mc:Ignorable="d" Width="500" Height="500">
    <Grid>
        <TabControl x:Name="Items" >
            <TabControl.ContentTemplate>
                <DataTemplate>
                    <StackPanel>
                        <Label cal:Bind.ModelWithoutContext="{Binding}" x:Name="DisplayName" Height="200" Width="200"/>
                    </StackPanel>
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>
    </Grid>
    </UserControl>

P.S。为什么我在构造函数中删除了您的 displayName 变量...因为您不需要它,它已经作为 属性.

Caliburn:Micro.Screen

编辑#2 约定将起作用,只需在 Label 中添加 cal:Bind.ModelWithoutContext="{Binding}" (编辑答案)。

现在我有一些时间来测试您的示例项目...正如我评论的那样,您应该选择正确的绑定类型...

来自 All About Actions,我猜周围会有其他文档来源,列出相同的基本信息:

  • Bind.Model – View-First - Set’s the Action.Target and DataContext properties to the specified instance. Applies conventions to the view. String values are used to resolve an instance from the IoC container. (Use on root nodes like Window/UserControl/Page.)
  • Bind.ModelWithoutContext - View-First - Set’s the Action.Target to the specified instance. Applies conventions to the view. (Use inside of DataTemplate.)
  • View.Model – ViewModel-First – Locates the view for the specified VM instance and injects it at the content site. Sets the VM to the Action.Target and the DataContext. Applies conventions to the view.

如前所述,我不是 Caliburn 专家,所以我不得不尝试一下...第二个选项对我来说最好 ("Use inside of DataTemplate"),所以这是工作结果:

来自

<Label  cal:Bind.Model="{Binding}" x:Name="DisplayName" Height="200" Width="200" />

替换为

<Label cal:Bind.ModelWithoutContext="{Binding}" x:Name="DisplayName" Height="200" Width="200" />

及其工作原理。

实际上,我建议改为在周围的堆栈面板中引入模型(数据模板根)

<StackPanel cal:Bind.ModelWithoutContext="{Binding}">
    <Label x:Name="DisplayName" Height="200" Width="200" />
</StackPanel>