3

有没有人有关于如何为 AvalonDock 的 DocumentPane 和 DockingPane 创建区域适配器的示例代码?

4

4 回答 4

4

Markus Raufer在CodePlex的CompositeWpfContrib项目中添加了两个区域适配器,支持和.DocumentPaneDockingPane

于 2009-10-16T05:38:30.923 回答
3

我曾使用Raffaeu Bermuda片段来支持 Avalon 选项卡区域适配器,但发现有一些问题没有解决:

1-它不支持激活某个视图(又名 - 选项卡 - DockableContent),因此代码Region.Activate(object view)将不起作用。

2- 默认情况下,该地区的所有选项卡都处于活动状态。因此Region.ActiveViews默认情况下集合包含所有视图,这并不理想,因为有时我需要验证视图是否处于活动状态(您可以想象工具栏区域上的保存按钮仅在当前活动视图上执行 SaveCommand =在我们的例子中标签)

3-关闭的视图实际上并没有被关闭,只是被隐藏了。即使您HideOnClose = true在添加 newDockableContent 时设置了,它仍然不会从Region.Views集合中删除。这可能会导致内存泄漏问题。

4- 如果您之前在窗格中添加了 DockableContent,它们将不会同步并添加到 Region.Views 集合中。

所以这里是我现在使用的代码,它只是对 PRISM 源代码中的 Selector Adapter 和 Selector Sync Behavior 的一个小调整:

AvalonRegionAdapter 类:

public class AvalonRegionAdapter : RegionAdapterBase<DocumentPane>
{
    public AvalonRegionAdapter(IRegionBehaviorFactory factory) : base(factory) {}

    protected override void AttachBehaviors(IRegion region, DocumentPane regionTarget)
    {
        if (region == null) throw new System.ArgumentNullException("region");
        //Add the behavior that syncs the items source items with the rest of the items
        region.Behaviors.Add(AvalonDocumentSyncBehavior.BehaviorKey,
                             new AvalonDocumentSyncBehavior()
        {
            HostControl = regionTarget
        });
        base.AttachBehaviors(region, regionTarget);
    }

    protected override void Adapt(IRegion region, DocumentPane regionTarget){ }

    protected override IRegion CreateRegion()
    {
        return new Region();
    }
}

AvalonDocumentSyncBehavior 行为代码:

public class AvalonDocumentSyncBehavior : RegionBehavior, IHostAwareRegionBehavior
{
    /// <summary>
    /// Name that identifies the SelectorItemsSourceSyncBehavior behavior in a collection of RegionsBehaviors. 
    /// </summary>
    public static readonly string BehaviorKey = "AvalonDocumentSyncBehavior";
    private bool updatingActiveViewsInHostControlSelectionChanged;
    private Selector hostControl;

    /// <summary>
    /// Gets or sets the <see cref="DependencyObject"/> that the <see cref="IRegion"/> is attached to.
    /// </summary>
    /// <value>
    /// A <see cref="DependencyObject"/> that the <see cref="IRegion"/> is attached to.
    /// </value>
    /// <remarks>For this behavior, the host control must always be a <see cref="Selector"/> or an inherited class.</remarks>
    public DependencyObject HostControl
    {
        get
        {
            return this.hostControl;
        }

        set
        {
            this.hostControl = value as Selector;
        }
    }

    /// <summary>
    /// Starts to monitor the <see cref="IRegion"/> to keep it in synch with the items of the <see cref="HostControl"/>.
    /// </summary>
    protected override void OnAttach()
    {
        bool itemsSourceIsSet = this.hostControl.ItemsSource != null;
        if (itemsSourceIsSet)
        {
            //throw new InvalidOperationException(Resources.ItemsControlHasItemsSourceException);
        }

        this.SynchronizeItems();

        this.hostControl.SelectionChanged += this.HostControlSelectionChanged;
        this.Region.ActiveViews.CollectionChanged += this.ActiveViews_CollectionChanged;
        this.Region.Views.CollectionChanged += this.Views_CollectionChanged;
    }

