代码改变世界

(粗译) Prism and WPF - 定制 Tab region adapter - 01部分

2011-02-11 17:52  tan_Cool  阅读(2929)  评论(4编辑  收藏  举报

原作者:Raffaeu

有一件事让我很讨厌,当我开始使用Prism的时候,Prism的开发者没有提供一个好用的 tab region adapter。 好吧,items adapter 提供了完好的标签控制,但如果你开始使用,就会觉得它很糟糕。

我非常肯定,只要你想要发布你的第一个“Visual Studio风格的”Prism应用程序,你的主管会要求一个类似下面的选项卡标签:

image

这是在VS2010上的选项卡标签,正如你可以看到它可以让你:1)关闭当前活动标签,2)标识标签,有一个红色的变化点,3)使用不同的颜色,以便识别活动标签和不同类型的文件。

另一个很酷的标签系统VS 2010,就是一个把Design和XAML分开的标签系统,看这里:

image

我只是喜欢这种非方形标签的风格,它来自我对Chrome浏览器的记忆。

 

Prism region adapter 是如何工作的?

首先,使用Prism默认方式,我们如何在 tab region adapter 中创建一个新的选项卡标签。 你平时做的是将view添加到一个region这样...下面的代码演示了如何定义一个tab region adapter,然后如何添加新的tab。 非常简单。

首先,我们初始化 Shell 项目中的 bootstrapper。

The Shell bootstrapper

protected override System.Windows.DependencyObject CreateShell()
{
    var shell = Container.Resolve<MainWindow>();
    shell.Show();
    return shell;
}
 
protected override IModuleCatalog GetModuleCatalog()
{
    var catalog = new ModuleCatalog()
        .AddModule(typeof(TabModule));
    return catalog;
}

 

然后,准备 Shell XAML Window 中添加我们需要的 regions。

Shell XAML Window

<ItemsControl Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
              cal:RegionManager.RegionName="HeaderRegion" Name="HeaderRegion">
    
</ItemsControl>
<TabControl Grid.Row="1" Grid.Column="1"
            cal:RegionManager.RegionName="TabRegion" Name="TabRegion">
    
</TabControl>

然后,在该 module 中,分配到每个 view 在 shell 的特定 region 。

Module initialization

public sealed class TabModule : IModule
{
    private IRegionManager regionManager = null;
 
    public TabModule(IRegionManager regionManager)
    {
        this.regionManager = regionManager;
    }
 
    public void Initialize()
    {
        regionManager
            .AddToRegion("TabRegion", new FirstView())
            .AddToRegion("TabRegion", new SecondView());
    }
}


你会得到这个丑陋的结果...

image

因为我们没有指定任何样式,你可以看到选项卡不知道如何显示选项卡的标签。

现在,为了解决这个问题有两个简单的解决方案。 第一个是创建一个自定义 region adapter 和重写 region adapter 添加 region 的方法,并为 region adapter 提供所需信息。 这很不错,尤其当您正在使用第三方控件的时候。 第二种方法是有钱人的方法,可以使用第三方控件,但我不使用,原因很简单,作为一个博客,我没有钱... [:)](译者:这句不太会翻译)

一切始于依赖项属性。

XAML很酷,特别是因为它可以让你按照你想要的扩展它,那么为什么我们不开始考虑利用其强大的引擎来实现前面疯狂奇怪的解决方案?? 因此,让我们构建一个简单的 .dll 项目,来实现我们 TabItem 风格的“model”。 这种模式实现我们想要的特性:1)有一个标题,2)可查看或不能被关闭,3)view发生改变,必须保存更改。

Model for a tab item

public sealed class TabModel : DependencyObject
{
    public string Title
    {
        get { return (string)GetValue(TitleProperty); }
        set { SetValue(TitleProperty, value); }
    }
 
    // Using a DependencyProperty as the backing store for Title.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty TitleProperty =
        DependencyProperty.Register("Title", typeof(string), typeof(TabModel));
 
    public bool CanClose
    {
        get { return (bool)GetValue(CanCloseProperty); }
        set { SetValue(CanCloseProperty, value); }
    }
 
    // Using a DependencyProperty as the backing store for CanClose.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty CanCloseProperty =
        DependencyProperty.Register("CanClose", typeof(bool), typeof(TabModel));
 
    public bool IsModified
    {
        get { return (bool)GetValue(IsModifiedProperty); }
        set { SetValue(IsModifiedProperty, value); }
    }
 
    // Using a DependencyProperty as the backing store for IsModified.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty IsModifiedProperty =
        DependencyProperty.Register("IsModified", typeof(bool), typeof(TabModel));
 
}

 

现在我们只需要创建一个 ViewModel 描述 View 中的标签控件。 当然,你的 ViewModel 应该包含整个基础项目,但在这个示例中,我们并不真的需要它! 这是代码:

image

现在,这一切安排好了,我们需要两个以上的代码块。 首先是 ViewModel 为每个 View 的实现 TabItem, 下面的代码是FirstView:

Concrete ViewModel for TabItem

public sealed class FirstViewModel : TabViewModel
{
    public FirstViewModel()
    {
        this.TabModel = new TabModel
        {
            Title = "First View.",
            CanClose = true,
            IsModified = false
        };
    }
}

和非常容易的XAML:

FirstView vm binding

         xmlns:vm="clr-namespace:PrismCustomModule.ViewModels"
         mc:Ignorable="d"
         d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
    <vm:FirstViewModel />
</UserControl.DataContext>

自定义TabItem的使用样式。

第一步是很容易,只要在Shell项目中添加一个资源字典,然后在Shell Window引用。 创建一个自定义样式的标签,并指定容器如何将我们先前创建的标题文字绑定TabItem model:

Dictionary

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style TargetType="{x:Type TabItem}" x:Key="HeaderStyle">
        <Setter Property="Header"
                Value="{Binding RelativeSource={RelativeSource Self},
                    Path=Content.DataContext.TabModel.Title}" />
    </Style>
</ResourceDictionary>

到目前为止还很不错,现在我们只是在Shell中自定义TabContainer,这就是最终结果:

代码片段

<TabControl Grid.Row="1" Grid.Column="1"
            cal:RegionManager.RegionName="TabRegion" Name="TabRegion"
            ItemContainerStyle="{StaticResource HeaderStyle}">
    
</TabControl>

image

很不错,因为TabItem model 是一个属性,可以在ViewModel中引发更改,我们可以在用户打开选项卡时更改此属性的“on fly”。 我们也可以用它来激活一个特定的标签,增加了标签式的行为等等。 但是标签头仍然很烂,和其他很多烂东东! 我是说我真的不喜欢它。 所以在这里,我们可以用 Blend 4和 Design4。