Code:
/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / cdf / src / NetFx40 / Tools / System.Activities.Presentation / System / Activities / Presentation / WorkflowItemsPresenter.cs / 1305376 / WorkflowItemsPresenter.cs
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//---------------------------------------------------------------
namespace System.Activities.Presentation
{
using System.Activities.Presentation.Internal.PropertyEditing;
using System.Activities.Presentation.Model;
using System.Activities.Presentation.Services;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Threading;
using System.Activities.Presentation.View;
using System.Windows.Shapes;
// This is similar to the WorkflowItemPresenter , but its an edit box for collections. It supports drag drop, and delete.
// it auto refreshes the collection on collection changed events.
public class WorkflowItemsPresenter : ContentControl, ICompositeView
{
public static readonly DependencyProperty HintTextProperty =
DependencyProperty.Register("HintText", typeof(string), typeof(WorkflowItemsPresenter), new UIPropertyMetadata(String.Empty, new PropertyChangedCallback(WorkflowItemsPresenter.OnHintTextChanged)));
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ModelItemCollection), typeof(WorkflowItemsPresenter), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(WorkflowItemsPresenter.OnItemsChanged)));
public static readonly DependencyProperty SpacerTemplateProperty =
DependencyProperty.Register("SpacerTemplate", typeof(DataTemplate), typeof(WorkflowItemsPresenter), new UIPropertyMetadata(null));
public static readonly DependencyProperty HeaderTemplateProperty =
DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(WorkflowItemsPresenter), new UIPropertyMetadata(null));
public static readonly DependencyProperty FooterTemplateProperty =
DependencyProperty.Register("FooterTemplate", typeof(DataTemplate), typeof(WorkflowItemsPresenter), new UIPropertyMetadata(null));
public static readonly DependencyProperty ItemsPanelProperty =
DependencyProperty.Register("ItemsPanel", typeof(ItemsPanelTemplate), typeof(WorkflowItemsPresenter), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(WorkflowItemsPresenter.OnItemsPanelChanged)));
public static readonly DependencyProperty IndexProperty =
DependencyProperty.RegisterAttached("Index", typeof(int), typeof(WorkflowItemsPresenter), new UIPropertyMetadata(addAtEndMarker));
public static readonly DependencyProperty AllowedItemTypeProperty =
DependencyProperty.Register("AllowedItemType", typeof(Type), typeof(WorkflowItemsPresenter), new UIPropertyMetadata(typeof(object)));
public static readonly DependencyProperty IsDefaultContainerProperty =
DependencyProperty.Register("IsDefaultContainer", typeof(bool), typeof(WorkflowItemsPresenter), new UIPropertyMetadata(false));
public static readonly DependencyProperty DroppingTypeResolvingOptionsProperty =
DependencyProperty.Register("DroppingTypeResolvingOptions", typeof(TypeResolvingOptions), typeof(WorkflowItemsPresenter));
const int addAtEndMarker = -2;
int selectedSpacerIndex;
ItemsControl panel;
Grid hintTextGrid;
EditingContext context = null;
bool isRegisteredWithParent = false;
bool populateOnLoad = false;
bool handleSpacerGotKeyboardFocus = false;
Grid outerGrid;
public WorkflowItemsPresenter()
{
panel = new ItemsControl();
panel.Focusable = false;
hintTextGrid = new Grid();
hintTextGrid.Focusable = false;
hintTextGrid.Background = Brushes.Transparent;
hintTextGrid.DataContext = this;
hintTextGrid.SetBinding(Grid.MinHeightProperty, "MinHeight");
hintTextGrid.SetBinding(Grid.MinWidthProperty, "MinWidth");
TextBlock text = new TextBlock();
text.Focusable = false;
text.SetBinding(TextBlock.TextProperty, "HintText");
text.HorizontalAlignment = HorizontalAlignment.Center;
text.VerticalAlignment = VerticalAlignment.Center;
text.DataContext = this;
text.Foreground = new SolidColorBrush(SystemColors.GrayTextColor);
text.FontStyle = FontStyles.Italic;
((IAddChild)hintTextGrid).AddChild(text);
this.outerGrid = new Grid()
{
RowDefinitions = { new RowDefinition(), new RowDefinition() },
ColumnDefinitions = { new ColumnDefinition() }
};
Grid.SetRow(this.panel, 0);
Grid.SetColumn(this.panel, 0);
Grid.SetRow(this.hintTextGrid, 1);
Grid.SetColumn(this.hintTextGrid, 0);
this.outerGrid.Children.Add(panel);
this.outerGrid.Children.Add(hintTextGrid);
}
public Type AllowedItemType
{
get { return (Type)GetValue(AllowedItemTypeProperty); }
set { SetValue(AllowedItemTypeProperty, value); }
}
public string HintText
{
get { return (string)GetValue(HintTextProperty); }
set { SetValue(HintTextProperty, value); }
}
[Fx.Tag.KnownXamlExternal]
public DataTemplate SpacerTemplate
{
get { return (DataTemplate)GetValue(SpacerTemplateProperty); }
set { SetValue(SpacerTemplateProperty, value); }
}
[Fx.Tag.KnownXamlExternal]
public DataTemplate HeaderTemplate
{
get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
set { SetValue(HeaderTemplateProperty, value); }
}
[Fx.Tag.KnownXamlExternal]
public DataTemplate FooterTemplate
{
get { return (DataTemplate)GetValue(FooterTemplateProperty); }
set { SetValue(FooterTemplateProperty, value); }
}
[Fx.Tag.KnownXamlExternal]
public ItemsPanelTemplate ItemsPanel
{
get { return (ItemsPanelTemplate)GetValue(ItemsPanelProperty); }
set { SetValue(ItemsPanelProperty, value); }
}
[SuppressMessage(FxCop.Category.Usage, FxCop.Rule.CollectionPropertiesShouldBeReadOnly,
Justification = "Setter is provided to enable setting this property in code.")]
[Fx.Tag.KnownXamlExternal]
public ModelItemCollection Items
{
get { return (ModelItemCollection)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
EditingContext Context
{
get
{
if (context == null)
{
IModelTreeItem modelTreeItem = this.Items as IModelTreeItem;
if (modelTreeItem != null)
{
this.context = modelTreeItem.ModelTreeManager.Context;
}
}
return context;
}
}
public bool IsDefaultContainer
{
get { return (bool)GetValue(IsDefaultContainerProperty); }
set { SetValue(IsDefaultContainerProperty, value); }
}
[Fx.Tag.KnownXamlExternal]
public TypeResolvingOptions DroppingTypeResolvingOptions
{
get { return (TypeResolvingOptions)GetValue(DroppingTypeResolvingOptionsProperty); }
set { SetValue(DroppingTypeResolvingOptionsProperty, value); }
}
static void OnHintTextChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
WorkflowItemsPresenter itemsPresenter = (WorkflowItemsPresenter)dependencyObject;
itemsPresenter.UpdateHintTextVisibility(e.NewValue as string);
}
static void OnItemsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
WorkflowItemsPresenter itemsPresenter = (WorkflowItemsPresenter)dependencyObject;
itemsPresenter.OnItemsChanged(e.NewValue);
}
static void OnItemsPanelChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
WorkflowItemsPresenter itemsPresenter = (WorkflowItemsPresenter)dependencyObject;
itemsPresenter.panel.ItemsPanel = (ItemsPanelTemplate)e.NewValue;
}
void OnItemsChanged(object newItems)
{
ModelItemCollection newItemsCollection = (ModelItemCollection)newItems;
if (!isRegisteredWithParent)
{
CutCopyPasteHelper.RegisterWithParentViewElement(this);
isRegisteredWithParent = true;
}
populateOnLoad = false;
PopulateContent();
}
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// if this.Items is null, and we are getting a collection changed that
// means this event some how happened before this can get the unloaded event
// and unsubscribe from this event.
if (this.Items == null)
{
return;
}
bool fullRepopulateNeeded = true;
// when one item is dropped into this items presenter focus on the new view element for it.
if (e.Action == NotifyCollectionChangedAction.Add
&& e.NewItems != null
&& e.NewItems.Count == 1)
{
// insert itemview and spacer
fullRepopulateNeeded = false;
int itemViewIndex = GetViewIndexForItem(e.NewStartingIndex);
VirtualizedContainerService containerService = this.Context.Services.GetService();
UIElement itemView = containerService.GetContainer((ModelItem)e.NewItems[0], this);
this.panel.Items.Insert(itemViewIndex, itemView as UIElement);
// index 2 + i*2 + 1 is spacer i+1
FrameworkElement spacer = CreateSpacer();
this.panel.Items.Insert(itemViewIndex + 1, spacer);
ModelItem insertedItem = (ModelItem)e.NewItems[0];
this.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, (Action)(() =>
{
UIElement view = (UIElement)insertedItem.View;
if (view != null)
{
Keyboard.Focus(view);
}
}));
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
if (e.OldItems != null && e.OldItems.Count == 1)
{
fullRepopulateNeeded = false;
int itemViewIndex = GetViewIndexForItem(e.OldStartingIndex);
this.panel.Items.RemoveAt(itemViewIndex);
//remove spacer also
this.panel.Items.RemoveAt(itemViewIndex);
}
if (this.Items.Count == 0)
{
fullRepopulateNeeded = true;
}
// deselect removed items
if (this.Context != null)
{
IList selectedItems = this.Context.Items.GetValue().SelectedObjects.ToList();
foreach (ModelItem selectedAndRemovedItem in selectedItems.Intersect(e.OldItems.Cast()))
{
Selection.Toggle(this.Context, selectedAndRemovedItem);
}
}
}
if (this.Items.Count > 0)
{
this.hintTextGrid.Visibility = Visibility.Collapsed;
}
else
{
this.hintTextGrid.Visibility = Visibility.Visible;
}
if (fullRepopulateNeeded)
{
PopulateContent();
}
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
this.AllowDrop = true;
this.Content = outerGrid;
if (this.ItemsPanel != null)
{
this.panel.ItemsPanel = this.ItemsPanel;
}
ICompositeViewEvents containerEvents = null;
bool isDefault = false;
this.Loaded += (s, eventArgs) =>
{
isDefault = this.IsDefaultContainer;
selectedSpacerIndex = addAtEndMarker;
DependencyObject parent = VisualTreeHelper.GetParent(this);
while (null != parent && !typeof(ICompositeViewEvents).IsAssignableFrom(parent.GetType()))
{
parent = VisualTreeHelper.GetParent(parent);
}
containerEvents = parent as ICompositeViewEvents;
if (null != containerEvents)
{
if (isDefault)
{
containerEvents.RegisterDefaultCompositeView(this);
}
else
{
containerEvents.RegisterCompositeView(this);
}
}
if (this.Items != null)
{
//UnRegistering because of 137896: Inside tab control multiple Loaded events happen without an Unloaded event.
this.Items.CollectionChanged -= new NotifyCollectionChangedEventHandler(OnCollectionChanged);
this.Items.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
}
if (populateOnLoad)
{
this.PopulateContent();
}
};
this.Unloaded += (s, eventArgs) =>
{
if (null != containerEvents)
{
if (isDefault)
{
containerEvents.UnregisterDefaultCompositeView(this);
}
else
{
containerEvents.UnregisterCompositeView(this);
}
}
if (this.Items != null)
{
this.Items.CollectionChanged -= new NotifyCollectionChangedEventHandler(OnCollectionChanged);
}
populateOnLoad = true;
};
}
void PopulateContent()
{
this.panel.Items.Clear();
if (this.Items != null)
{
// index 0 is header.
ContentControl header = new ContentControl();
header.Focusable = false;
header.ContentTemplate = this.HeaderTemplate;
header.SetValue(IndexProperty, 0);
header.Drop += new DragEventHandler(OnSpacerDrop);
this.panel.Items.Add(header);
// index 1 is first spacer
FrameworkElement startSpacer = CreateSpacer();
this.panel.Items.Add(startSpacer);
foreach (ModelItem item in this.Items)
{
// index 2 + i*2 is itemView i
VirtualizedContainerService containerService = this.Context.Services.GetService();
UIElement itemView = containerService.GetContainer(item, this);
this.panel.Items.Add(itemView as UIElement);
// index 2 + i*2 + 1 is spacer i+1
FrameworkElement spacer = CreateSpacer();
this.panel.Items.Add(spacer);
}
// index 2 + count*2 is footer
ContentControl footer = new ContentControl();
footer.ContentTemplate = this.FooterTemplate;
footer.Focusable = true;
footer.IsHitTestVisible = true;
footer.IsTabStop = true;
footer.SetValue(IndexProperty, addAtEndMarker);
footer.Drop += new DragEventHandler(OnSpacerDrop);
footer.LostFocus += new RoutedEventHandler(OnSpacerLostFocus);
footer.GotFocus += new RoutedEventHandler(OnSpacerGotFocus);
this.panel.Items.Add(footer);
footer.Focusable = false;
}
UpdateHintTextVisibility(HintText);
}
int GetViewIndexForItem(int itemIndex)
{
return 2 + itemIndex * 2;
}
int GetViewIndexForSpacer(int spacerIndex)
{
return 2 + spacerIndex * 2 + 1;
}
int GetSpacerIndex(int viewIndex)
{
if (viewIndex == 1)
{
return 0;
}
else
{
return (viewIndex - 3) / 2 + 1;
}
}
void UpdateHintTextVisibility(string hintText)
{
if (this.hintTextGrid != null && this.Items != null)
{
this.hintTextGrid.Visibility = (this.Items.Count == 0 && !string.IsNullOrEmpty(hintText)) ? Visibility.Visible : Visibility.Collapsed;
}
}
private FrameworkElement CreateSpacer()
{
FrameworkElement spacer = (this.SpacerTemplate != null) ? (FrameworkElement)this.SpacerTemplate.LoadContent() : new Rectangle();
spacer.IsHitTestVisible = true;
Control spacerControl = spacer as Control;
if (spacerControl != null)
{
spacerControl.IsTabStop = true;
}
spacer.Drop += new DragEventHandler(OnSpacerDrop);
spacer.LostFocus += new RoutedEventHandler(OnSpacerLostFocus);
spacer.GotFocus += new RoutedEventHandler(OnSpacerGotFocus);
spacer.GotKeyboardFocus += new KeyboardFocusChangedEventHandler(OnSpacerGotKeyboardFocus);
spacer.MouseDown += new MouseButtonEventHandler(OnSpacerMouseDown);
return spacer;
}
void OnSpacerDrop(object sender, DragEventArgs e)
{
int index = GetSpacerIndexFromView(sender);
OnItemDropped(e, index);
}
private int GetSpacerIndexFromView(object sender)
{
if (((DependencyObject)sender).ReadLocalValue(IndexProperty) != DependencyProperty.UnsetValue)
{
int index = (int)((DependencyObject)sender).GetValue(IndexProperty);
return index;
}
else
{
return GetSpacerIndex(this.panel.Items.IndexOf(sender));
}
}
void OnSpacerGotFocus(object sender, RoutedEventArgs e)
{
int index = GetSpacerIndexFromView(sender);
selectedSpacerIndex = index;
}
void OnSpacerGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
// Handle the event so that it won't be routed to the containing designer to affect selection
if (handleSpacerGotKeyboardFocus)
{
e.Handled = true;
}
}
void OnSpacerLostFocus(object sender, RoutedEventArgs e)
{
int index = GetSpacerIndexFromView(sender);
selectedSpacerIndex = addAtEndMarker;
}
void OnSpacerMouseDown(object sender, MouseButtonEventArgs e)
{
// Schedule the Keyboard.Focus command to let it execute later than WorkflowViewElement.OnMouseDown
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
{
this.handleSpacerGotKeyboardFocus = true;
Keyboard.Focus((FrameworkElement)sender);
this.handleSpacerGotKeyboardFocus = false;
}));
}
void OnItemDropped(DragEventArgs e, int index)
{
DragDropHelper.SetDragDropCompletedEffects(e, DragDropEffects.None);
object droppedObject = DragDropHelper.GetDroppedObject(this, e, Context);
ICompositeView sourceContainer = DragDropHelper.GetCompositeView(e);
if (droppedObject != null)
{
// move within this presenter case, dont set anything in e.Effects.
// this just causes an change in ordering within the collection
if (sourceContainer == this)
{
Fx.Assert(this.Items.Contains(droppedObject), "droppedObject should have been added to the Items collection");
int oldIndex;
if (droppedObject is ModelItem)
{
oldIndex = this.Items.IndexOf((ModelItem)droppedObject);
}
else
{
oldIndex = ((IList)this.Items.GetCurrentValue()).IndexOf(droppedObject);
}
// this check excludes the case where an item is dropped on to the spacer just before or just after itself.
if (index != oldIndex && index != (oldIndex + 1))
{
//if element is placed ahead of old location, decrement the index not to include moved object
if (oldIndex < index)
{
--index;
}
this.Items.Remove(droppedObject);
InsertItem(index, droppedObject);
}
}
// dropped from elsewhere case, consider it a move.
else
{
if (IsDropAllowed(droppedObject))
{
InsertItem(index, droppedObject);
DragDropHelper.SetDragDropCompletedEffects(e, DragDropEffects.Move);
}
}
e.Handled = true;
}
}
private bool IsDropAllowed(object droppedObject)
{
bool isDropAllowed = false;
ModelItem modelItem = droppedObject as ModelItem;
if (modelItem != null && !IsInParentChain(modelItem))
{
if (this.AllowedItemType.IsAssignableFrom(modelItem.ItemType))
{
isDropAllowed = true;
}
}
else if (droppedObject is Type && this.AllowedItemType.IsAssignableFrom((Type)droppedObject))
{
isDropAllowed = true;
}
else
{
if (this.AllowedItemType.IsAssignableFrom(droppedObject.GetType()))
{
isDropAllowed = true;
}
}
return isDropAllowed;
}
private bool IsInParentChain(ModelItem droppedModelItem)
{
bool isInParentChain = false;
ModelItem parentModelItem = this.Items;
while (parentModelItem != null)
{
if (parentModelItem == droppedModelItem)
{
isInParentChain = true;
break;
}
parentModelItem = parentModelItem.Parent;
}
return isInParentChain;
}
void InsertItem(int index, object droppedObject)
{
ModelItem insertedItem = null;
if (index == addAtEndMarker || index >= this.Items.Count)
{
insertedItem = this.Items.Add(droppedObject);
}
else
{
insertedItem = this.Items.Insert(index, droppedObject);
}
if (insertedItem != null)
{
Selection.SelectOnly(this.Context, insertedItem);
}
}
protected override void OnDrop(DragEventArgs e)
{
int index = addAtEndMarker;
WorkflowViewElement dropTarget = null;
if (e.OriginalSource is WorkflowViewElement)
{
dropTarget = (WorkflowViewElement)e.OriginalSource;
}
else
{
dropTarget = VisualTreeUtils.FindFocusableParent((UIElement)e.OriginalSource);
}
if (null != dropTarget && null != dropTarget.ModelItem)
{
int targetIndex = this.Items.IndexOf(dropTarget.ModelItem);
if (-1 != targetIndex)
{
index = targetIndex + 1;
}
}
OnItemDropped(e, index);
base.OnDrop(e);
}
void OnDrag(DragEventArgs e)
{
if (!e.Handled)
{
if (!DragDropHelper.AllowDrop(e.Data, this.Context, this.AllowedItemType))
{
e.Effects = DragDropEffects.None;
}
e.Handled = true;
}
}
protected override void OnDragEnter(DragEventArgs e)
{
this.OnDrag(e);
base.OnDragEnter(e);
}
protected override void OnDragOver(DragEventArgs e)
{
this.OnDrag(e);
base.OnDragOver(e);
}
public void OnItemMoved(ModelItem modelItem)
{
if (this.Items.Contains(modelItem))
{
this.Items.Remove(modelItem);
}
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return new WorkflowItemsPresenterAutomationPeer(this);
}
public object OnItemsCut(List itemsToCut)
{
List