(粗译) Prism and WPF - 定制 Tab region adapter - 02部分
2011-02-12 18:29 tan_Cool 阅读(2193) 评论(2) 编辑 收藏 举报原作者:Raffaeu
上一篇文章我们看到,在WPF中创建定制的并重写默认样式的 TabControl 是相当复杂的,但对于扩展其行为还是很简单的。
作为资深开发者,我通常不喜欢: 1) 能够运行即可, 2) 推倒重来但仅仅写了两次相同的代码。那么这里要做代码重构,或做的更多!
所以,让我们继续以前的文章做要求的工作,制作一个模仿 VS IDE 的应用程序,就是他! 我们看到Prism已有了 RegionAdapter, 因此现在我们仅仅需要一个酷酷的控件. 好的,这里有一个开源项目 Avalon Dock , 他真的不错, 支持 WPF 4并且非常灵活. 因此,为了我们的目标,使用它吧。最终的效果应该是这样:
Avalon dock 异常强大,它允许你创建一个完整的支持窗口停靠的WPF应用程序. 但在使用它之前,你需要为它编写定制的 region adapter !
所以,有一个基本概念,在Prism中定制Region Adapter . 你通过下面的方法创建你自己的adapter class ,他继承自RegionAdapterBase<T> :
public sealed class AvalonRegionAdapter :RegionAdapterBase<DocumentPane> { public AvalonRegionAdapter(IRegionBehaviorFactoryfactory) : base(factory) { } //... }
Avalon dock 可以做得更多, 你可以创建多种类型的可停靠区域的view, 在当前文章我们仅仅使用它来创建 Tab region adapter 来装载 DocumentPane. 现在 RegionAdapterBase 需要实现三种方法:
protected override IRegion CreateRegion() { return new AllActiveRegion(); }
创建一个 region ,指定其使用的 adapter . 在本例中我们想要指定多种类型的View来添加到这个 adapter中, 比如ItemsContainer 或者 TabControl.
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中加入和移除.
private void OnViewsCollectionChanged(object sender,NotifyCollectionChangedEventArgs e, IRegion region,DocumentPane regionTarget) { if (e.Action == NotifyCollectionChangedAction.Add) { //Add content panes for each associated view. foreach (object item in e.NewItems) { UIElement view = item as UIElement; if (view != null) { DockableContent newContentPane = newDockableContent(); newContentPane.Content = item; //if associated view has metadata then apply it. newContentPane.Title = view.GetType().ToString(); //When contentPane is closed remove the associated region 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的时候,我们也要释放对应的标签控件.
现在我们稍稍回头,对我们的代码做些改变:
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的代码:
<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>