    private void Views_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            int startIndex = e.NewStartingIndex;
            foreach (object newItem in e.NewItems)
            {
                UIElement view = newItem as UIElement;
                TabViewModel viewModel = ((UserControl)view).DataContext as TabViewModel;
                if (view != null)
                {
                    DockableContent newDockableContent = new DockableContent();
                    newDockableContent.Content = newItem;
                    //if associated view has metadata then apply it.
                    newDockableContent.Title = view.GetType().ToString();
                    if (viewModel != null)
                    {
                        //Image img = new Image();
                        //img.Source = new BitmapImage(new Uri(@"Resources/Alerts.png", UriKind.Relative));
                        newDockableContent.Title = viewModel.TabModel.Title;
                        newDockableContent.IsCloseable = viewModel.TabModel.CanClose;
                        //newContentPane.Icon = img.Source;
                    }

                    //When contentPane is closed remove from the associated region
                    newDockableContent.Closed += new EventHandler(newDockableContent_Closed);

                    newDockableContent.HideOnClose = false; 
                    this.hostControl.Items.Add(newDockableContent);
                    newDockableContent.Activate();
                }
            }
        }
        else if (e.Action == NotifyCollectionChangedAction.Remove)
        {
            foreach (object oldItem in e.OldItems)
            {
                this.hostControl.Items.Remove(oldItem);
            }
        }
    }

    void newDockableContent_Closed(object sender, EventArgs e)
    {
        var dockableContent = sender as DockableContent;
        if(dockableContent != null)
            if (this.Region.Views.Contains(dockableContent.Content))
            {
                this.Region.Remove(dockableContent.Content);
            }
    }

    private void SynchronizeItems()
    {
        List<object> existingItems = new List<object>();

        // Control must be empty before "Binding" to a region
        foreach (object childItem in this.hostControl.Items)
        {
            existingItems.Add(childItem);
        }

        foreach (object view in this.Region.Views)
        {
            this.hostControl.Items.Add(view);
        }

        foreach (object existingItem in existingItems)
        {
            this.Region.Add(existingItem);
        }
    }


    private void ActiveViews_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (this.updatingActiveViewsInHostControlSelectionChanged)
        {
            // If we are updating the ActiveViews collection in the HostControlSelectionChanged, that 
            // means the user has set the SelectedItem or SelectedItems himself and we don't need to do that here now
            return;
        }

        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            var selectedDockableContent = this.hostControl.SelectedItem as DockableContent;
            if (selectedDockableContent != null
                && selectedDockableContent.Content != null
                && selectedDockableContent.Content != e.NewItems[0]
                && this.Region.ActiveViews.Contains(selectedDockableContent.Content))
            {
                this.Region.Deactivate(selectedDockableContent.Content);
            }

            var _UIElement = e.NewItems[0] as FrameworkElement;
            this.hostControl.SelectedItem = _UIElement.Parent;
        }
        else if (e.Action == NotifyCollectionChangedAction.Remove &&
                 e.OldItems.Contains(this.hostControl.SelectedItem))
        {
            this.hostControl.SelectedItem = null;
        }
    }

    private void HostControlSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        try
        {
            // Record the fact that we are now updating active views in the HostControlSelectionChanged method. 
            // This is needed to prevent the ActiveViews_CollectionChanged() method from firing. 
            this.updatingActiveViewsInHostControlSelectionChanged = true;

            object source;
            source = e.OriginalSource;

            if (source == sender)
            {
                foreach (object item in e.RemovedItems)
                {
                    // check if the view is in both Views and ActiveViews collections (there may be out of sync)
                    var dockableContent = item as DockableContent;
                    if (this.Region.Views.Contains(dockableContent.Content) && this.Region.ActiveViews.Contains(dockableContent.Content))
                    {
                        this.Region.Deactivate(dockableContent.Content);
                    }
                }

                foreach (object item in e.AddedItems)
                {
                    var dockableContent = item as DockableContent;
                    if (this.Region.Views.Contains(dockableContent.Content) && 
                         !this.Region.ActiveViews.Contains(dockableContent.Content))
                    {
                        this.Region.Activate(dockableContent.Content);
                    }
                }
            }
        }
        finally
        {
            this.updatingActiveViewsInHostControlSelectionChanged = false;
        }
    }
}

