原作者: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>