0%

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

2011-02-12 18:29  tan_Cool  阅读(1988)  评论()  编辑  收藏

原作者:Raffaeu

上一篇文章我们看到,在WPF中创建定制的并重写默认样式的 TabControl 是相当复杂的,但对于扩展其行为还是很简单的。

作为资深开发者,我通常不喜欢:  1) 能够运行即可, 2) 推倒重来但仅仅写了两次相同的代码。那么这里要做代码重构,或做的更多!

所以,让我们继续以前的文章做要求的工作,制作一个模仿 VS IDE 的应用程序,就是他! 我们看到Prism已有了 RegionAdapter, 因此现在我们仅仅需要一个酷酷的控件. 好的,这里有一个开源项目 Avalon Dock , 他真的不错, 支持 WPF 4并且非常灵活. 因此,为了我们的目标,使用它吧。最终的效果应该是这样:

image

Avalon dock 异常强大,它允许你创建一个完整的支持窗口停靠的WPF应用程序. 但在使用它之前,你需要为它编写定制的 region adapter !

所以,有一个基本概念,在Prism中定制Region Adapter . 你通过下面的方法创建你自己的adapter class ,他继承自RegionAdapterBase :

1

2

3

4

5

6

7

8

9

10

11

public sealed class AvalonRegionAdapter :RegionAdapterBase<DocumentPane>

{

public AvalonRegionAdapter(IRegionBehaviorFactoryfactory)

: base``(factory)

{

}

}

Avalon dock 可以做得更多, 你可以创建多种类型的可停靠区域的view, 在当前文章我们仅仅使用它来创建 Tab region adapter 来装载 DocumentPane. 现在 RegionAdapterBase 需要实现三种方法:

1

2

3

4

protected override IRegion CreateRegion()

{

return new AllActiveRegion();

}

创建一个 region ,指定其使用的 adapter . 在本例中我们想要指定多种类型的View来添加到这个 adapter中, 比如ItemsContainer 或者 TabControl.

1

2

3

4

5

6

7

protected override void Adapt(IRegion region,DocumentPane regionTarget)

{

region.Views.CollectionChanged += delegate``(Objectsender, NotifyCollectionChangedEventArgs e)

{

OnViewsCollectionChanged(sender, e, region, regionTarget);

};

}

现在我们重写了adapt方法. 在本例中这个方法被调用了一次, 因为我只有一个 DocumentPane ,并且接下来我还要监听 Views.CollectionChanged事件. 通过这个我可以在任何时间知道当view从region中加入和移除.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

private void OnViewsCollectionChanged(``object sender,NotifyCollectionChangedEventArgs e, IRegion region,DocumentPane regionTarget)

{

if (e.Action == NotifyCollectionChangedAction.Add)

{

foreach (``object item in e.NewItems)

{

UIElement view = item as UIElement;

if (view != null``)

{

DockableContent newContentPane = newDockableContent();

newContentPane.Content = item;

newContentPane.Title = view.GetType().ToString();

newContentPane.Closed += (contentPaneSender, args) =>

{

};

regionTarget.Items.Add(newContentPane);

newContentPane.Activate();

}

}

}

else

{

if (e.Action ==NotifyCollectionChangedAction.Remove)

{

}

}

}

现在,这里有两个主要步骤. 首先我们想知道项目从集合中添加或移除。如果他添加了我们新创建的DockableContent并在view中设置内容。我们需要设置几个属性如标题和名称。 在我们的例子中,我仅仅添加了view,我们晚一会儿再看怎么实现我们的 TabModel property. 现在我们要做些什么呢?我们要监听标签的关闭事件。为什么呢?因为当Avalon关闭了dock document时我们需要释放相对应的view。

接着,当 regionAdapter 需要关闭view的时候,我们也要释放对应的标签控件.

现在我们稍稍回头,对我们的代码做些改变:

1

2

3

4

5

6

7

8

9

10

11

12

13

TabViewModel viewModel = ((UserControl)view).DataContext as TabViewModel;

if (view != null``)

{

DockableContent newContentPane = newDockableContent();

newContentPane.Content = item;

if (viewModel != null``)

{

Image img = new Image();

img.Source = new BitmapImage(newUri(``@"Resources/Alerts.png"``, UriKind.Relative));

newContentPane.Title = viewModel.TabModel.Title;

newContentPane.IsCloseable = viewModel.TabModel.CanClose;

newContentPane.Icon = img.Source;

}

这里有一点脏代码,但是我们尝试将View.DataContext对应到TabViewModel类型. 这是正确的类型, NET不会抛出异常 但会返回一个空的实例… 我们将用我们的信息来填充 tab controls .

最终结果是这样:

SNAGHTML388d63e

第一个标签不能被关闭,第二个可以, 我们也在上下文菜单中增加了一个特殊图标. 还有更多, 他仍然是一个WPF 控件,你可以应用你的自定义样式 就是这样!

Ops, 当然,这是新的MainView的代码:

1

2

3

4

5

6

7

<ad:DockingManager Grid.Column=``"1" Grid.Row=``"1" >

<ad:DocumentPanecal:RegionManager.RegionName=``"TabRegion"``Name=``"TabRegion"``>

<ad:DockableContent Title=``"Some title"``>

</ad:DockableContent>

</ad:DocumentPane>

</ad:DockingManager>