mirror of
https://gitee.com/akwkevin/aistudio.-wpf.-diagram
synced 2026-04-03 23:56:37 +08:00
1539 lines
71 KiB
C#
1539 lines
71 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Specialized;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using System.Windows.Automation.Peers;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Controls.Primitives;
|
|
using System.Windows.Data;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using System.Windows.Threading;
|
|
using Dragablz.Core;
|
|
using Dragablz.Dockablz;
|
|
using Dragablz.Referenceless;
|
|
|
|
namespace Dragablz
|
|
{
|
|
//original code specific to keeping visual tree "alive" sourced from http://stackoverflow.com/questions/12432062/binding-to-itemssource-of-tabcontrol-in-wpf
|
|
|
|
/// <summary>
|
|
/// Extended tab control which supports tab repositioning, and drag and drop. Also
|
|
/// uses the common WPF technique for pesisting the visual tree across tabs.
|
|
/// </summary>
|
|
[TemplatePart(Name = HeaderItemsControlPartName, Type = typeof(DragablzItemsControl))]
|
|
[TemplatePart(Name = ItemsHolderPartName, Type = typeof(Panel))]
|
|
public class TabablzControl : TabControl
|
|
{
|
|
/// <summary>
|
|
/// Template part.
|
|
/// </summary>
|
|
public const string HeaderItemsControlPartName = "PART_HeaderItemsControl";
|
|
/// <summary>
|
|
/// Template part.
|
|
/// </summary>
|
|
public const string ItemsHolderPartName = "PART_ItemsHolder";
|
|
|
|
/// <summary>
|
|
/// Routed command which can be used to close a tab.
|
|
/// </summary>
|
|
public static RoutedCommand CloseItemCommand = new RoutedUICommand("Close", "Close", typeof(TabablzControl));
|
|
|
|
/// <summary>
|
|
/// Routed command which can be used to add a new tab. See <see cref="NewItemFactory"/>.
|
|
/// </summary>
|
|
public static RoutedCommand AddItemCommand = new RoutedUICommand("Add", "Add", typeof(TabablzControl));
|
|
|
|
private static readonly HashSet<TabablzControl> LoadedInstances = new HashSet<TabablzControl>();
|
|
private static readonly HashSet<TabablzControl> VisibleInstances = new HashSet<TabablzControl>();
|
|
|
|
private Panel _itemsHolder;
|
|
private TabHeaderDragStartInformation _tabHeaderDragStartInformation;
|
|
private WeakReference _previousSelection;
|
|
private DragablzItemsControl _dragablzItemsControl;
|
|
private IDisposable _templateSubscription;
|
|
private readonly SerialDisposable _windowSubscription = new SerialDisposable();
|
|
|
|
private InterTabTransfer _interTabTransfer;
|
|
|
|
static TabablzControl()
|
|
{
|
|
DefaultStyleKeyProperty.OverrideMetadata(typeof(TabablzControl), new FrameworkPropertyMetadata(typeof(TabablzControl)));
|
|
CommandManager.RegisterClassCommandBinding(typeof(FrameworkElement), new CommandBinding(CloseItemCommand, CloseItemClassHandler, CloseItemCanExecuteClassHandler));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Default constructor.
|
|
/// </summary>
|
|
public TabablzControl()
|
|
{
|
|
AddHandler(DragablzItem.DragStarted, new DragablzDragStartedEventHandler(ItemDragStarted), true);
|
|
AddHandler(DragablzItem.PreviewDragDelta, new DragablzDragDeltaEventHandler(PreviewItemDragDelta), true);
|
|
AddHandler(DragablzItem.DragDelta, new DragablzDragDeltaEventHandler(ItemDragDelta), true);
|
|
AddHandler(DragablzItem.DragCompleted, new DragablzDragCompletedEventHandler(ItemDragCompleted), true);
|
|
CommandBindings.Add(new CommandBinding(AddItemCommand, AddItemHandler));
|
|
|
|
Loaded += OnLoaded;
|
|
Unloaded += OnUnloaded;
|
|
IsVisibleChanged += OnIsVisibleChanged;
|
|
}
|
|
|
|
public static readonly DependencyProperty CustomHeaderItemStyleProperty = DependencyProperty.Register(
|
|
"CustomHeaderItemStyle", typeof (Style), typeof (TabablzControl), new PropertyMetadata(default(Style)));
|
|
|
|
/// <summary>
|
|
/// Helper method which returns all the currently loaded instances.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public static IEnumerable<TabablzControl> GetLoadedInstances()
|
|
{
|
|
return LoadedInstances.Union(VisibleInstances).Distinct().ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method to close all tabs where the item is the tab's content (helpful with MVVM scenarios)
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// In MVVM scenarios where you don't want to bind the routed command to your ViewModel,
|
|
/// with this helper method and embedding the TabablzControl in a UserControl, you can keep
|
|
/// the View-specific dependencies out of the ViewModel.
|
|
/// </remarks>
|
|
/// <param name="tabContentItem">An existing Tab item content (a ViewModel in MVVM scenarios) which is backing a tab control</param>
|
|
public static void CloseItem(object tabContentItem)
|
|
{
|
|
if (tabContentItem == null) return; //Do nothing.
|
|
|
|
//Find all loaded TabablzControl instances with tabs backed by this item and close them
|
|
foreach(var tabWithItemContent in
|
|
GetLoadedInstances().SelectMany(tc =>
|
|
tc._dragablzItemsControl.DragablzItems().Where(di => di.Content.Equals(tabContentItem)).Select(di => new { tc, di })))
|
|
{
|
|
TabablzControl.CloseItem(tabWithItemContent.di, tabWithItemContent.tc);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method to add an item next to an existing item.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Due to the organisable nature of the control, the order of items may not reflect the order in the source collection. This method
|
|
/// will add items to the source collection, managing their initial appearance on screen at the same time.
|
|
/// If you are using a <see cref="InterTabController.InterTabClient"/> this will be used to add the item into the source collection.
|
|
/// </remarks>
|
|
/// <param name="item">New item to add.</param>
|
|
/// <param name="nearItem">Existing object/tab item content which defines which tab control should be used to add the object.</param>
|
|
/// <param name="addLocationHint">Location, relative to the <paramref name="nearItem"/> object</param>
|
|
public static void AddItem(object item, object nearItem, AddLocationHint addLocationHint)
|
|
{
|
|
if (nearItem == null) throw new ArgumentNullException("nearItem");
|
|
|
|
var existingLocation = GetLoadedInstances().SelectMany(tabControl =>
|
|
(tabControl.ItemsSource ?? tabControl.Items).OfType<object>()
|
|
.Select(existingObject => new {tabControl, existingObject}))
|
|
.SingleOrDefault(a => nearItem.Equals(a.existingObject));
|
|
|
|
if (existingLocation == null)
|
|
throw new ArgumentException("Did not find precisely one instance of adjacentTo", "nearItem");
|
|
|
|
existingLocation.tabControl.AddToSource(item);
|
|
if (existingLocation.tabControl._dragablzItemsControl != null)
|
|
existingLocation.tabControl._dragablzItemsControl.MoveItem(new MoveItemRequest(item, nearItem, addLocationHint));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds and selects an item.
|
|
/// </summary>
|
|
/// <param name="item"></param>
|
|
public static void SelectItem(object item)
|
|
{
|
|
var existingLocation = GetLoadedInstances().SelectMany(tabControl =>
|
|
(tabControl.ItemsSource ?? tabControl.Items).OfType<object>()
|
|
.Select(existingObject => new {tabControl, existingObject}))
|
|
.FirstOrDefault(a => item.Equals(a.existingObject));
|
|
|
|
if (existingLocation == null) return;
|
|
|
|
existingLocation.tabControl.SelectedItem = item;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Style to apply to header items which are not their own item container (<see cref="TabItem"/>). Typically items bound via the <see cref="ItemsSource"/> will use this style.
|
|
/// </summary>
|
|
[Obsolete]
|
|
public Style CustomHeaderItemStyle
|
|
{
|
|
get { return (Style) GetValue(CustomHeaderItemStyleProperty); }
|
|
set { SetValue(CustomHeaderItemStyleProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty CustomHeaderItemTemplateProperty = DependencyProperty.Register(
|
|
"CustomHeaderItemTemplate", typeof (DataTemplate), typeof (TabablzControl), new PropertyMetadata(default(DataTemplate)));
|
|
|
|
[Obsolete("Prefer HeaderItemTemplate")]
|
|
public DataTemplate CustomHeaderItemTemplate
|
|
{
|
|
get { return (DataTemplate) GetValue(CustomHeaderItemTemplateProperty); }
|
|
set { SetValue(CustomHeaderItemTemplateProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty DefaultHeaderItemStyleProperty = DependencyProperty.Register(
|
|
"DefaultHeaderItemStyle", typeof (Style), typeof (TabablzControl), new PropertyMetadata(default(Style)));
|
|
|
|
[Obsolete]
|
|
public Style DefaultHeaderItemStyle
|
|
{
|
|
get { return (Style) GetValue(DefaultHeaderItemStyleProperty); }
|
|
set { SetValue(DefaultHeaderItemStyleProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty AdjacentHeaderItemOffsetProperty = DependencyProperty.Register(
|
|
"AdjacentHeaderItemOffset", typeof (double), typeof (TabablzControl), new PropertyMetadata(default(double), AdjacentHeaderItemOffsetPropertyChangedCallback));
|
|
|
|
private static void AdjacentHeaderItemOffsetPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
|
|
{
|
|
dependencyObject.SetValue(HeaderItemsOrganiserProperty, new HorizontalOrganiser((double)dependencyPropertyChangedEventArgs.NewValue));
|
|
}
|
|
|
|
public double AdjacentHeaderItemOffset
|
|
{
|
|
get { return (double) GetValue(AdjacentHeaderItemOffsetProperty); }
|
|
set { SetValue(AdjacentHeaderItemOffsetProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty HeaderItemsOrganiserProperty = DependencyProperty.Register(
|
|
"HeaderItemsOrganiser", typeof (IItemsOrganiser), typeof (TabablzControl), new PropertyMetadata(new HorizontalOrganiser()));
|
|
|
|
public IItemsOrganiser HeaderItemsOrganiser
|
|
{
|
|
get { return (IItemsOrganiser) GetValue(HeaderItemsOrganiserProperty); }
|
|
set { SetValue(HeaderItemsOrganiserProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty HeaderMemberPathProperty = DependencyProperty.Register(
|
|
"HeaderMemberPath", typeof (string), typeof (TabablzControl), new PropertyMetadata(default(string)));
|
|
|
|
public string HeaderMemberPath
|
|
{
|
|
get { return (string) GetValue(HeaderMemberPathProperty); }
|
|
set { SetValue(HeaderMemberPathProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty HeaderItemTemplateProperty = DependencyProperty.Register(
|
|
"HeaderItemTemplate", typeof (DataTemplate), typeof (TabablzControl), new PropertyMetadata(default(DataTemplate)));
|
|
|
|
public DataTemplate HeaderItemTemplate
|
|
{
|
|
get { return (DataTemplate) GetValue(HeaderItemTemplateProperty); }
|
|
set { SetValue(HeaderItemTemplateProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty HeaderPrefixContentProperty = DependencyProperty.Register(
|
|
"HeaderPrefixContent", typeof (object), typeof (TabablzControl), new PropertyMetadata(default(object)));
|
|
|
|
public object HeaderPrefixContent
|
|
{
|
|
get { return (object) GetValue(HeaderPrefixContentProperty); }
|
|
set { SetValue(HeaderPrefixContentProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty HeaderPrefixContentStringFormatProperty = DependencyProperty.Register(
|
|
"HeaderPrefixContentStringFormat", typeof (string), typeof (TabablzControl), new PropertyMetadata(default(string)));
|
|
|
|
public string HeaderPrefixContentStringFormat
|
|
{
|
|
get { return (string) GetValue(HeaderPrefixContentStringFormatProperty); }
|
|
set { SetValue(HeaderPrefixContentStringFormatProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty HeaderPrefixContentTemplateProperty = DependencyProperty.Register(
|
|
"HeaderPrefixContentTemplate", typeof (DataTemplate), typeof (TabablzControl), new PropertyMetadata(default(DataTemplate)));
|
|
|
|
public DataTemplate HeaderPrefixContentTemplate
|
|
{
|
|
get { return (DataTemplate) GetValue(HeaderPrefixContentTemplateProperty); }
|
|
set { SetValue(HeaderPrefixContentTemplateProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty HeaderPrefixContentTemplateSelectorProperty = DependencyProperty.Register(
|
|
"HeaderPrefixContentTemplateSelector", typeof (DataTemplateSelector), typeof (TabablzControl), new PropertyMetadata(default(DataTemplateSelector)));
|
|
|
|
public DataTemplateSelector HeaderPrefixContentTemplateSelector
|
|
{
|
|
get { return (DataTemplateSelector) GetValue(HeaderPrefixContentTemplateSelectorProperty); }
|
|
set { SetValue(HeaderPrefixContentTemplateSelectorProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty HeaderSuffixContentProperty = DependencyProperty.Register(
|
|
"HeaderSuffixContent", typeof(object), typeof(TabablzControl), new PropertyMetadata(default(object)));
|
|
|
|
public object HeaderSuffixContent
|
|
{
|
|
get { return (object)GetValue(HeaderSuffixContentProperty); }
|
|
set { SetValue(HeaderSuffixContentProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty HeaderSuffixContentStringFormatProperty = DependencyProperty.Register(
|
|
"HeaderSuffixContentStringFormat", typeof(string), typeof(TabablzControl), new PropertyMetadata(default(string)));
|
|
|
|
public string HeaderSuffixContentStringFormat
|
|
{
|
|
get { return (string)GetValue(HeaderSuffixContentStringFormatProperty); }
|
|
set { SetValue(HeaderSuffixContentStringFormatProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty HeaderSuffixContentTemplateProperty = DependencyProperty.Register(
|
|
"HeaderSuffixContentTemplate", typeof(DataTemplate), typeof(TabablzControl), new PropertyMetadata(default(DataTemplate)));
|
|
|
|
public DataTemplate HeaderSuffixContentTemplate
|
|
{
|
|
get { return (DataTemplate)GetValue(HeaderSuffixContentTemplateProperty); }
|
|
set { SetValue(HeaderSuffixContentTemplateProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty HeaderSuffixContentTemplateSelectorProperty = DependencyProperty.Register(
|
|
"HeaderSuffixContentTemplateSelector", typeof(DataTemplateSelector), typeof(TabablzControl), new PropertyMetadata(default(DataTemplateSelector)));
|
|
|
|
public DataTemplateSelector HeaderSuffixContentTemplateSelector
|
|
{
|
|
get { return (DataTemplateSelector)GetValue(HeaderSuffixContentTemplateSelectorProperty); }
|
|
set { SetValue(HeaderSuffixContentTemplateSelectorProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty ShowDefaultCloseButtonProperty = DependencyProperty.Register(
|
|
"ShowDefaultCloseButton", typeof (bool), typeof (TabablzControl), new PropertyMetadata(default(bool)));
|
|
|
|
/// <summary>
|
|
/// Indicates whether a default close button should be displayed. If manually templating the tab header content the close command
|
|
/// can be called by executing the <see cref="TabablzControl.CloseItemCommand"/> command (typically via a <see cref="Button"/>).
|
|
/// </summary>
|
|
public bool ShowDefaultCloseButton
|
|
{
|
|
get { return (bool) GetValue(ShowDefaultCloseButtonProperty); }
|
|
set { SetValue(ShowDefaultCloseButtonProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty ShowDefaultAddButtonProperty = DependencyProperty.Register(
|
|
"ShowDefaultAddButton", typeof (bool), typeof (TabablzControl), new PropertyMetadata(default(bool)));
|
|
|
|
/// <summary>
|
|
/// Indicates whether a default add button should be displayed. Alternately an add button
|
|
/// could be added in <see cref="HeaderPrefixContent"/> or <see cref="HeaderSuffixContent"/>, utilising
|
|
/// <see cref="AddItemCommand"/>.
|
|
/// </summary>
|
|
public bool ShowDefaultAddButton
|
|
{
|
|
get { return (bool) GetValue(ShowDefaultAddButtonProperty); }
|
|
set { SetValue(ShowDefaultAddButtonProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty IsHeaderPanelVisibleProperty = DependencyProperty.Register(
|
|
"IsHeaderPanelVisible", typeof(bool), typeof(TabablzControl), new PropertyMetadata(true));
|
|
|
|
/// <summary>
|
|
/// Indicates wither the heaeder panel is visible. Default is <c>true</c>.
|
|
/// </summary>
|
|
public bool IsHeaderPanelVisible
|
|
{
|
|
get { return (bool)GetValue(IsHeaderPanelVisibleProperty); }
|
|
set { SetValue(IsHeaderPanelVisibleProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty AddLocationHintProperty = DependencyProperty.Register(
|
|
"AddLocationHint", typeof (AddLocationHint), typeof (TabablzControl), new PropertyMetadata(AddLocationHint.Last));
|
|
|
|
/// <summary>
|
|
/// Gets or sets the location to add new tab items in the header.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The logical order of the header items might not add match the content of the source items,
|
|
/// so this property allows control of where new items should appear.
|
|
/// </remarks>
|
|
public AddLocationHint AddLocationHint
|
|
{
|
|
get { return (AddLocationHint) GetValue(AddLocationHintProperty); }
|
|
set { SetValue(AddLocationHintProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty FixedHeaderCountProperty = DependencyProperty.Register(
|
|
"FixedHeaderCount", typeof (int), typeof (TabablzControl), new PropertyMetadata(default(int)));
|
|
|
|
/// <summary>
|
|
/// Allows a the first adjacent tabs to be fixed (no dragging, and default close button will not show).
|
|
/// </summary>
|
|
public int FixedHeaderCount
|
|
{
|
|
get { return (int) GetValue(FixedHeaderCountProperty); }
|
|
set { SetValue(FixedHeaderCountProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty InterTabControllerProperty = DependencyProperty.Register(
|
|
"InterTabController", typeof (InterTabController), typeof (TabablzControl), new PropertyMetadata(null, InterTabControllerPropertyChangedCallback));
|
|
|
|
private static void InterTabControllerPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
|
|
{
|
|
var instance = (TabablzControl)dependencyObject;
|
|
if (dependencyPropertyChangedEventArgs.OldValue != null)
|
|
instance.RemoveLogicalChild(dependencyPropertyChangedEventArgs.OldValue);
|
|
if (dependencyPropertyChangedEventArgs.NewValue != null)
|
|
instance.AddLogicalChild(dependencyPropertyChangedEventArgs.NewValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// An <see cref="InterTabController"/> must be provided to enable tab tearing. Behaviour customisations can be applied
|
|
/// vie the controller.
|
|
/// </summary>
|
|
public InterTabController InterTabController
|
|
{
|
|
get { return (InterTabController) GetValue(InterTabControllerProperty); }
|
|
set { SetValue(InterTabControllerProperty, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allows a factory to be provided for generating new items. Typically used in conjunction with <see cref="AddItemCommand"/>.
|
|
/// </summary>
|
|
public static readonly DependencyProperty NewItemFactoryProperty = DependencyProperty.Register(
|
|
"NewItemFactory", typeof (Func<object>), typeof (TabablzControl), new PropertyMetadata(default(Func<object>)));
|
|
|
|
/// <summary>
|
|
/// Allows a factory to be provided for generating new items. Typically used in conjunction with <see cref="AddItemCommand"/>.
|
|
/// </summary>
|
|
public Func<object> NewItemFactory
|
|
{
|
|
get { return (Func<object>) GetValue(NewItemFactoryProperty); }
|
|
set { SetValue(NewItemFactoryProperty, value); }
|
|
}
|
|
|
|
private static readonly DependencyPropertyKey IsEmptyPropertyKey =
|
|
DependencyProperty.RegisterReadOnly(
|
|
"IsEmpty", typeof (bool), typeof (TabablzControl),
|
|
new PropertyMetadata(true, OnIsEmptyChanged));
|
|
|
|
/// <summary>
|
|
/// Indicates if there are no current tab items.
|
|
/// </summary>
|
|
public static readonly DependencyProperty IsEmptyProperty =
|
|
IsEmptyPropertyKey.DependencyProperty;
|
|
|
|
/// <summary>
|
|
/// Indicates if there are no current tab items.
|
|
/// </summary>
|
|
public bool IsEmpty
|
|
{
|
|
get { return (bool) GetValue(IsEmptyProperty); }
|
|
private set { SetValue(IsEmptyPropertyKey, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raised when <see cref="IsEmpty"/> changes.
|
|
/// </summary>
|
|
public static readonly RoutedEvent IsEmptyChangedEvent =
|
|
EventManager.RegisterRoutedEvent(
|
|
"IsEmptyChanged",
|
|
RoutingStrategy.Bubble,
|
|
typeof (RoutedPropertyChangedEventHandler<bool>),
|
|
typeof (TabablzControl));
|
|
|
|
/// <summary>
|
|
/// Event handler to list to <see cref="IsEmptyChangedEvent"/>.
|
|
/// </summary>
|
|
public event RoutedPropertyChangedEventHandler<bool> IsEmptyChanged
|
|
{
|
|
add { AddHandler(IsEmptyChangedEvent, value); }
|
|
remove { RemoveHandler(IsEmptyChangedEvent, value); }
|
|
}
|
|
|
|
private static void OnIsEmptyChanged(
|
|
DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
var instance = d as TabablzControl;
|
|
var args = new RoutedPropertyChangedEventArgs<bool>(
|
|
(bool) e.OldValue,
|
|
(bool) e.NewValue) {RoutedEvent = IsEmptyChangedEvent};
|
|
instance?.RaiseEvent(args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Optionally allows a close item hook to be bound in. If this propety is provided, the func must return true for the close to continue.
|
|
/// </summary>
|
|
public static readonly DependencyProperty ClosingItemCallbackProperty = DependencyProperty.Register(
|
|
"ClosingItemCallback", typeof(ItemActionCallback), typeof(TabablzControl), new PropertyMetadata(default(ItemActionCallback)));
|
|
|
|
/// <summary>
|
|
/// Optionally allows a close item hook to be bound in. If this propety is provided, the func must return true for the close to continue.
|
|
/// </summary>
|
|
public ItemActionCallback ClosingItemCallback
|
|
{
|
|
get { return (ItemActionCallback)GetValue(ClosingItemCallbackProperty); }
|
|
set { SetValue(ClosingItemCallbackProperty, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set to <c>true</c> to have tabs automatically be moved to another tab is a window is closed, so that they arent lost.
|
|
/// Can be useful for fixed/persistant tabs that may have been dragged into another Window. You can further control
|
|
/// this behaviour on a per tab item basis by providing <see cref="ConsolidatingOrphanedItemCallback" />.
|
|
/// </summary>
|
|
public static readonly DependencyProperty ConsolidateOrphanedItemsProperty = DependencyProperty.Register(
|
|
"ConsolidateOrphanedItems", typeof (bool), typeof (TabablzControl), new PropertyMetadata(default(bool)));
|
|
|
|
/// <summary>
|
|
/// Set to <c>true</c> to have tabs automatically be moved to another tab is a window is closed, so that they arent lost.
|
|
/// Can be useful for fixed/persistant tabs that may have been dragged into another Window. You can further control
|
|
/// this behaviour on a per tab item basis by providing <see cref="ConsolidatingOrphanedItemCallback" />.
|
|
/// </summary>
|
|
public bool ConsolidateOrphanedItems
|
|
{
|
|
get { return (bool) GetValue(ConsolidateOrphanedItemsProperty); }
|
|
set { SetValue(ConsolidateOrphanedItemsProperty, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Assuming <see cref="ConsolidateOrphanedItems"/> is set to <c>true</c>, consolidation of individual
|
|
/// tab items can be cancelled by providing this call back and cancelling the <see cref="ItemActionCallbackArgs{TOwner}"/>
|
|
/// instance.
|
|
/// </summary>
|
|
public static readonly DependencyProperty ConsolidatingOrphanedItemCallbackProperty = DependencyProperty.Register(
|
|
"ConsolidatingOrphanedItemCallback", typeof (ItemActionCallback), typeof (TabablzControl), new PropertyMetadata(default(ItemActionCallback)));
|
|
|
|
/// <summary>
|
|
/// Assuming <see cref="ConsolidateOrphanedItems"/> is set to <c>true</c>, consolidation of individual
|
|
/// tab items can be cancelled by providing this call back and cancelling the <see cref="ItemActionCallbackArgs{TOwner}"/>
|
|
/// instance.
|
|
/// </summary>
|
|
public ItemActionCallback ConsolidatingOrphanedItemCallback
|
|
{
|
|
get { return (ItemActionCallback) GetValue(ConsolidatingOrphanedItemCallbackProperty); }
|
|
set { SetValue(ConsolidatingOrphanedItemCallbackProperty, value); }
|
|
}
|
|
|
|
|
|
|
|
private static readonly DependencyPropertyKey IsDraggingWindowPropertyKey =
|
|
DependencyProperty.RegisterReadOnly(
|
|
"IsDraggingWindow", typeof (bool), typeof (TabablzControl),
|
|
new PropertyMetadata(default(bool), OnIsDraggingWindowChanged));
|
|
|
|
/// <summary>
|
|
/// Readonly dependency property which indicates whether the owning <see cref="Window"/>
|
|
/// is currently dragged
|
|
/// </summary>
|
|
public static readonly DependencyProperty IsDraggingWindowProperty =
|
|
IsDraggingWindowPropertyKey.DependencyProperty;
|
|
|
|
/// <summary>
|
|
/// Readonly dependency property which indicates whether the owning <see cref="Window"/>
|
|
/// is currently dragged
|
|
/// </summary>
|
|
public bool IsDraggingWindow
|
|
{
|
|
get { return (bool) GetValue(IsDraggingWindowProperty); }
|
|
private set { SetValue(IsDraggingWindowPropertyKey, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event indicating <see cref="IsDraggingWindow"/> has changed.
|
|
/// </summary>
|
|
public static readonly RoutedEvent IsDraggingWindowChangedEvent =
|
|
EventManager.RegisterRoutedEvent(
|
|
"IsDraggingWindowChanged",
|
|
RoutingStrategy.Bubble,
|
|
typeof (RoutedPropertyChangedEventHandler<bool>),
|
|
typeof (TabablzControl));
|
|
|
|
/// <summary>
|
|
/// Event indicating <see cref="IsDraggingWindow"/> has changed.
|
|
/// </summary>
|
|
public event RoutedPropertyChangedEventHandler<bool> IsDraggingWindowChanged
|
|
{
|
|
add { AddHandler(IsDraggingWindowChangedEvent, value); }
|
|
remove { RemoveHandler(IsDraggingWindowChangedEvent, value); }
|
|
}
|
|
|
|
private static void OnIsDraggingWindowChanged(
|
|
DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
var instance = (TabablzControl) d;
|
|
var args = new RoutedPropertyChangedEventArgs<bool>(
|
|
(bool) e.OldValue,
|
|
(bool) e.NewValue)
|
|
{
|
|
RoutedEvent = IsDraggingWindowChangedEvent
|
|
};
|
|
instance.RaiseEvent(args);
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Temporarily set by the framework if a users drag opration causes a Window to close (e.g if a tab is dragging into another tab).
|
|
/// </summary>
|
|
public static readonly DependencyProperty IsClosingAsPartOfDragOperationProperty = DependencyProperty.RegisterAttached(
|
|
"IsClosingAsPartOfDragOperation", typeof (bool), typeof (TabablzControl), new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.NotDataBindable));
|
|
|
|
internal static void SetIsClosingAsPartOfDragOperation(Window element, bool value)
|
|
{
|
|
element.SetValue(IsClosingAsPartOfDragOperationProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method which can tell you if a <see cref="Window"/> is being automatically closed due
|
|
/// to a user instigated drag operation (typically when a single tab is dropped into another window.
|
|
/// </summary>
|
|
/// <param name="element"></param>
|
|
/// <returns></returns>
|
|
public static bool GetIsClosingAsPartOfDragOperation(Window element)
|
|
{
|
|
return (bool) element.GetValue(IsClosingAsPartOfDragOperationProperty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provide a hint for how the header should size itself if there are no tabs left (and a Window is still open).
|
|
/// </summary>
|
|
public static readonly DependencyProperty EmptyHeaderSizingHintProperty = DependencyProperty.Register(
|
|
"EmptyHeaderSizingHint", typeof (EmptyHeaderSizingHint), typeof (TabablzControl), new PropertyMetadata(default(EmptyHeaderSizingHint)));
|
|
|
|
/// <summary>
|
|
/// Provide a hint for how the header should size itself if there are no tabs left (and a Window is still open).
|
|
/// </summary>
|
|
public EmptyHeaderSizingHint EmptyHeaderSizingHint
|
|
{
|
|
get { return (EmptyHeaderSizingHint) GetValue(EmptyHeaderSizingHintProperty); }
|
|
set { SetValue(EmptyHeaderSizingHintProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty IsWrappingTabItemProperty = DependencyProperty.RegisterAttached(
|
|
"IsWrappingTabItem", typeof (bool), typeof (TabablzControl), new PropertyMetadata(default(bool)));
|
|
|
|
internal static void SetIsWrappingTabItem(DependencyObject element, bool value)
|
|
{
|
|
element.SetValue(IsWrappingTabItemProperty, value);
|
|
}
|
|
|
|
public static bool GetIsWrappingTabItem(DependencyObject element)
|
|
{
|
|
return (bool) element.GetValue(IsWrappingTabItemProperty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds an item to the source collection. If the InterTabController.InterTabClient is set that instance will be deferred to.
|
|
/// Otherwise an attempt will be made to add to the <see cref="ItemsSource" /> property, and lastly <see cref="Items"/>.
|
|
/// </summary>
|
|
/// <param name="item"></param>
|
|
public void AddToSource(object item)
|
|
{
|
|
if (item == null) throw new ArgumentNullException("item");
|
|
|
|
var manualInterTabClient = InterTabController == null ? null : InterTabController.InterTabClient as IManualInterTabClient;
|
|
if (manualInterTabClient != null)
|
|
{
|
|
manualInterTabClient.Add(item);
|
|
}
|
|
else
|
|
{
|
|
CollectionTeaser collectionTeaser;
|
|
if (CollectionTeaser.TryCreate(ItemsSource, out collectionTeaser))
|
|
collectionTeaser.Add(item);
|
|
else
|
|
Items.Add(item);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes an item from the source collection. If the InterTabController.InterTabClient is set that instance will be deferred to.
|
|
/// Otherwise an attempt will be made to remove from the <see cref="ItemsSource" /> property, and lastly <see cref="Items"/>.
|
|
/// </summary>
|
|
/// <param name="item"></param>
|
|
public void RemoveFromSource(object item)
|
|
{
|
|
if (item == null) throw new ArgumentNullException("item");
|
|
|
|
var manualInterTabClient = InterTabController == null ? null : InterTabController.InterTabClient as IManualInterTabClient;
|
|
if (manualInterTabClient != null)
|
|
{
|
|
manualInterTabClient.Remove(item);
|
|
}
|
|
else
|
|
{
|
|
CollectionTeaser collectionTeaser;
|
|
if (CollectionTeaser.TryCreate(ItemsSource, out collectionTeaser))
|
|
collectionTeaser.Remove(item);
|
|
else
|
|
Items.Remove(item);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the header items, ordered according to their current visual position in the tab header.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public IEnumerable<DragablzItem> GetOrderedHeaders()
|
|
{
|
|
return _dragablzItemsControl.ItemsOrganiser.Sort(_dragablzItemsControl.DragablzItems());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when <see cref="M:System.Windows.FrameworkElement.ApplyTemplate"/> is called.
|
|
/// </summary>
|
|
public override void OnApplyTemplate()
|
|
{
|
|
_templateSubscription?.Dispose();
|
|
_templateSubscription = Disposable.Empty;
|
|
|
|
_dragablzItemsControl = GetTemplateChild(HeaderItemsControlPartName) as DragablzItemsControl;
|
|
if (_dragablzItemsControl != null)
|
|
{
|
|
_dragablzItemsControl.ItemContainerGenerator.StatusChanged += ItemContainerGeneratorOnStatusChanged;
|
|
_templateSubscription =
|
|
Disposable.Create(
|
|
() =>
|
|
_dragablzItemsControl.ItemContainerGenerator.StatusChanged -=
|
|
ItemContainerGeneratorOnStatusChanged);
|
|
|
|
_dragablzItemsControl.ContainerCustomisations = new ContainerCustomisations(null, PrepareChildContainerForItemOverride);
|
|
}
|
|
|
|
if (SelectedItem == null)
|
|
SetCurrentValue(SelectedItemProperty, Items.OfType<object>().FirstOrDefault());
|
|
|
|
_itemsHolder = GetTemplateChild(ItemsHolderPartName) as Panel;
|
|
UpdateSelectedItem();
|
|
MarkWrappedTabItems();
|
|
MarkInitialSelection();
|
|
|
|
base.OnApplyTemplate();
|
|
}
|
|
|
|
/// <summary>
|
|
/// update the visible child in the ItemsHolder
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
|
|
{
|
|
if (e.RemovedItems.Count > 0 && e.AddedItems.Count > 0)
|
|
_previousSelection = new WeakReference(e.RemovedItems[0]);
|
|
|
|
base.OnSelectionChanged(e);
|
|
UpdateSelectedItem();
|
|
|
|
if (_dragablzItemsControl == null) return;
|
|
|
|
Func<IList, IEnumerable<DragablzItem>> notTabItems =
|
|
l =>
|
|
l.Cast<object>()
|
|
.Where(o => !(o is TabItem))
|
|
.Select(o => _dragablzItemsControl.ItemContainerGenerator.ContainerFromItem(o))
|
|
.OfType<DragablzItem>();
|
|
foreach (var addedItem in notTabItems(e.AddedItems))
|
|
{
|
|
addedItem.IsSelected = true;
|
|
addedItem.BringIntoView();
|
|
}
|
|
foreach (var removedItem in notTabItems(e.RemovedItems))
|
|
{
|
|
removedItem.IsSelected = false;
|
|
}
|
|
|
|
foreach (var tabItem in e.AddedItems.OfType<TabItem>().Select(t => _dragablzItemsControl.ItemContainerGenerator.ContainerFromItem(t)).OfType<DragablzItem>())
|
|
{
|
|
tabItem.IsSelected = true;
|
|
tabItem.BringIntoView();
|
|
}
|
|
foreach (var tabItem in e.RemovedItems.OfType<TabItem>().Select(t => _dragablzItemsControl.ItemContainerGenerator.ContainerFromItem(t)).OfType<DragablzItem>())
|
|
{
|
|
tabItem.IsSelected = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// when the items change we remove any generated panel children and add any new ones as necessary
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
|
|
{
|
|
base.OnItemsChanged(e);
|
|
|
|
if (_itemsHolder == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch (e.Action)
|
|
{
|
|
case NotifyCollectionChangedAction.Reset:
|
|
_itemsHolder.Children.Clear();
|
|
|
|
if (Items.Count > 0)
|
|
{
|
|
SelectedItem = base.Items[0];
|
|
UpdateSelectedItem();
|
|
}
|
|
|
|
break;
|
|
|
|
case NotifyCollectionChangedAction.Add:
|
|
UpdateSelectedItem();
|
|
if (e.NewItems.Count == 1 && Items.Count > 1 && _dragablzItemsControl != null && _interTabTransfer == null)
|
|
_dragablzItemsControl.MoveItem(new MoveItemRequest(e.NewItems[0], SelectedItem, AddLocationHint));
|
|
|
|
break;
|
|
|
|
case NotifyCollectionChangedAction.Remove:
|
|
foreach (var item in e.OldItems)
|
|
{
|
|
var cp = FindChildContentPresenter(item);
|
|
if (cp != null)
|
|
_itemsHolder.Children.Remove(cp);
|
|
}
|
|
|
|
if (SelectedItem == null)
|
|
RestorePreviousSelection();
|
|
UpdateSelectedItem();
|
|
break;
|
|
|
|
case NotifyCollectionChangedAction.Replace:
|
|
throw new NotImplementedException("Replace not implemented yet");
|
|
}
|
|
|
|
IsEmpty = Items.Count == 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides class handling for the <see cref="E:System.Windows.ContentElement.KeyDown"/> routed event that occurs when the user presses a key.
|
|
/// </summary>
|
|
/// <param name="e">Provides data for <see cref="T:System.Windows.Input.KeyEventArgs"/>.</param>
|
|
protected override void OnKeyDown(KeyEventArgs e)
|
|
{
|
|
var sortedDragablzItems = _dragablzItemsControl.ItemsOrganiser.Sort(_dragablzItemsControl.DragablzItems()).ToList();
|
|
DragablzItem selectDragablzItem = null;
|
|
switch (e.Key)
|
|
{
|
|
case Key.Tab:
|
|
if (SelectedItem == null)
|
|
{
|
|
selectDragablzItem = sortedDragablzItems.FirstOrDefault();
|
|
break;
|
|
}
|
|
|
|
if ((e.KeyboardDevice.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
|
|
{
|
|
var selectedDragablzItem = (DragablzItem)_dragablzItemsControl.ItemContainerGenerator.ContainerFromItem(SelectedItem);
|
|
var selectedDragablzItemIndex = sortedDragablzItems.IndexOf(selectedDragablzItem);
|
|
var direction = ((e.KeyboardDevice.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
|
|
? -1 : 1;
|
|
var newIndex = selectedDragablzItemIndex + direction;
|
|
if (newIndex < 0) newIndex = sortedDragablzItems.Count - 1;
|
|
else if (newIndex == sortedDragablzItems.Count) newIndex = 0;
|
|
|
|
selectDragablzItem = sortedDragablzItems[newIndex];
|
|
}
|
|
break;
|
|
case Key.Home:
|
|
selectDragablzItem = sortedDragablzItems.FirstOrDefault();
|
|
break;
|
|
case Key.End:
|
|
selectDragablzItem = sortedDragablzItems.LastOrDefault();
|
|
break;
|
|
}
|
|
|
|
if (selectDragablzItem != null)
|
|
{
|
|
var item = _dragablzItemsControl.ItemContainerGenerator.ItemFromContainer(selectDragablzItem);
|
|
SetCurrentValue(SelectedItemProperty, item);
|
|
e.Handled = true;
|
|
}
|
|
|
|
if (!e.Handled)
|
|
base.OnKeyDown(e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides an appropriate automation peer implementation for this control
|
|
/// as part of the WPF automation infrastructure.
|
|
/// </summary>
|
|
/// <returns>The type-specific System.Windows.Automation.Peers.AutomationPeer implementation.</returns>
|
|
protected override AutomationPeer OnCreateAutomationPeer()
|
|
{
|
|
return new FrameworkElementAutomationPeer(this);
|
|
}
|
|
|
|
internal static TabablzControl GetOwnerOfHeaderItems(DragablzItemsControl itemsControl)
|
|
{
|
|
return LoadedInstances.FirstOrDefault(t => Equals(t._dragablzItemsControl, itemsControl));
|
|
}
|
|
|
|
private static void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
|
|
{
|
|
var tabablzControl = (TabablzControl) sender;
|
|
if (tabablzControl.IsVisible)
|
|
VisibleInstances.Add(tabablzControl);
|
|
else if (VisibleInstances.Contains(tabablzControl))
|
|
VisibleInstances.Remove(tabablzControl);
|
|
}
|
|
|
|
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
|
|
{
|
|
LoadedInstances.Add(this);
|
|
var window = Window.GetWindow(this);
|
|
if (window == null) return;
|
|
window.Closing += WindowOnClosing;
|
|
_windowSubscription.Disposable = Disposable.Create(() => window.Closing -= WindowOnClosing);
|
|
}
|
|
|
|
private void WindowOnClosing(object sender, CancelEventArgs cancelEventArgs)
|
|
{
|
|
_windowSubscription.Disposable = Disposable.Empty;
|
|
if (!ConsolidateOrphanedItems || InterTabController == null) return;
|
|
|
|
var window = (Window)sender;
|
|
|
|
var orphanedItems = _dragablzItemsControl.DragablzItems();
|
|
if (ConsolidatingOrphanedItemCallback != null)
|
|
{
|
|
orphanedItems =
|
|
orphanedItems.Where(
|
|
di =>
|
|
{
|
|
var args = new ItemActionCallbackArgs<TabablzControl>(window, this, di);
|
|
ConsolidatingOrphanedItemCallback(args);
|
|
return !args.IsCancelled;
|
|
}).ToList();
|
|
}
|
|
|
|
var target =
|
|
LoadedInstances.Except(this)
|
|
.FirstOrDefault(
|
|
other =>
|
|
other.InterTabController != null &&
|
|
other.InterTabController.Partition == InterTabController.Partition);
|
|
if (target == null) return;
|
|
|
|
foreach (var item in orphanedItems.Select(orphanedItem => _dragablzItemsControl.ItemContainerGenerator.ItemFromContainer(orphanedItem)))
|
|
{
|
|
RemoveFromSource(item);
|
|
target.AddToSource(item);
|
|
}
|
|
}
|
|
|
|
private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs)
|
|
{
|
|
_windowSubscription.Disposable = Disposable.Empty;
|
|
LoadedInstances.Remove(this);
|
|
}
|
|
|
|
private void MarkWrappedTabItems()
|
|
{
|
|
if (_dragablzItemsControl == null) return;
|
|
|
|
foreach (var pair in _dragablzItemsControl.Items.OfType<TabItem>().Select(tabItem =>
|
|
new
|
|
{
|
|
tabItem,
|
|
dragablzItem = _dragablzItemsControl.ItemContainerGenerator.ContainerFromItem(tabItem) as DragablzItem
|
|
}).Where(a => a.dragablzItem != null))
|
|
{
|
|
var toolTipBinding = new Binding("ToolTip") { Source = pair.tabItem };
|
|
BindingOperations.SetBinding(pair.dragablzItem, ToolTipProperty, toolTipBinding);
|
|
SetIsWrappingTabItem(pair.dragablzItem, true);
|
|
}
|
|
}
|
|
|
|
private void MarkInitialSelection()
|
|
{
|
|
if (_dragablzItemsControl == null ||
|
|
_dragablzItemsControl.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated) return;
|
|
|
|
if (_dragablzItemsControl == null || SelectedItem == null) return;
|
|
|
|
var tabItem = SelectedItem as TabItem;
|
|
tabItem?.SetCurrentValue(IsSelectedProperty, true);
|
|
|
|
var containerFromItem =
|
|
_dragablzItemsControl.ItemContainerGenerator.ContainerFromItem(SelectedItem) as DragablzItem;
|
|
|
|
containerFromItem?.SetCurrentValue(DragablzItem.IsSelectedProperty, true);
|
|
}
|
|
|
|
private void ItemDragStarted(object sender, DragablzDragStartedEventArgs e)
|
|
{
|
|
if (!IsMyItem(e.DragablzItem)) return;
|
|
|
|
//the thumb may steal the user selection, so we will try and apply it manually
|
|
if (_dragablzItemsControl == null) return;
|
|
|
|
e.DragablzItem.IsDropTargetFound = false;
|
|
|
|
var sourceOfDragItemsControl = ItemsControlFromItemContainer(e.DragablzItem) as DragablzItemsControl;
|
|
if (sourceOfDragItemsControl == null || !Equals(sourceOfDragItemsControl, _dragablzItemsControl)) return;
|
|
|
|
var itemsControlOffset = Mouse.GetPosition(_dragablzItemsControl);
|
|
_tabHeaderDragStartInformation = new TabHeaderDragStartInformation(e.DragablzItem, itemsControlOffset.X,
|
|
itemsControlOffset.Y, e.DragStartedEventArgs.HorizontalOffset, e.DragStartedEventArgs.VerticalOffset);
|
|
|
|
foreach (var otherItem in _dragablzItemsControl.Containers<DragablzItem>().Except(e.DragablzItem))
|
|
otherItem.IsSelected = false;
|
|
e.DragablzItem.IsSelected = true;
|
|
e.DragablzItem.PartitionAtDragStart = InterTabController?.Partition;
|
|
var item = _dragablzItemsControl.ItemContainerGenerator.ItemFromContainer(e.DragablzItem);
|
|
var tabItem = item as TabItem;
|
|
if (tabItem != null)
|
|
tabItem.IsSelected = true;
|
|
SelectedItem = item;
|
|
|
|
if (ShouldDragWindow(sourceOfDragItemsControl))
|
|
IsDraggingWindow = true;
|
|
}
|
|
|
|
private bool ShouldDragWindow(DragablzItemsControl sourceOfDragItemsControl)
|
|
{
|
|
return (Items.Count == 1
|
|
&& (InterTabController == null || InterTabController.MoveWindowWithSolitaryTabs)
|
|
&& !Layout.IsContainedWithinBranch(sourceOfDragItemsControl));
|
|
}
|
|
|
|
private void PreviewItemDragDelta(object sender, DragablzDragDeltaEventArgs e)
|
|
{
|
|
if (_dragablzItemsControl == null) return;
|
|
|
|
var sourceOfDragItemsControl = ItemsControlFromItemContainer(e.DragablzItem) as DragablzItemsControl;
|
|
if (sourceOfDragItemsControl == null || !Equals(sourceOfDragItemsControl, _dragablzItemsControl)) return;
|
|
|
|
if (!ShouldDragWindow(sourceOfDragItemsControl)) return;
|
|
|
|
if (MonitorReentry(e)) return;
|
|
|
|
var myWindow = Window.GetWindow(this);
|
|
if (myWindow == null) return;
|
|
|
|
if (_interTabTransfer != null)
|
|
{
|
|
var cursorPos = Native.GetCursorPos().ToWpf();
|
|
if (_interTabTransfer.BreachOrientation == Orientation.Vertical)
|
|
{
|
|
var vector = cursorPos - _interTabTransfer.DragStartWindowOffset;
|
|
myWindow.Left = vector.X;
|
|
myWindow.Top = vector.Y;
|
|
}
|
|
else
|
|
{
|
|
var offset = e.DragablzItem.TranslatePoint(_interTabTransfer.OriginatorContainer.MouseAtDragStart, myWindow);
|
|
var borderVector = myWindow.PointToScreen(new Point()).ToWpf() - new Point(myWindow.Left, myWindow.Top);
|
|
offset.Offset(borderVector.X, borderVector.Y);
|
|
myWindow.Left = cursorPos.X - offset.X;
|
|
myWindow.Top = cursorPos.Y - offset.Y;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
myWindow.Left += e.DragDeltaEventArgs.HorizontalChange;
|
|
myWindow.Top += e.DragDeltaEventArgs.VerticalChange;
|
|
}
|
|
|
|
e.Handled = true;
|
|
}
|
|
|
|
private bool MonitorReentry(DragablzDragDeltaEventArgs e)
|
|
{
|
|
var screenMousePosition = _dragablzItemsControl.PointToScreen(Mouse.GetPosition(_dragablzItemsControl));
|
|
|
|
var sourceTabablzControl = (TabablzControl) e.Source;
|
|
if (sourceTabablzControl.Items.Count > 1 && e.DragablzItem.LogicalIndex < sourceTabablzControl.FixedHeaderCount)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var otherTabablzControls = LoadedInstances
|
|
.Where(
|
|
tc =>
|
|
tc != this && tc.InterTabController != null && InterTabController != null
|
|
&& Equals(tc.InterTabController.Partition, InterTabController.Partition)
|
|
&& tc._dragablzItemsControl != null)
|
|
.Select(tc =>
|
|
{
|
|
var topLeft = tc._dragablzItemsControl.PointToScreen(new Point());
|
|
var lastFixedItem = tc._dragablzItemsControl.DragablzItems()
|
|
.OrderBy(di=> di.LogicalIndex)
|
|
.Take(tc._dragablzItemsControl.FixedItemCount)
|
|
.LastOrDefault();
|
|
//TODO work this for vert tabs
|
|
if (lastFixedItem != null)
|
|
topLeft.Offset(lastFixedItem.X + lastFixedItem.ActualWidth, 0);
|
|
var bottomRight =
|
|
tc._dragablzItemsControl.PointToScreen(new Point(tc._dragablzItemsControl.ActualWidth,
|
|
tc._dragablzItemsControl.ActualHeight));
|
|
|
|
return new {tc, topLeft, bottomRight};
|
|
});
|
|
|
|
|
|
var target = Native.SortWindowsTopToBottom(Application.Current.Windows.OfType<Window>())
|
|
.Join(otherTabablzControls, w => w, a => Window.GetWindow(a.tc), (w, a) => a)
|
|
.FirstOrDefault(a => new Rect(a.topLeft, a.bottomRight).Contains(screenMousePosition));
|
|
|
|
if (target == null) return false;
|
|
|
|
var mousePositionOnItem = Mouse.GetPosition(e.DragablzItem);
|
|
|
|
var floatingItemSnapShots = this.VisualTreeDepthFirstTraversal()
|
|
.OfType<Layout>()
|
|
.SelectMany(l => l.FloatingDragablzItems().Select(FloatingItemSnapShot.Take))
|
|
.ToList();
|
|
|
|
e.DragablzItem.IsDropTargetFound = true;
|
|
var item = RemoveItem(e.DragablzItem);
|
|
|
|
var interTabTransfer = new InterTabTransfer(item, e.DragablzItem, mousePositionOnItem, floatingItemSnapShots);
|
|
e.DragablzItem.IsDragging = false;
|
|
|
|
target.tc.ReceiveDrag(interTabTransfer);
|
|
e.Cancel = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
internal object RemoveItem(DragablzItem dragablzItem)
|
|
{
|
|
var item = _dragablzItemsControl.ItemContainerGenerator.ItemFromContainer(dragablzItem);
|
|
|
|
//stop the header shrinking if the tab stays open when empty
|
|
var minSize = EmptyHeaderSizingHint == EmptyHeaderSizingHint.PreviousTab
|
|
? new Size(_dragablzItemsControl.ActualWidth, _dragablzItemsControl.ActualHeight)
|
|
: new Size();
|
|
|
|
_dragablzItemsControl.MinHeight = 0;
|
|
_dragablzItemsControl.MinWidth = 0;
|
|
|
|
var contentPresenter = FindChildContentPresenter(item);
|
|
RemoveFromSource(item);
|
|
_itemsHolder.Children.Remove(contentPresenter);
|
|
|
|
if (Items.Count != 0) return item;
|
|
|
|
var window = Window.GetWindow(this);
|
|
if (window != null
|
|
&& InterTabController != null
|
|
&& InterTabController.InterTabClient.TabEmptiedHandler(this, window) == TabEmptiedResponse.CloseWindowOrLayoutBranch)
|
|
{
|
|
if (Layout.ConsolidateBranch(this)) return item;
|
|
|
|
try
|
|
{
|
|
SetIsClosingAsPartOfDragOperation(window, true);
|
|
window.Close();
|
|
}
|
|
finally
|
|
{
|
|
SetIsClosingAsPartOfDragOperation(window, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_dragablzItemsControl.MinHeight = minSize.Height;
|
|
_dragablzItemsControl.MinWidth = minSize.Width;
|
|
}
|
|
return item;
|
|
}
|
|
|
|
private void ItemDragCompleted(object sender, DragablzDragCompletedEventArgs e)
|
|
{
|
|
if (!IsMyItem(e.DragablzItem)) return;
|
|
|
|
_interTabTransfer = null;
|
|
_dragablzItemsControl.LockedMeasure = null;
|
|
IsDraggingWindow = false;
|
|
}
|
|
|
|
private void ItemDragDelta(object sender, DragablzDragDeltaEventArgs e)
|
|
{
|
|
if (!IsMyItem(e.DragablzItem)) return;
|
|
if (FixedHeaderCount > 0 &&
|
|
_dragablzItemsControl.ItemsOrganiser.Sort(_dragablzItemsControl.DragablzItems())
|
|
.Take(FixedHeaderCount)
|
|
.Contains(e.DragablzItem))
|
|
return;
|
|
|
|
if (_tabHeaderDragStartInformation == null ||
|
|
!Equals(_tabHeaderDragStartInformation.DragItem, e.DragablzItem) || InterTabController == null) return;
|
|
|
|
if (InterTabController.InterTabClient == null)
|
|
throw new InvalidOperationException("An InterTabClient must be provided on an InterTabController.");
|
|
|
|
MonitorBreach(e);
|
|
}
|
|
|
|
private bool IsMyItem(DragablzItem item)
|
|
{
|
|
return _dragablzItemsControl != null && _dragablzItemsControl.DragablzItems().Contains(item);
|
|
}
|
|
|
|
private void MonitorBreach(DragablzDragDeltaEventArgs e)
|
|
{
|
|
var mousePositionOnHeaderItemsControl = Mouse.GetPosition(_dragablzItemsControl);
|
|
|
|
Orientation? breachOrientation = null;
|
|
if (mousePositionOnHeaderItemsControl.X < -InterTabController.HorizontalPopoutGrace
|
|
|| (mousePositionOnHeaderItemsControl.X - _dragablzItemsControl.ActualWidth) > InterTabController.HorizontalPopoutGrace)
|
|
breachOrientation = Orientation.Horizontal;
|
|
else if (mousePositionOnHeaderItemsControl.Y < -InterTabController.VerticalPopoutGrace
|
|
|| (mousePositionOnHeaderItemsControl.Y - _dragablzItemsControl.ActualHeight) > InterTabController.VerticalPopoutGrace)
|
|
breachOrientation = Orientation.Vertical;
|
|
|
|
if (!breachOrientation.HasValue) return;
|
|
|
|
var newTabHost = InterTabController.InterTabClient.GetNewHost(InterTabController.InterTabClient,
|
|
InterTabController.Partition, this);
|
|
if (newTabHost?.TabablzControl == null || newTabHost.Container == null)
|
|
throw new ApplicationException("New tab host was not correctly provided");
|
|
|
|
var item = _dragablzItemsControl.ItemContainerGenerator.ItemFromContainer(e.DragablzItem);
|
|
var isTransposing = IsTransposing(newTabHost.TabablzControl);
|
|
|
|
var myWindow = Window.GetWindow(this);
|
|
if (myWindow == null) throw new ApplicationException("Unable to find owning window.");
|
|
var dragStartWindowOffset = ConfigureNewHostSizeAndGetDragStartWindowOffset(myWindow, newTabHost, e.DragablzItem, isTransposing);
|
|
|
|
var dragableItemHeaderPoint = e.DragablzItem.TranslatePoint(new Point(), _dragablzItemsControl);
|
|
var dragableItemSize = new Size(e.DragablzItem.ActualWidth, e.DragablzItem.ActualHeight);
|
|
var floatingItemSnapShots = this.VisualTreeDepthFirstTraversal()
|
|
.OfType<Layout>()
|
|
.SelectMany(l => l.FloatingDragablzItems().Select(FloatingItemSnapShot.Take))
|
|
.ToList();
|
|
|
|
var interTabTransfer = new InterTabTransfer(item, e.DragablzItem, breachOrientation.Value, dragStartWindowOffset, e.DragablzItem.MouseAtDragStart, dragableItemHeaderPoint, dragableItemSize, floatingItemSnapShots, isTransposing);
|
|
|
|
if (myWindow.WindowState == WindowState.Maximized)
|
|
{
|
|
var desktopMousePosition = Native.GetCursorPos().ToWpf();
|
|
newTabHost.Container.Left = desktopMousePosition.X - dragStartWindowOffset.X;
|
|
newTabHost.Container.Top = desktopMousePosition.Y - dragStartWindowOffset.Y;
|
|
}
|
|
else
|
|
{
|
|
newTabHost.Container.Left = myWindow.Left;
|
|
newTabHost.Container.Top = myWindow.Top;
|
|
}
|
|
newTabHost.Container.Show();
|
|
var contentPresenter = FindChildContentPresenter(item);
|
|
|
|
//stop the header shrinking if the tab stays open when empty
|
|
var minSize = EmptyHeaderSizingHint == EmptyHeaderSizingHint.PreviousTab
|
|
? new Size(_dragablzItemsControl.ActualWidth, _dragablzItemsControl.ActualHeight)
|
|
: new Size();
|
|
System.Diagnostics.Debug.WriteLine("B " + minSize);
|
|
|
|
RemoveFromSource(item);
|
|
_itemsHolder.Children.Remove(contentPresenter);
|
|
if (Items.Count == 0)
|
|
{
|
|
_dragablzItemsControl.MinHeight = minSize.Height;
|
|
_dragablzItemsControl.MinWidth = minSize.Width;
|
|
Layout.ConsolidateBranch(this);
|
|
}
|
|
|
|
RestorePreviousSelection();
|
|
|
|
foreach (var dragablzItem in _dragablzItemsControl.DragablzItems())
|
|
{
|
|
dragablzItem.IsDragging = false;
|
|
dragablzItem.IsSiblingDragging = false;
|
|
}
|
|
|
|
newTabHost.TabablzControl.ReceiveDrag(interTabTransfer);
|
|
interTabTransfer.OriginatorContainer.IsDropTargetFound = true;
|
|
e.Cancel = true;
|
|
}
|
|
|
|
private bool IsTransposing(TabControl target)
|
|
{
|
|
return IsVertical(this) != IsVertical(target);
|
|
}
|
|
|
|
private static bool IsVertical(TabControl tabControl)
|
|
{
|
|
return tabControl.TabStripPlacement == Dock.Left
|
|
|| tabControl.TabStripPlacement == Dock.Right;
|
|
}
|
|
|
|
private void RestorePreviousSelection()
|
|
{
|
|
var previousSelection = _previousSelection?.Target;
|
|
if (previousSelection != null && Items.Contains(previousSelection))
|
|
SelectedItem = previousSelection;
|
|
else
|
|
SelectedItem = Items.OfType<object>().FirstOrDefault();
|
|
}
|
|
|
|
private Point ConfigureNewHostSizeAndGetDragStartWindowOffset(Window currentWindow, INewTabHost<Window> newTabHost, DragablzItem dragablzItem, bool isTransposing)
|
|
{
|
|
var layout = this.VisualTreeAncestory().OfType<Layout>().FirstOrDefault();
|
|
Point dragStartWindowOffset;
|
|
if (layout != null)
|
|
{
|
|
newTabHost.Container.Width = ActualWidth + Math.Max(0, currentWindow.RestoreBounds.Width - layout.ActualWidth);
|
|
newTabHost.Container.Height = ActualHeight + Math.Max(0, currentWindow.RestoreBounds.Height - layout.ActualHeight);
|
|
dragStartWindowOffset = dragablzItem.TranslatePoint(new Point(), this);
|
|
//dragStartWindowOffset.Offset(currentWindow.RestoreBounds.Width - layout.ActualWidth, currentWindow.RestoreBounds.Height - layout.ActualHeight);
|
|
}
|
|
else
|
|
{
|
|
if (newTabHost.Container.GetType() == currentWindow.GetType())
|
|
{
|
|
newTabHost.Container.Width = currentWindow.RestoreBounds.Width;
|
|
newTabHost.Container.Height = currentWindow.RestoreBounds.Height;
|
|
dragStartWindowOffset = isTransposing ? new Point(dragablzItem.MouseAtDragStart.X, dragablzItem.MouseAtDragStart.Y) : dragablzItem.TranslatePoint(new Point(), currentWindow);
|
|
}
|
|
else
|
|
{
|
|
newTabHost.Container.Width = ActualWidth;
|
|
newTabHost.Container.Height = ActualHeight;
|
|
dragStartWindowOffset = isTransposing ? new Point() : dragablzItem.TranslatePoint(new Point(), this);
|
|
dragStartWindowOffset.Offset(dragablzItem.MouseAtDragStart.X, dragablzItem.MouseAtDragStart.Y);
|
|
return dragStartWindowOffset;
|
|
}
|
|
}
|
|
|
|
dragStartWindowOffset.Offset(dragablzItem.MouseAtDragStart.X, dragablzItem.MouseAtDragStart.Y);
|
|
var borderVector = currentWindow.PointToScreen(new Point()).ToWpf() - new Point(currentWindow.GetActualLeft(), currentWindow.GetActualTop());
|
|
dragStartWindowOffset.Offset(borderVector.X, borderVector.Y);
|
|
return dragStartWindowOffset;
|
|
}
|
|
|
|
internal void ReceiveDrag(InterTabTransfer interTabTransfer)
|
|
{
|
|
var myWindow = Window.GetWindow(this);
|
|
if (myWindow == null) throw new ApplicationException("Unable to find owning window.");
|
|
myWindow.Activate();
|
|
|
|
_interTabTransfer = interTabTransfer;
|
|
|
|
if (Items.Count == 0)
|
|
{
|
|
if (interTabTransfer.IsTransposing)
|
|
_dragablzItemsControl.LockedMeasure = new Size(
|
|
interTabTransfer.ItemSize.Width,
|
|
interTabTransfer.ItemSize.Height);
|
|
else
|
|
_dragablzItemsControl.LockedMeasure = new Size(
|
|
interTabTransfer.ItemPositionWithinHeader.X + interTabTransfer.ItemSize.Width,
|
|
interTabTransfer.ItemPositionWithinHeader.Y + interTabTransfer.ItemSize.Height);
|
|
}
|
|
|
|
var lastFixedItem = _dragablzItemsControl.DragablzItems()
|
|
.OrderBy(i => i.LogicalIndex)
|
|
.Take(_dragablzItemsControl.FixedItemCount)
|
|
.LastOrDefault();
|
|
|
|
AddToSource(interTabTransfer.Item);
|
|
SelectedItem = interTabTransfer.Item;
|
|
|
|
Dispatcher.BeginInvoke(new Action(() => Layout.RestoreFloatingItemSnapShots(this, interTabTransfer.FloatingItemSnapShots)), DispatcherPriority.Loaded);
|
|
_dragablzItemsControl.InstigateDrag(interTabTransfer.Item, newContainer =>
|
|
{
|
|
newContainer.PartitionAtDragStart = interTabTransfer.OriginatorContainer.PartitionAtDragStart;
|
|
newContainer.IsDropTargetFound = true;
|
|
|
|
if (interTabTransfer.TransferReason == InterTabTransferReason.Breach)
|
|
{
|
|
if (interTabTransfer.IsTransposing)
|
|
{
|
|
newContainer.Y = 0;
|
|
newContainer.X = 0;
|
|
}
|
|
else
|
|
{
|
|
newContainer.Y = interTabTransfer.OriginatorContainer.Y;
|
|
newContainer.X = interTabTransfer.OriginatorContainer.X;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom)
|
|
{
|
|
var mouseXOnItemsControl = Native.GetCursorPos().X -
|
|
_dragablzItemsControl.PointToScreen(new Point()).X;
|
|
var newX = mouseXOnItemsControl - interTabTransfer.DragStartItemOffset.X;
|
|
if (lastFixedItem != null)
|
|
{
|
|
newX = Math.Max(newX, lastFixedItem.X + lastFixedItem.ActualWidth);
|
|
}
|
|
newContainer.X = newX;
|
|
newContainer.Y = 0;
|
|
}
|
|
else
|
|
{
|
|
var mouseYOnItemsControl = Native.GetCursorPos().Y -
|
|
_dragablzItemsControl.PointToScreen(new Point()).Y;
|
|
var newY = mouseYOnItemsControl - interTabTransfer.DragStartItemOffset.Y;
|
|
if (lastFixedItem != null)
|
|
{
|
|
newY = Math.Max(newY, lastFixedItem.Y + lastFixedItem.ActualHeight);
|
|
}
|
|
newContainer.X = 0;
|
|
newContainer.Y = newY;
|
|
}
|
|
}
|
|
newContainer.MouseAtDragStart = interTabTransfer.DragStartItemOffset;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// generate a ContentPresenter for the selected item
|
|
/// </summary>
|
|
private void UpdateSelectedItem()
|
|
{
|
|
if (_itemsHolder == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CreateChildContentPresenter(SelectedItem);
|
|
|
|
// show the right child
|
|
var selectedContent = GetContent(SelectedItem);
|
|
foreach (ContentPresenter child in _itemsHolder.Children)
|
|
{
|
|
var isSelected = (child.Content == selectedContent);
|
|
child.Visibility = isSelected ? Visibility.Visible : Visibility.Collapsed;
|
|
child.IsEnabled = isSelected;
|
|
}
|
|
}
|
|
|
|
private static object GetContent(object item)
|
|
{
|
|
return (item is TabItem) ? ((TabItem) item).Content : item;
|
|
}
|
|
|
|
/// <summary>
|
|
/// create the child ContentPresenter for the given item (could be data or a TabItem)
|
|
/// </summary>
|
|
/// <param name="item"></param>
|
|
/// <returns></returns>
|
|
private void CreateChildContentPresenter(object item)
|
|
{
|
|
if (item == null) return;
|
|
|
|
var cp = FindChildContentPresenter(item);
|
|
if (cp != null) return;
|
|
|
|
// the actual child to be added. cp.Tag is a reference to the TabItem
|
|
cp = new ContentPresenter
|
|
{
|
|
Content = GetContent(item),
|
|
ContentTemplate = ContentTemplate,
|
|
ContentTemplateSelector = ContentTemplateSelector,
|
|
ContentStringFormat = ContentStringFormat,
|
|
Visibility = Visibility.Collapsed,
|
|
};
|
|
_itemsHolder.Children.Add(cp);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find the CP for the given object. data could be a TabItem or a piece of data
|
|
/// </summary>
|
|
/// <param name="data"></param>
|
|
/// <returns></returns>
|
|
private ContentPresenter FindChildContentPresenter(object data)
|
|
{
|
|
if (data is TabItem)
|
|
data = ((TabItem) data).Content;
|
|
|
|
return data == null
|
|
? null
|
|
: _itemsHolder?.Children.Cast<ContentPresenter>().FirstOrDefault(cp => cp.Content == data);
|
|
}
|
|
|
|
private void ItemContainerGeneratorOnStatusChanged(object sender, EventArgs eventArgs)
|
|
{
|
|
MarkWrappedTabItems();
|
|
MarkInitialSelection();
|
|
}
|
|
|
|
private static void CloseItem(DragablzItem item, TabablzControl owner)
|
|
{
|
|
if (item == null)
|
|
throw new ApplicationException("Valid DragablzItem to close is required.");
|
|
|
|
if (owner == null)
|
|
throw new ApplicationException("Valid TabablzControl container is required.");
|
|
|
|
if (!owner.IsMyItem(item))
|
|
throw new ApplicationException("TabablzControl container must be an owner of the DragablzItem to close");
|
|
|
|
var cancel = false;
|
|
if (owner.ClosingItemCallback != null)
|
|
{
|
|
var callbackArgs = new ItemActionCallbackArgs<TabablzControl>(Window.GetWindow(owner), owner, item);
|
|
owner.ClosingItemCallback(callbackArgs);
|
|
cancel = callbackArgs.IsCancelled;
|
|
}
|
|
|
|
if (!cancel)
|
|
owner.RemoveItem(item);
|
|
}
|
|
|
|
private static void CloseItemCanExecuteClassHandler(object sender, CanExecuteRoutedEventArgs e)
|
|
{
|
|
e.CanExecute = FindOwner(e.Parameter, e.OriginalSource) != null;
|
|
}
|
|
private static void CloseItemClassHandler(object sender, ExecutedRoutedEventArgs e)
|
|
{
|
|
var owner = FindOwner(e.Parameter, e.OriginalSource);
|
|
|
|
if (owner == null) throw new ApplicationException("Unable to ascertain DragablzItem to close.");
|
|
|
|
CloseItem(owner.Item1, owner.Item2);
|
|
}
|
|
|
|
private static Tuple<DragablzItem, TabablzControl> FindOwner(object eventParameter, object eventOriginalSource)
|
|
{
|
|
var dragablzItem = eventParameter as DragablzItem;
|
|
if (dragablzItem == null)
|
|
{
|
|
var dependencyObject = eventOriginalSource as DependencyObject;
|
|
dragablzItem = dependencyObject.VisualTreeAncestory().OfType<DragablzItem>().FirstOrDefault();
|
|
if (dragablzItem == null)
|
|
{
|
|
var popup = dependencyObject.LogicalTreeAncestory().OfType<Popup>().LastOrDefault();
|
|
if (popup?.PlacementTarget != null)
|
|
{
|
|
dragablzItem = popup.PlacementTarget.VisualTreeAncestory().OfType<DragablzItem>().FirstOrDefault();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dragablzItem == null) return null;
|
|
|
|
var tabablzControl = LoadedInstances.FirstOrDefault(tc => tc.IsMyItem(dragablzItem));
|
|
|
|
return tabablzControl == null ? null : new Tuple<DragablzItem, TabablzControl>(dragablzItem, tabablzControl);
|
|
}
|
|
|
|
private void AddItemHandler(object sender, ExecutedRoutedEventArgs e)
|
|
{
|
|
if (NewItemFactory == null)
|
|
throw new InvalidOperationException("NewItemFactory must be provided.");
|
|
|
|
var newItem = NewItemFactory();
|
|
if (newItem == null) throw new ApplicationException("NewItemFactory returned null.");
|
|
|
|
AddToSource(newItem);
|
|
SelectedItem = newItem;
|
|
|
|
Dispatcher.BeginInvoke(new Action(_dragablzItemsControl.InvalidateMeasure), DispatcherPriority.Loaded);
|
|
}
|
|
|
|
private void PrepareChildContainerForItemOverride(DependencyObject dependencyObject, object o)
|
|
{
|
|
var dragablzItem = dependencyObject as DragablzItem;
|
|
if (dragablzItem != null && HeaderMemberPath != null)
|
|
{
|
|
var contentBinding = new Binding(HeaderMemberPath) { Source = o };
|
|
dragablzItem.SetBinding(ContentControl.ContentProperty, contentBinding);
|
|
dragablzItem.UnderlyingContent = o;
|
|
}
|
|
|
|
SetIsWrappingTabItem(dependencyObject, o is TabItem);
|
|
}
|
|
}
|
|
}
|