用于配置适配器的引导程序代码

protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
    var mappings = base.ConfigureRegionAdapterMappings();
    mappings.RegisterMapping(typeof(AvalonDock.DocumentPane), 
                             this.Container.Resolve<AvalonRegionAdapter>());
    return mappings;
}

然后你需要来自Raffaeu Bermuda的 TabModel 和 TabViewModel

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));

}

以及作为基类的 TabViewModel:

public class TabViewModel : INotifyPropertyChanged
{
    private TabModel _tabModel;

    public TabModel TabModel
    {
        get { return this._tabModel; }
        set
        {
            this._tabModel = value;
            OnPropertyChanged("TabModel");
        }
    }
    public TabViewModel()
    {
        this.TabModel = new TabModel();
        this.TabModel.CanClose = true;
        this.TabModel.IsModified = false;
    }
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(string info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

如果您需要进一步的帮助,请告诉我,我会在不久的将来发布一个博客。

于 2011-05-12T21:07:02.097 回答
2

由于 Avalon DocumentPane 和 DockingPane 都基于 System.Windows.Controls.Primitives.Selector,您可以在 Prism 中使用默认的 SelectorRegionAdapter。

只需基于 DockableContent 进行控制

<ad:DockableContent x:Class="DesignerWPF.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             xmlns:ad="clr-namespace:AvalonDock;assembly=AvalonDock"
             d:DesignHeight="300" d:DesignWidth="300" Title="dans">
    <Grid>
        <TextBox Text="sdfdf"></TextBox>
    </Grid>
</ad:DockableContent>

在您的主 Shell.xmal 上设置可停靠窗格中的区域

       <ad:DockingManager x:Name="dockManager" Grid.Row="1" Margin="0,4,0,0">
            <ad:ResizingPanel Orientation="Horizontal">
                <ad:DockablePane cal:RegionManager.RegionName="LeftRegion">

                </ad:DockablePane>
                <ad:DocumentPane cal:RegionManager.RegionName="DocumentRegion">

                </ad:DocumentPane>
            </ad:ResizingPanel>
        </ad:DockingManager>

然后,当您为控件初始化演示者时,它将显示在 Dock 中。

public class UserTestControl : IModule
    {
        public UserTestControl(IUnityContainer container, IRegionManager regionManager)
        {
            Container = container;
            RegionManager = regionManager;
        }
        public void Initialize()
        {
            var addFundView = Container.Resolve<UserControl1>();
            RegionManager.Regions["LeftRegion"].Add(addFundView);
        }

        public IUnityContainer Container { get; private set; }
        public IRegionManager RegionManager { get; private set; }
    }
于 2010-11-16T02:53:25.933 回答
0

我的建议是查看 Prism 源中的 Microsoft.Practices.Composite.Presentation.Regions。具体来说,看一下 ItemsControlRegionAdapter 并将其用作模板。记得从 RegionAdapterBase<> 继承:

public class ItemsControlRegionAdapter : RegionAdapterBase<ItemsControl>

并覆盖引导程序中的 ConfigureRegionAdapterMappings()。这看起来像:

protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
    RegionAdapterMappings mappings =  base.ConfigureRegionAdapterMappings();
    mappings.RegisterMapping(typeof(Canvas), Container.Resolve<CanvasRegionAdapter>());
    return mappings;
}
于 2009-05-29T05:30:35.733 回答