(粗译) 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并且非常灵活. 因此,为了我们的目标,使用它吧。最终的效果应该是这样:
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 .
最终结果是这样:
第一个标签不能被关闭,第二个可以, 我们也在上下文菜单中增加了一个特殊图标. 还有更多, 他仍然是一个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>