![](/img/trans.png)
[英]How to make TabControl with Caliburn.Micro and diffrent 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
)时激活。 OnDeactivate
的bool
参数指示视图模型是刚刚停用还是完全“关闭”(例如,当您关闭对话窗口并离开时)。 如果完全关闭视图模型,它将在下次显示时重新初始化。
这意味着在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);
}
}
请注意Configure
的viewModels
注册。
只是为了补充 Patryk Ćwiek 的好答案!
如果您已经在使用 Caliburn Mirco 并且不想添加更多依赖项,您可以使用它们的SimpleContainer
代替 SimpleInjector 或 AutoFac。
只需像这样注册实现:
container.AllTypesOf<IMainScreenTabItem>(Assembly.GetExecutingAssembly());
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.