|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Drawing;
using System.Windows.Forms.Design.Behavior;
namespace System.Windows.Forms.Design;
/// <summary>
/// Designer for ToolStripMenuItems.
/// </summary>
internal class ToolStripMenuItemDesigner : ToolStripDropDownItemDesigner
{
private const int GLYPHINSET = 2;
// This is the typeHereNode that appears to the bottom and right of each committed ToolStripDropDownItem
private DesignerToolStripControlHost _typeHereNode;
private ToolStripTemplateNode _typeHereTemplateNode;
// This is the TemplateNode.EditorToolStrip. The editor ToolStrip is encapsulated in a ToolStripControlHost and
// then added as a ToolStripDropDownItem on the current ToolStripDropDownItems DropDown.
private DesignerToolStripControlHost _commitedEditorNode;
// Actual InSitu Editor. This is created for every selected ToolStripDropDownItem and is DISPOSED immediately
// after the item comes out of InSitu edit Mode.
private ToolStripTemplateNode _commitedTemplateNode;
// DesignerHost for current Component
private IDesignerHost _designerHost;
private ToolStripItem _parentItem;
private ToolStripAdornerWindowService _toolStripAdornerWindowService;
private ToolStripKeyboardHandlingService _keyboardHandlingService;
// Make Selection service a class level member.. used a lot.
private ISelectionService _selectionService;
private IComponentChangeService _componentChangeService;
// DesignerTransaction used for removing Items
private DesignerTransaction _pendingTransaction;
private bool _fireComponentChanged;
// indicates that we are adding new MenuItem ..
private bool _componentAddingFired;
// new item index
private int _indexToInsertNewItem = -1;
// only used by DropDownMenu, DropDown or ContextMenuStrip.
private DesignerTransaction _insertMenuItemTransaction;
// We want one parent transaction when the item is added by InSitu editing which is now supported only for MenuItems.
// This parent transaction would include
// - Adding the DummyItem
// - Swapping the DummyItem with the InsItu
// - Committing the new Item with the text entered in the InSitu.
private DesignerTransaction _newMenuItemTransaction;
// DropDownToInvalidate
private Rectangle _dropDownSizeToInvalidate = Rectangle.Empty;
// DropDownToInvalidate while ComponentRemove
private Rectangle _boundsToInvalidateOnRemove = Rectangle.Empty;
private ToolStripDropDownGlyph _rootControlGlyph;
private bool _initialized;
// Hook on the Undoing and Undone Events...
private UndoEngine _undoEngine;
private bool _undoingCalled;
private bool _addingDummyItem;
// custom DropDown
private ToolStripDropDown _customDropDown;
private bool _dropDownSet;
private SerializationStore _serializedDataForDropDownItems;
private bool _dropDownSetFailed;
/// <summary>
/// The ToolStripDropDownItems are the associated components.
/// We want those to come with in any cut, copy opreations.
/// </summary>
public override ICollection AssociatedComponents
{
get
{
ArrayList items = [];
// Return the list only if the DropDown is AutoGenerated. else the DropDown property set/get will handle it.
if (MenuItem.DropDown.IsAutoGenerated)
{
foreach (ToolStripItem item in MenuItem.DropDownItems)
{
if (item is not DesignerToolStripControlHost)
{
items.Add(item);
}
}
}
return items;
}
}
/// <summary>
/// ShadowProperty.
/// </summary>
private bool CheckOnClick
{
get => (bool)ShadowProperties[nameof(CheckOnClick)];
set => ShadowProperties[nameof(CheckOnClick)] = value;
}
private bool DoubleClickEnabled
{
get => (bool)ShadowProperties[nameof(DoubleClickEnabled)];
set => ShadowProperties[nameof(DoubleClickEnabled)] = value;
}
private ToolStripDropDown DropDown
{
get => _customDropDown;
set
{
_dropDownSetFailed = false;
if (IsParentDropDown(value))
{
_dropDownSetFailed = true;
throw new ArgumentException(string.Format(SR.InvalidArgument, "DropDown", value.ToString()));
}
// Check if DropDown Exists .. and if yes then Close the DropDown
if (MenuItem.DropDown is not null)
{
RemoveTypeHereNode(MenuItem);
// remove and destroy all the items...
if (MenuItem.DropDown.IsAutoGenerated && MenuItem.DropDownItems.Count > 0)
{
// Serialize all the DropDownItems for this Item....
if (TryGetService(out ComponentSerializationService serializationService))
{
_serializedDataForDropDownItems = serializationService.CreateStore();
foreach (ToolStripItem item in MenuItem.DropDownItems)
{
// Don't Serialize the DesignerToolStripControlHost...
if (item is not DesignerToolStripControlHost)
{
serializationService.Serialize(_serializedDataForDropDownItems, item);
}
}
// close the SerializationStore to Serialize Items..
_serializedDataForDropDownItems.Close();
}
ToolStripItem[] items = new ToolStripItem[MenuItem.DropDownItems.Count];
MenuItem.DropDownItems.CopyTo(items, 0);
foreach (ToolStripItem item in items)
{
MenuItem.DropDownItems.Remove(item);
_designerHost.DestroyComponent(item);
}
}
MenuItem.HideDropDown();
}
// Set The DropDown
MenuItem.DropDown = value;
// Set the Shadow Property
_customDropDown = value;
// If DropDown is Set to null and we have valid serializedData, then use it to recreate Items.
if (value is null && !_dropDownSet && _serializedDataForDropDownItems is not null)
{
try
{
// turn off Adding/Added events listened to by the ToolStripDesigner...
ToolStripDesigner.s_autoAddNewItems = false;
CreatetypeHereNode();
if (TryGetService(out ComponentSerializationService serializationService))
{
serializationService.Deserialize(_serializedDataForDropDownItems);
_serializedDataForDropDownItems = null;
}
}
finally
{
// turn on Adding/Added events listened to by the ToolStripDesigner...
ToolStripDesigner.s_autoAddNewItems = true;
}
}
MenuItem.DropDown.OwnerItem = MenuItem;
MenuItem.DropDown.Disposed += OnDropDownDisposed;
// Show the drop down.
if (MenuItem.Equals(_selectionService.PrimarySelection))
{
// Add TypeHereNode & show the Dropdown
InitializeDropDown();
}
}
}
/// <summary>
/// ToolStripEditorManager used this internal property to
/// Activate the editor.
/// </summary>
internal override ToolStripTemplateNode Editor
{
get
{
if (base.Editor is not null)
{
return base.Editor;
}
if (_commitedTemplateNode is not null)
{
return _commitedTemplateNode;
}
return _typeHereTemplateNode;
}
set => _commitedTemplateNode = value;
}
/// <summary>
/// True if the MenuItem is on ContextMenu.
/// </summary>
private bool IsOnContextMenu
{
get
{
ToolStrip mainStrip = GetMainToolStrip();
if (mainStrip is not null && mainStrip.Site is not null && !(mainStrip is ContextMenuStrip))
{
return false;
}
return true;
}
}
/// <summary>
/// Easy method for getting to the ToolStripDropDownItem
/// </summary>
private ToolStripDropDownItem MenuItem
{
get => ToolStripItem as ToolStripDropDownItem;
}
/// <summary>
/// This property is true when the OwnerItem is selected during COPY and PASTE operations.
/// </summary>
private bool MenuItemSelected
{
get
{
// check to see if the primary selection is the ToolStrip or one of it's children.
if (_selectionService is not null)
{
object selectedItem = _selectionService.PrimarySelection;
ToolStripItem toolItem;
if (selectedItem is null)
{
if (KeyboardHandlingService is not null)
{
selectedItem = KeyboardHandlingService.SelectedDesignerControl;
}
toolItem = selectedItem as ToolStripItem;
}
else
{
toolItem = selectedItem as ToolStripItem;
}
if (toolItem is not null)
{
// We always need to return the current selection if we are adding a DummyNode...
if (_designerHost is not null)
{
if (_designerHost.GetDesigner(toolItem) is ToolStripItemDesigner designer)
{
if (designer._dummyItemAdded)
{
return (toolItem == MenuItem);
}
}
}
// We need to return parent if we are on DropDown...
if (toolItem.IsOnDropDown && toolItem.Owner is ToolStripDropDown dropDown)
{
ToolStripDropDownItem parentItem = dropDown.OwnerItem as ToolStripDropDownItem;
return (parentItem == MenuItem);
}
else
{
return (toolItem == MenuItem);
}
}
else if (selectedItem is ContextMenuStrip menuStrip
// VSO 214130--SelectedItem must belong to this designer
&& menuStrip.OwnerItem == Component
&& MenuItem.DropDown == selectedItem)
{
return true;
}
}
return false;
}
}
private ToolStripKeyboardHandlingService KeyboardHandlingService
=> _keyboardHandlingService ??= GetService<ToolStripKeyboardHandlingService>();
/// <summary>
/// ParentComponent in case of MenuItems on the DropDown is the OwnerItem of the DropDown and
/// not the ToolStripDropDown (since the DropDowns are not sited).
/// </summary>
protected override IComponent ParentComponent
{
get
{
if (ToolStripItem is not null)
{
if (!ToolStripItem.IsOnOverflow && ToolStripItem.IsOnDropDown)
{
// If the dropDown is NOT AutoGenerated then its a dropdown the user has set or its a
// contextMenuStrip/ToolStripDropDownMenu/ToolStripDropDown that has been added from the ToolBox.
// In such a case don't return the ownerItem but the DropDown itself as the ParentComponent.
if (MenuItem.Owner is ToolStripDropDown dropDown)
{
if (dropDown.IsAutoGenerated)
{
return dropDown.OwnerItem;
}
else
{
return dropDown;
}
}
}
return base.ParentComponent;
}
return null;
}
}
private IComponentChangeService ComponentChangeService => _componentChangeService ??= GetService<IComponentChangeService>();
/// <summary>
/// Adds the dummy node for InSitu Edit.
/// </summary>
internal void AddNewTemplateNode(ToolStripDropDown dropDown)
{
// Check if the DropDown contains a typeHereNode....
foreach (ToolStripItem currentItem in dropDown.Items)
{
if (currentItem is DesignerToolStripControlHost host)
{
_typeHereNode = host;
}
}
if (_typeHereNode is not null)
{
dropDown.Items.Remove(_typeHereNode);
}
// setup the MINIToolStrip host...
_typeHereTemplateNode = new ToolStripTemplateNode(Component, SR.ToolStripDesignerTemplateNodeEnterText);
int width = _typeHereTemplateNode.EditorToolStrip.Width;
_typeHereNode = new DesignerToolStripControlHost(_typeHereTemplateNode.EditorToolStrip);
_typeHereTemplateNode.ControlHost = _typeHereNode;
_typeHereNode.AutoSize = false;
_typeHereNode.Width = width;
dropDown.Items.Add(_typeHereNode);
}
// used by morphing in ToolStripItemDesigner ... hence internal method.
internal void AddItemBodyGlyph(ToolStripItem item)
{
if (item is not null)
{
ToolStripItemDesigner dropDownItemDesigner = (ToolStripItemDesigner)_designerHost.GetDesigner(item);
if (dropDownItemDesigner is not null)
{
Rectangle bounds = dropDownItemDesigner.GetGlyphBounds();
Behavior.Behavior toolStripBehavior = new ToolStripItemBehavior();
// Initialize Glyph
ToolStripItemGlyph bodyGlyphForddItem = new(item, dropDownItemDesigner, bounds, toolStripBehavior);
// Set the glyph for the item .. so that we can remove it later....
dropDownItemDesigner._bodyGlyph = bodyGlyphForddItem;
// Add ItemGlyph to the Collection
_toolStripAdornerWindowService?.DropDownAdorner.Glyphs.Insert(0, bodyGlyphForddItem);
}
}
}
// Add individual item Body Glyphs
private void AddBodyGlyphs(ToolStripDropDownItem item)
{
if (item is not null)
{
ToolStripMenuItemDesigner itemDesigner = (ToolStripMenuItemDesigner)_designerHost.GetDesigner(item);
if (itemDesigner is not null)
{
foreach (ToolStripItem ddItem in item.DropDownItems)
{
AddItemBodyGlyph(ddItem);
}
}
}
}
/// <summary>
/// This is called by the TemplateNode to Commit the Edit. This function creates a NEW ToolStripDropDownItem
/// if we are committing a dummy node or else just replaces the TEXT and IMAGE if we are changing
/// an existing MenuItem.
/// </summary>
internal override void CommitEdit(Type type, string text, bool commit, bool enterKeyPressed, bool tabKeyPressed)
{
IsEditorActive = false;
if (!(MenuItem.Owner is ToolStripDropDown) && base.Editor is not null) // we are on Base MenuStrip !!
{
base.CommitEdit(type, text, commit, enterKeyPressed, tabKeyPressed);
}
else if (commit)
{
bool dummyItem = _dummyItemAdded;
_dummyItemAdded = false;
MenuItem.DropDown.SuspendLayout();
int index;
if (_commitedEditorNode is not null)
{
// This means we have a valid node and we just changed some properties.
index = MenuItem.DropDownItems.IndexOf(_commitedEditorNode);
ToolStripItem editedItem = MenuItem.DropDownItems[index + 1];
// Remove TemplateNode
MenuItem.DropDown.Items.Remove(_commitedEditorNode);
// Get rid of the templateNode.
_commitedTemplateNode?.CloseEditor();
_commitedTemplateNode = null;
_commitedEditorNode.Dispose();
_commitedEditorNode = null;
// If we have type "-" this means the user wants to add a Separator.
if (text == "-")
{
if (_designerHost.GetDesigner(editedItem) is ToolStripItemDesigner itemDesigner)
{
try
{
editedItem = itemDesigner.MorphCurrentItem(typeof(ToolStripSeparator));
// Remove the ActionGlyph Added by morphing
RemoveItemBodyGlyph(editedItem);
}
catch
{
if (_newMenuItemTransaction is not null)
{
try
{
_newMenuItemTransaction.Cancel();
}
catch // This will cause ROLLBACK and hence throw if the project is already in DEBUG and instuEdit was Active.
{
}
_newMenuItemTransaction = null;
}
}
finally
{
if (_newMenuItemTransaction is not null)
{
_newMenuItemTransaction.Commit();
_newMenuItemTransaction = null;
}
}
}
}
else
{
// We are adding the item through InSitu for the first time
if (dummyItem)
{
try
{
_dummyItemAdded = true;
CreateNewItem(type, index, text);
_designerHost.DestroyComponent(editedItem);
// One place where we need to call this explicitly since the selection doesn't change.
if (enterKeyPressed)
{
_typeHereNode.SelectControl();
}
}
catch
{
if (_newMenuItemTransaction is not null)
{
try
{
_newMenuItemTransaction.Cancel();
}
catch // This will cause ROLLBACK and hence throw if the project is already in DEBUG and instuEdit was Active.
{
}
_newMenuItemTransaction = null;
}
}
finally
{
if (_newMenuItemTransaction is not null)
{
_newMenuItemTransaction.Commit();
_newMenuItemTransaction = null;
}
_dummyItemAdded = false;
}
}
else // We are editing item that was already present
{
// put the item back...
editedItem.Visible = true;
// create our transaction
DesignerTransaction designerTransaction = _designerHost.CreateTransaction(SR.ToolStripItemPropertyChangeTransaction);
try
{
// Change the properties
PropertyDescriptor textProp = TypeDescriptor.GetProperties(editedItem)["Text"];
string oldValue = (string)textProp.GetValue(editedItem);
if (textProp is not null && text != oldValue)
{
textProp.SetValue(editedItem, text);
}
}
catch
{
if (designerTransaction is not null)
{
designerTransaction.Cancel();
designerTransaction = null;
}
}
finally
{
designerTransaction?.Commit();
}
}
}
}
else
{
// if committedEditorNode is null and we are in commitEdit then just add the new Item, since this item is added through dropDown.
// if no editorNode then we are committing a NEW NODE...
index = MenuItem.DropDownItems.IndexOf(_typeHereNode);
try
{
_dummyItemAdded = true;
CreateNewItem(type, index, text);
}
finally
{
_dummyItemAdded = false;
}
// One place where we need to call this explicitly since the selection doesn't change.
_typeHereNode.SelectControl();
}
// Invalidate DropDown..
MenuItem.DropDown.ResumeLayout(true);
MenuItem.DropDown.PerformLayout();
// Reset the Glyphs after Item addition...
ResetGlyphs(MenuItem);
// set the selection to our new item only if item was committed via ENTER KEY in the InSitu Mode.
if (_selectionService is not null)
{
if (enterKeyPressed)
{
ToolStripItem nextItem = (MenuItem.DropDownDirection == ToolStripDropDownDirection.AboveLeft ||
MenuItem.DropDownDirection == ToolStripDropDownDirection.AboveRight) && index >= 1
? MenuItem.DropDownItems[index - 1]
: MenuItem.DropDownItems[index + 1];
if (KeyboardHandlingService is not null)
{
if (nextItem is not null)
{
if (MenuItem.DropDownItems[index] is ToolStripDropDownItem currentItem)
{
currentItem.HideDropDown();
}
}
if (nextItem == _typeHereNode)
{
// Put Selection on the TypeHereNode.
KeyboardHandlingService.SelectedDesignerControl = nextItem;
_selectionService.SetSelectedComponents(null, SelectionTypes.Replace);
}
else
{
KeyboardHandlingService.SelectedDesignerControl = null;
_selectionService.SetSelectedComponents(new object[] { nextItem });
}
}
}
else if (tabKeyPressed) // just select this the new Item
{
_selectionService.SetSelectedComponents(new object[] { MenuItem.DropDownItems[index] }, SelectionTypes.Replace);
}
}
}
else
{
// We come here if we have not committed so revert our state.
if (_commitedEditorNode is not null)
{
// We just changed some properties which we want to revert.
MenuItem.DropDown.SuspendLayout();
bool dummyItem = _dummyItemAdded;
_dummyItemAdded = false;
int index = MenuItem.DropDownItems.IndexOf(_commitedEditorNode);
ToolStripItem editedItem = MenuItem.DropDownItems[index + 1];
MenuItem.DropDown.Items.Remove(_commitedEditorNode);
// put the item back...
editedItem.Visible = true;
if (_commitedTemplateNode is not null)
{
_commitedTemplateNode.CloseEditor();
_commitedTemplateNode = null;
}
if (_commitedEditorNode is not null)
{
_commitedEditorNode.Dispose();
_commitedEditorNode = null;
}
if (dummyItem)
{
MenuItem.DropDownItems.Remove(editedItem);
try
{
_designerHost.DestroyComponent(editedItem);
}
catch
{
if (_newMenuItemTransaction is not null)
{
try
{
_newMenuItemTransaction.Cancel();
}
catch // This will cause ROLLBACK and hence throw if the project is already in DEBUG and instuEdit was Active.
{
}
_newMenuItemTransaction = null;
}
}
editedItem = null;
}
// Required to do here...
MenuItem.DropDown.ResumeLayout();
// Remove the Glyphs so that Mouse Message go to the Editor
if (editedItem is not null)
{
AddItemBodyGlyph(editedItem);
}
if (dummyItem)
{
// Since the operation is cancelled and there is no change of glyphs,
// set SelectionManager.NeedRefresh to false so that it doesn't refresh,
// when the transaction is cancelled.
GetService<SelectionManager>().NeedRefresh = false;
if (_newMenuItemTransaction is not null)
{
try
{
_dummyItemAdded = true;
_newMenuItemTransaction.Cancel();
_newMenuItemTransaction = null;
if (MenuItem.DropDownItems.Count == 0)
{
CreatetypeHereNode();
}
}
finally
{
_dummyItemAdded = false;
}
}
}
// Added for Separators...
MenuItem.DropDown.PerformLayout();
}
}
}
/// <summary>
/// Creates the dummy node for InSitu Edit.
/// </summary>
private void CreatetypeHereNode()
{
if (_typeHereNode is null)
{
AddNewTemplateNode(MenuItem.DropDown);
// Add Text for Debugging Non Sited DropDown..
if (MenuItem.DropDown.Site is null)
{
MenuItem.DropDown.Text = $"{MenuItem.Name}.DropDown";
}
}
else if (_typeHereNode is not null && MenuItem.DropDownItems.IndexOf(_typeHereNode) == -1)
{
MenuItem.DropDown.Items.Add(_typeHereNode);
_typeHereNode.Visible = true;
}
// Invalidate DropDown..
MenuItem.DropDown.PerformLayout();
}
/// <summary>
/// This Function is called by EnterInSituEdit where in we Swap the typeHereNode by
/// the TemplateNode (the InSitu Editor). Since the TemplateNode had a EditorToolStrip we can just HOST
/// that ToolStrip as a ToolStripControlHost and add it to the DropDown.
/// </summary>
private void CreateDummyMenuItem(ToolStripItem item, string text)
{
_commitedTemplateNode = new ToolStripTemplateNode(Component, text)
{
ActiveItem = item
};
int width = _commitedTemplateNode.EditorToolStrip.Width;
_commitedEditorNode = new DesignerToolStripControlHost(_commitedTemplateNode.EditorToolStrip)
{
AutoSize = false,
Width = width
};
}
/// <summary>
/// This helper function creates a dummyItem for InSitu editing.
/// </summary>
private ToolStripItem CreateDummyItem(Type t, int dummyIndex)
{
if (_designerHost is null)
{
Debug.Fail("Couldn't get designer host!");
return null;
}
ToolStripItem newItem = null;
// For the "Upward DropDown" add at index +1...
if (MenuItem.DropDownDirection is ToolStripDropDownDirection.AboveLeft or ToolStripDropDownDirection.AboveRight)
{
dummyIndex++;
}
try
{
// turn off Adding/Added events listened to by the ToolStripDesigner...
ToolStripDesigner.s_autoAddNewItems = false;
// Store the index into a class level member so that the componentChanged can access it
_indexToInsertNewItem = dummyIndex;
try
{
// create our transaction
_newMenuItemTransaction ??= _designerHost.CreateTransaction(SR.ToolStripCreatingNewItemTransaction);
_fireComponentChanged = true;
newItem = (ToolStripItem)_designerHost.CreateComponent(t);
}
finally
{
_fireComponentChanged = false;
}
ToolStripItemDesigner designer = _designerHost.GetDesigner(newItem) as ToolStripItemDesigner;
try
{
// ToolStripItem designer tries to set the TEXT for the item in the InitializeNewComponent().
// But since we are create item thru InSitu .. we shouldn't do this.
designer.InternalCreate = true;
designer.InitializeNewComponent(null);
}
finally
{
designer.InternalCreate = false;
}
}
catch (InvalidOperationException ex)
{
CommitInsertTransaction(commit: false);
if (_newMenuItemTransaction is not null)
{
_newMenuItemTransaction.Cancel();
_newMenuItemTransaction = null;
}
GetService<IUIService>().ShowError(ex.Message);
}
finally
{
// turn on Adding/Added events listened to by the ToolStripDesigner...
ToolStripDesigner.s_autoAddNewItems = true;
// Reset the index
_indexToInsertNewItem = -1;
}
return newItem;
}
/// <summary>
/// Asks the host to create a new DropDownItem, inserts the item into the collection and selects it.
/// </summary>
private ToolStripItem CreateNewItem(Type t, int dummyIndex, string newText)
{
if (_designerHost is null)
{
Debug.Fail("Couldn't get designer host!");
return null;
}
ToolStripItem newItem = null;
// For the "Upward DropDown" add at index +1...
if (MenuItem.DropDownDirection is ToolStripDropDownDirection.AboveLeft or ToolStripDropDownDirection.AboveRight)
{
dummyIndex++;
}
// create our transaction
DesignerTransaction outerTransaction = _designerHost.CreateTransaction(SR.ToolStripCreatingNewItemTransaction);
try
{
// turn off Adding/Added events listened to by the ToolStripDesigner...
ToolStripDesigner.s_autoAddNewItems = false;
// Store the index into a class level member so that the componentChanged can access it
_indexToInsertNewItem = dummyIndex;
try
{
_fireComponentChanged = true;
newItem = (ToolStripItem)_designerHost.CreateComponent(t, ToolStripDesigner.NameFromText(newText, t, MenuItem.Site));
}
finally
{
_fireComponentChanged = false;
}
ToolStripItemDesigner designer = _designerHost.GetDesigner(newItem) as ToolStripItemDesigner;
try
{
// ToolStripItem designer tries to set the TEXT for the item in the InitializeNewComponent().
// But since we are create item thru InSitu .. we shouldn't do this.
if (!string.IsNullOrEmpty(newText) || _addingDummyItem)
{
designer.InternalCreate = true;
}
designer?.InitializeNewComponent(null);
}
finally
{
designer.InternalCreate = false;
}
// Set the Text and Image..
if (newItem is not null)
{
PropertyDescriptor textProperty = TypeDescriptor.GetProperties(newItem)["Text"];
Debug.Assert(textProperty is not null, "Could not find 'Text' property in ToolStripItem.");
if (textProperty is not null && !string.IsNullOrEmpty(newText))
{
textProperty.SetValue(newItem, newText);
}
}
}
catch
{
// There might be scenarios where the ComponentAdding is fired but the Added doesn't get fired.
// Is such cases the InsertTransaction might be still active... So we need to cancel that too here.
CommitInsertTransaction(false);
if (outerTransaction is not null)
{
outerTransaction.Cancel();
outerTransaction = null;
}
}
finally
{
outerTransaction?.Commit();
// turn on Adding/Added events listened to by the ToolStripDesigner...
ToolStripDesigner.s_autoAddNewItems = true;
// Reset the index
_indexToInsertNewItem = -1;
}
return newItem;
}
/// <summary>
/// Helper function to find whether the passed in DropDownItems have same owner.
/// </summary>
private static bool CheckSameOwner(ToolStripDropDownItem lastSelected, ToolStripDropDownItem currentSelected)
{
if (lastSelected is not null && currentSelected is not null)
{
if (lastSelected.Owner is ToolStripDropDown lastDropDown && currentSelected.Owner is ToolStripDropDown currentDropDown)
{
ToolStripItem ownerLastSelected = lastDropDown.OwnerItem;
ToolStripItem ownerCurrentSelected = currentDropDown.OwnerItem;
return (ownerLastSelected == ownerCurrentSelected);
}
}
return false;
}
// internal method to commit any node.
internal void Commit()
{
if (_commitedTemplateNode is not null && _commitedTemplateNode.Active)
{
// Get Index of the CommittedItem..
int index = MenuItem.DropDownItems.IndexOf(_commitedEditorNode);
_commitedTemplateNode.Commit(false, false);
if (index != -1 && MenuItem.DropDownItems.Count > index)
{
if (MenuItem.DropDownItems[index] is ToolStripDropDownItem newItem)
{
newItem.HideDropDown();
}
}
}
else if (_typeHereTemplateNode is not null && _typeHereTemplateNode.Active)
{
_typeHereTemplateNode.Commit(false, false);
}
// COMMIT ALL THE THE PARENT CHAIN ....
ToolStripDropDownItem currentItem = MenuItem;
while (currentItem?.Owner is ToolStripDropDown dropDown)
{
currentItem = (ToolStripDropDownItem)dropDown.OwnerItem;
if (currentItem is not null)
{
ToolStripMenuItemDesigner itemDesigner = (ToolStripMenuItemDesigner)_designerHost.GetDesigner(currentItem);
itemDesigner?.Commit();
}
}
}
/// <summary>
/// Disposes of this designer.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
// clean up
if (_selectionService is not null)
{
_selectionService.SelectionChanged -= OnSelectionChanged;
}
if (_undoEngine is not null)
{
_undoEngine.Undoing -= OnUndoing;
_undoEngine.Undone -= OnUndone;
}
if (MenuItem is not null && MenuItem.HasDropDown)
{
MenuItem.DropDown.Hide();
UnHookEvents();
}
if (ComponentChangeService is not null)
{
ComponentChangeService.ComponentAdding -= ComponentChangeSvc_ComponentAdding;
ComponentChangeService.ComponentAdded -= ComponentChangeSvc_ComponentAdded;
ComponentChangeService.ComponentRemoving -= ComponentChangeSvc_ComponentRemoving;
ComponentChangeService.ComponentRemoved -= ComponentChangeSvc_ComponentRemoved;
}
if (_toolStripAdornerWindowService is not null)
{
_toolStripAdornerWindowService = null;
}
if (_typeHereTemplateNode is not null)
{
_typeHereTemplateNode.RollBack();
_typeHereTemplateNode.CloseEditor();
_typeHereTemplateNode = null;
}
if (_typeHereNode is not null)
{
_typeHereNode.Dispose();
_typeHereNode = null;
}
if (_commitedTemplateNode is not null)
{
_commitedTemplateNode.RollBack();
_commitedTemplateNode.CloseEditor();
_commitedTemplateNode = null;
}
if (_commitedEditorNode is not null)
{
_commitedEditorNode.Dispose();
_commitedEditorNode = null;
}
if (_parentItem is not null)
{
_parentItem = null;
}
}
base.Dispose(disposing);
}
// When the dropDown is clicked; Commit any active InSitu node.
private void DropDownClick(object sender, EventArgs e)
{
// Commit any InsituEdit Node.
if (KeyboardHandlingService is not null && KeyboardHandlingService.TemplateNodeActive)
{
// If templateNode Active .. commit and Select it
KeyboardHandlingService.ActiveTemplateNode.CommitAndSelect();
}
}
// re-paint required to "validate" the glyphs
private void DropDownPaint(object sender, PaintEventArgs e)
{
// Select All requires the repaint of glyphs after the DropDown receives paint message.
if (_selectionService is not null && MenuItem is not null)
{
foreach (ToolStripItem item in MenuItem.DropDownItems)
{
if (item.Visible && _selectionService.GetComponentSelected(item))
{
if (_designerHost.GetDesigner(item) is ToolStripItemDesigner designer)
{
Rectangle r = designer.GetGlyphBounds();
ToolStripDesignerUtils.GetAdjustedBounds(item, ref r);
r.Inflate(GLYPHINSET, GLYPHINSET);
// This will allow any Glyphs to re-paint after this control and its designer has painted
GetService<BehaviorService>()?.ProcessPaintMessage(r);
}
}
// When you alt-tab from VS to any other application and come back and if the dropDown is open;
// then the templateNode doesn't paint. This happens when you have another control below the dropDown
// which paints over the controlhost so we need to refresh the TemplateNode in this case.
if (item is DesignerToolStripControlHost controlHost)
{
controlHost.Control.Refresh();
}
}
}
}
// Invalidate the BehaviorService if the location changed.
private void DropDownLocationChanged(object sender, EventArgs e)
{
// this shouldn't get fired many times.. only in certain case...
// but in those cases its needed to REFRESH THE ENTIRE adornerWindow.
ToolStripDropDown dropDown = sender as ToolStripDropDown;
if (dropDown.Visible)
{
GetService<BehaviorService>()?.Invalidate();
}
}
// Change the parent when the DropDown is opening.
private void DropDownItem_DropDownOpening(object sender, EventArgs e)
{
ToolStripDropDownItem ddi = sender as ToolStripDropDownItem;
if (_toolStripAdornerWindowService is not null)
{
ddi.DropDown.TopLevel = false;
ddi.DropDown.Parent = _toolStripAdornerWindowService.ToolStripAdornerWindowControl;
}
}
// Add the DropDownGlyph when the dropDown is opened.
private void DropDownItem_DropDownOpened(object sender, EventArgs e)
{
// Add Glyphs...
ToolStripDropDownItem ddi = sender as ToolStripDropDownItem;
if (ddi is not null)
{
ResetGlyphs(ddi);
}
// finally add Glyph for the "DropDown"
Control rootControl = ddi.DropDown;
if (rootControl is not null)
{
if (_designerHost.GetDesigner(_designerHost.RootComponent) is ControlDesigner designer)
{
_rootControlGlyph = new ToolStripDropDownGlyph(rootControl.Bounds, new DropDownBehavior(designer, this));
}
_toolStripAdornerWindowService?.DropDownAdorner.Glyphs.Add(_rootControlGlyph);
}
}
// Remove the dropDownGlyph after the dropDown is closed.
private void DropDownItem_DropDownClosed(object sender, EventArgs e)
{
if (sender is ToolStripDropDownItem ddi)
{
// Invalidate the ToolStripWindow.... for clearing the dropDowns
if (_toolStripAdornerWindowService is not null)
{
if (_rootControlGlyph is not null && _toolStripAdornerWindowService.DropDownAdorner.Glyphs.Contains(_rootControlGlyph))
{
_toolStripAdornerWindowService.DropDownAdorner.Glyphs.Remove(_rootControlGlyph);
}
}
// Remove body Glyphs...
InitializeBodyGlyphsForItems(false /*remove*/, ddi);
// Unhook all the Events
_initialized = false;
UnHookEvents();
// Check if this is a Sited-DropDown
if (ddi.DropDown.Site is not null || ddi.DropDownItems.Count == 1)
{
// Get Designer ... and call Remove on that Designer.
RemoveTypeHereNode(ddi);
}
else
{
_toolStripAdornerWindowService?.Invalidate(ddi.DropDown.Bounds);
}
}
}
// invalidate the AdornerWindow when the DropDown resizes
private void DropDownResize(object sender, EventArgs e)
{
ToolStripDropDown dropDown = sender as ToolStripDropDown;
if (!_dummyItemAdded)
{
if (dropDown is not null && dropDown.Visible)
{
// Invalidate only if new Size is LESS than old Size...
if (_toolStripAdornerWindowService is not null && (dropDown.Width < _dropDownSizeToInvalidate.Width || dropDown.Size.Height < _dropDownSizeToInvalidate.Height))
{
using Region invalidatingRegion = new(_dropDownSizeToInvalidate);
invalidatingRegion.Exclude(dropDown.Bounds);
_toolStripAdornerWindowService.Invalidate(invalidatingRegion);
// Invalidate BehaviorService AdornerWindow as well... but only the DropDownBounds
GetService<BehaviorService>()?.Invalidate(invalidatingRegion);
}
}
if (_toolStripAdornerWindowService is not null)
{
if (_rootControlGlyph is not null && _toolStripAdornerWindowService.DropDownAdorner.Glyphs.Contains(_rootControlGlyph))
{
_toolStripAdornerWindowService.DropDownAdorner.Glyphs.Remove(_rootControlGlyph);
}
if (_designerHost.GetDesigner(_designerHost.RootComponent) is ControlDesigner designer)
{
_rootControlGlyph = new ToolStripDropDownGlyph(dropDown.Bounds, new DropDownBehavior(designer, this));
}
_toolStripAdornerWindowService.DropDownAdorner.Glyphs.Add(_rootControlGlyph);
}
}
_dropDownSizeToInvalidate = dropDown.Bounds;
}
/// <summary>
/// Called when a menuItem wants to go into InSitu Editing Mode.
/// </summary>
internal void EditTemplateNode(bool clicked)
{
// If the parent has a window which is too small, there won't be any space to draw the entry box and typeHereNode will be null
if (_typeHereNode is null)
{
return;
}
// Refresh the state of the 'TypeHere' node to NotSelected state
_typeHereNode.RefreshSelectionGlyph();
// Commit any InsituEdit Node.
if (KeyboardHandlingService is not null && KeyboardHandlingService.TemplateNodeActive)
{
// If templateNode Active .. commit and Select it
KeyboardHandlingService.ActiveTemplateNode.CommitAndSelect();
}
// commit any existing InSitu editor...
if (clicked)
{
// We should come here for a valid parent !!!
if (MenuItem is null)
{
return;
}
}
// always select the parent item... but don't redraw the control during this Selection Change as this causes flicker
try
{
ToolStripDesigner.s_editTemplateNode = true;
_selectionService.SetSelectedComponents(new object[] { MenuItem }, SelectionTypes.Replace);
}
finally
{
ToolStripDesigner.s_editTemplateNode = false;
}
// Hide the DropDown of any previous Selection.. First get the SelectedItem...
ToolStripDropDownItem selectedItem = null;
if (_selectionService.PrimarySelection is null && KeyboardHandlingService is not null)
{
if (KeyboardHandlingService.SelectedDesignerControl is ToolStripItem item)
{
selectedItem = ((ToolStripDropDown)item.Owner).OwnerItem as ToolStripDropDownItem;
}
}
else
{
selectedItem = _selectionService.PrimarySelection as ToolStripDropDownItem;
}
// Now Hide the DropDown and Refresh Glyphs...
if (selectedItem is not null && selectedItem != MenuItem)
{
HideSiblingDropDowns(selectedItem);
}
MenuItem.DropDown.SuspendLayout();
_dummyItemAdded = true;
int index = MenuItem.DropDownItems.IndexOf(_typeHereNode);
ToolStripItem newDummyItem = null;
try
{
_addingDummyItem = true;
newDummyItem = CreateDummyItem(typeof(ToolStripMenuItem), index);
}
catch (CheckoutException checkoutException)
{
if (checkoutException.Equals(CheckoutException.Canceled))
{
CommitInsertTransaction(commit: false);
if (_newMenuItemTransaction is not null)
{
_newMenuItemTransaction.Cancel();
_newMenuItemTransaction = null;
}
}
else
{
throw;
}
}
finally
{
_dummyItemAdded = (newDummyItem is not null);
_addingDummyItem = false;
}
MenuItem.DropDown.ResumeLayout();
if (newDummyItem is not null)
{
if (_designerHost.GetDesigner(newDummyItem) is ToolStripMenuItemDesigner newItemDesigner)
{
newItemDesigner.InitializeDropDown();
newItemDesigner.ShowEditNode(clicked);
}
}
}
/// <summary>
/// Called from OnDoubleClickTimerTick to Enter in InSituMode
/// </summary>
private void EnterInSituMode()
{
// we need to tell our parent that we want to enter InSitu edit mode
if (MenuItem.Owner is ToolStripDropDown dropDown)
{
ToolStripItem ownerItem = dropDown.OwnerItem;
// need to inform the owner tha we want to enter InSitu mode
if (_designerHost is not null)
{
IDesigner designer = _designerHost.GetDesigner(ownerItem);
if (designer is ToolStripMenuItemDesigner menuItemDesigner)
{
// Need to Add Dummy Node For Direct InSitu..
MenuItem.HideDropDown();
menuItemDesigner.EnterInSituEdit(MenuItem);
}
}
}
}
/// <summary>
/// This method replaces the menItem with an in-situ TemplateNode.
/// </summary>
internal void EnterInSituEdit(ToolStripItem toolItem)
{
MenuItem.DropDown.SuspendLayout();
// Remove the Glyphs so that Mouse Message go to the Editor
RemoveItemBodyGlyph(toolItem);
if (toolItem is null)
{
return;
}
// we can only one Active Editor at one time.
if (IsEditorActive)
{
return;
}
CreateDummyMenuItem(toolItem, toolItem.Text);
int index = MenuItem.DropDownItems.IndexOf(toolItem);
// swap in our InSitu ToolStrip
MenuItem.DropDownItems.Insert(index, _commitedEditorNode);
if (toolItem is ToolStripControlHost host)
{
host.Control.Visible = false;
}
toolItem.Visible = false;
MenuItem.DropDown.ResumeLayout();
// Try Focusing the TextBox....
_commitedTemplateNode?.FocusEditor(toolItem);
ToolStripDropDownItem dropDownItem = toolItem as ToolStripDropDownItem;
if (!(dropDownItem.Owner is ToolStripDropDownMenu) && dropDownItem is not null && dropDownItem.Bounds.Width < _commitedEditorNode.Bounds.Width)
{
dropDownItem.Width = _commitedEditorNode.Width;
dropDownItem.DropDown.Location = new Point(dropDownItem.DropDown.Location.X + _commitedEditorNode.Bounds.Width - dropDownItem.Bounds.Width, dropDownItem.DropDown.Location.Y);
}
IsEditorActive = true;
}
/// <summary>
/// Get the Insertion Index to drop the current drag-drop item.
/// </summary>
private static int GetItemInsertionIndex(ToolStripDropDown wb, Point ownerClientAreaRelativeDropPoint)
{
for (int i = 0; i < wb.Items.Count; i++)
{
Rectangle bounds = wb.Items[i].Bounds;
bounds.Inflate(wb.Items[i].Margin.Size);
if (bounds.Contains(ownerClientAreaRelativeDropPoint))
{
return wb.Items.IndexOf(wb.Items[i]);
}
}
return -1;
}
/// <summary>
/// Returns the DropDown for this MenuItem else returns the Parent(Owner).
/// </summary>
protected override Component GetOwnerForActionList()
{
return MenuItem;
}
// Helper Function to get the Main ToolStrip (MenuStrip);
internal override ToolStrip GetMainToolStrip()
{
ToolStripDropDown topmost = GetFirstDropDown(MenuItem);
ToolStripItem topMostItem = topmost?.OwnerItem;
if (topMostItem is not null)
{
return topMostItem.Owner;
}
return MenuItem.Owner;
}
/// <summary>
/// Helper function to Hide the Active Dropdown from the given DropDownItem.
/// </summary>
private void HideAllDropDowns(ToolStripDropDownItem item)
{
try
{
if (MenuItem.Owner is ToolStripDropDown dropDown)
{
ToolStripItem ownerItem = dropDown.OwnerItem;
while (item != ownerItem)
{
if (item.DropDown.Visible)
{
item.HideDropDown();
}
if (item.Owner is ToolStripDropDown itemDropDown)
{
item = (ToolStripDropDownItem)itemDropDown.OwnerItem;
}
else
{
return;
}
}
}
}
catch (Exception e) when (!e.IsCriticalException())
{
}
}
/// <summary>
/// Helper function to Hide the Active Dropdown for all the siblings of the given DropDownItem.
/// </summary>
private void HideSiblingDropDowns(ToolStripDropDownItem item)
{
try
{
ToolStripItem ownerItem = MenuItem;
while (item != ownerItem)
{
item.HideDropDown();
if (item.Owner is ToolStripDropDown dropDown)
{
item = (ToolStripDropDownItem)dropDown.OwnerItem;
}
else
{
return;
}
}
}
catch (Exception e) when (!e.IsCriticalException())
{
}
}
/// <summary>
/// This will listen to the necessary dropDown events.
/// Now we add the events on selection and unhook when the dropDown is closed.
/// </summary>
internal void HookEvents()
{
if (MenuItem is not null)
{
MenuItem.DropDown.Closing += OnDropDownClosing;
MenuItem.DropDownOpening += DropDownItem_DropDownOpening;
MenuItem.DropDownOpened += DropDownItem_DropDownOpened;
MenuItem.DropDownClosed += DropDownItem_DropDownClosed;
MenuItem.DropDown.Resize += DropDownResize;
MenuItem.DropDown.ItemAdded += OnItemAdded;
MenuItem.DropDown.Paint += DropDownPaint;
MenuItem.DropDown.Click += DropDownClick;
MenuItem.DropDown.LocationChanged += DropDownLocationChanged;
}
}
/// <summary>
/// Initializes the ToolStripDropDownItem Designer.
/// </summary>
public override void Initialize(IComponent component)
{
base.Initialize(component);
// Initialize the properties we will be shadowing
Visible = true;
DoubleClickEnabled = MenuItem.DoubleClickEnabled;
// Hook our services
if (TryGetService(out _selectionService))
{
_selectionService.SelectionChanged += OnSelectionChanged;
}
// Hookup to the AdornerService
_toolStripAdornerWindowService = GetService<ToolStripAdornerWindowService>();
_designerHost = GetService<IDesignerHost>();
// Set the DoubleClickEnabled
MenuItem.DoubleClickEnabled = true;
if (ComponentChangeService is not null)
{
ComponentChangeService.ComponentAdding += ComponentChangeSvc_ComponentAdding;
ComponentChangeService.ComponentAdded += ComponentChangeSvc_ComponentAdded;
ComponentChangeService.ComponentRemoving += ComponentChangeSvc_ComponentRemoving;
ComponentChangeService.ComponentRemoved += ComponentChangeSvc_ComponentRemoved;
}
if (_undoEngine is null && TryGetService(out _undoEngine))
{
_undoEngine.Undoing += OnUndoing;
_undoEngine.Undone += OnUndone;
}
}
// internal since the Behavior uses this when the Items are moved on the DropDown.
internal void InitializeBodyGlyphsForItems(bool addGlyphs /* true for add */, ToolStripDropDownItem item)
{
if (addGlyphs)
{
AddBodyGlyphs(item);
}
else
{
RemoveBodyGlyphs(item);
}
}
/// <summary>
/// Important function that initializes the dropDown with the typeHereNode , hooks the events and then shows the dropDown.
/// </summary>
internal void InitializeDropDown()
{
ToolStrip main = GetMainToolStrip();
// Check if the TopMostItem is on normal dropdown or on the overflow.
ToolStripDropDown firstDropDown = GetFirstDropDown(MenuItem);
if (firstDropDown is not null)
{
ToolStripItem topMostItem = firstDropDown.OwnerItem;
if ((topMostItem is not null && topMostItem.GetCurrentParent() is ToolStripOverflow) && !main.CanOverflow)
{
return;
}
}
if (!_initialized)
{
_initialized = true;
// When the DropDown is Shared the ownerItem need not be the current MenuItem.
// In Such a case hide the dropDown for current owner ...
// this will bring everything to a sane state..
if (MenuItem.DropDown.OwnerItem is ToolStripDropDownItem currentOwner && currentOwner != MenuItem)
{
if (_designerHost.GetDesigner(currentOwner) is ToolStripMenuItemDesigner ownerdesigner)
{
ownerdesigner.RemoveTypeHereNode(currentOwner);
}
currentOwner.HideDropDown();
}
// Check if this is a Sited-DropDown
if (MenuItem.DropDown.Site is not null)
{
if (_designerHost.GetDesigner(MenuItem.DropDown) is ToolStripDropDownDesigner designer)
{
designer._currentParent = MenuItem as ToolStripMenuItem;
}
}
CreatetypeHereNode();
MenuItem.DropDown.TopLevel = false;
// Allow Drag - Drop....
MenuItem.DropDown.AllowDrop = true;
HookEvents();
MenuItem.DropDown.AutoClose = false;
MenuItem.ShowDropDown();
ShowOwnerDropDown(MenuItem);
// Every time you initialize the dropDown Reset Glyphs
ResetGlyphs(MenuItem);
if (!IsOnContextMenu && !_dummyItemAdded)
{
// Required to show the SelectionBorder when the item is selected through the PropertyGrid or Doc outline.
GetService<SelectionManager>()?.Refresh();
}
GetService<BehaviorService>()?.Invalidate(MenuItem.Owner.Bounds);
}
}
private bool IsParentDropDown(ToolStripDropDown currentDropDown)
{
if (currentDropDown is not null)
{
ToolStripDropDown startDropDown = MenuItem.Owner as ToolStripDropDown;
while (startDropDown is not null && startDropDown != currentDropDown)
{
if (startDropDown.OwnerItem is ToolStripDropDownItem ownerItem)
{
startDropDown = ownerItem.Owner as ToolStripDropDown;
}
else
{
startDropDown = null;
}
}
if (startDropDown is null)
{
return false;
}
return true;
}
return false;
}
/// <summary>
/// This will morph the current item to the provided type "t" of the item...
/// </summary>
internal override ToolStripItem MorphCurrentItem(Type t)
{
// Get the Hosting DropDown'd bounds
Rectangle hostingDropDownBounds = (MenuItem.GetCurrentParent()).Bounds;
// Get the currentItems DropDownBounds
Rectangle itemDropDownBounds = MenuItem.DropDown.Bounds;
// Remove body Glyphs...
InitializeBodyGlyphsForItems(false /*remove*/, MenuItem);
Rectangle boundstoInvalidate = Rectangle.Union(hostingDropDownBounds, itemDropDownBounds);
ToolStripAdornerWindowService toolStripService = _toolStripAdornerWindowService;
ToolStripItem newItem = base.MorphCurrentItem(t);
// We loose the ToolStripWindowService after Morphing... so use the cached one.
toolStripService?.Invalidate(boundstoInvalidate);
return newItem;
}
/// <summary>
/// Fired after a component has been added. Here, we add it to the ToolStrip and select it.
/// </summary>
private void ComponentChangeSvc_ComponentAdded(object sender, ComponentEventArgs e)
{
IComponentChangeService changeService = GetService<IComponentChangeService>();
// make sure it's one of ours and on DropDown.
if (e.Component is ToolStripItem newItem && _componentAddingFired && (MenuItemSelected || _fireComponentChanged))
{
_componentAddingFired = false;
try
{
if (IsOnContextMenu && MenuItem.DropDown.Site is not null)
{
if (changeService is not null)
{
MemberDescriptor member = TypeDescriptor.GetProperties(MenuItem.DropDown)["Items"];
changeService.OnComponentChanging(MenuItem.DropDown, member);
}
}
else
{
RaiseComponentChanging(TypeDescriptor.GetProperties(MenuItem)["DropDownItems"]);
}
// Get the current count of ToolStripItems.
int count = MenuItem.DropDownItems.Count;
// In Cut / Copy and Paste the 'indexToInsertNewItem' is not Set and hence is -1... so check for that value...
if (_indexToInsertNewItem != -1)
{
if (IsOnContextMenu && MenuItem.DropDown.Site is not null)
{
MenuItem.DropDown.Items.Insert(_indexToInsertNewItem, newItem);
}
else
{
MenuItem.DropDownItems.Insert(_indexToInsertNewItem, newItem);
}
}
else
{
// Insert Item at the current Selection...
if (_selectionService.PrimarySelection is ToolStripItem selectedItem && selectedItem != MenuItem)
{
int index = MenuItem.DropDownItems.IndexOf(selectedItem);
if (MenuItem.DropDownDirection is ToolStripDropDownDirection.AboveLeft or ToolStripDropDownDirection.AboveRight)
{
// Add at the next Index in case of "Up-Directed" dropDown.
if (IsOnContextMenu && MenuItem.DropDown.Site is not null)
{
MenuItem.DropDown.Items.Insert(index + 1, newItem);
}
else
{
MenuItem.DropDownItems.Insert(index + 1, newItem);
}
}
else
{
if (IsOnContextMenu && MenuItem.DropDown.Site is not null)
{
MenuItem.DropDown.Items.Insert(index, newItem);
}
else
{
MenuItem.DropDownItems.Insert(index, newItem);
}
}
}
else
{
if (count > 0)
{
if (MenuItem.DropDownDirection is not ToolStripDropDownDirection.AboveLeft and not ToolStripDropDownDirection.AboveRight)
{
// ADD at Last but one, the last one being the TemplateNode always...
if (IsOnContextMenu && MenuItem.DropDown.Site is not null)
{
MenuItem.DropDown.Items.Insert(count - 1, newItem);
}
else
{
MenuItem.DropDownItems.Insert(count - 1, newItem);
}
}
}
else // count == 0
{
if (IsOnContextMenu && MenuItem.DropDown.Site is not null)
{
MenuItem.DropDown.Items.Add(newItem);
}
else
{
MenuItem.DropDownItems.Add(newItem);
}
}
}
}
// If the Item is added through Undo/Redo ONLY then select the item
if (_undoingCalled)
{
_selectionService?.SetSelectedComponents(new IComponent[] { newItem }, SelectionTypes.Replace);
}
ResetGlyphs(MenuItem);
}
catch
{
CommitInsertTransaction(commit: false);
}
finally
{
if (IsOnContextMenu && MenuItem.DropDown.Site is not null)
{
if (changeService is not null)
{
MemberDescriptor member = TypeDescriptor.GetProperties(MenuItem.DropDown)["Items"];
changeService.OnComponentChanged(MenuItem.DropDown, member);
}
}
else
{
RaiseComponentChanged(TypeDescriptor.GetProperties(MenuItem)["DropDownItems"], null, null);
}
CommitInsertTransaction(/*commit=*/true);
}
}
}
private void CommitInsertTransaction(bool commit)
{
if (!IsOnContextMenu)
{
ToolStrip mainStrip = GetMainToolStrip();
if (_designerHost.GetDesigner(mainStrip) is ToolStripDesigner mainStripDesigner && mainStripDesigner.InsertTransaction is not null)
{
if (commit)
{
mainStripDesigner.InsertTransaction.Commit();
}
else
{
mainStripDesigner.InsertTransaction.Cancel();
}
mainStripDesigner.InsertTransaction = null;
}
}
else
{
if (_insertMenuItemTransaction is not null)
{
if (commit)
{
_insertMenuItemTransaction.Commit();
}
else
{
_insertMenuItemTransaction.Cancel();
}
_insertMenuItemTransaction = null;
}
}
}
/// <summary>
/// Checks if the component being added is a child ToolStripItem.
/// </summary>
private void ComponentChangeSvc_ComponentAdding(object sender, ComponentEventArgs e)
{
// Don't do anything if CopyInProgress is true
if (KeyboardHandlingService is not null && KeyboardHandlingService.CopyInProgress)
{
return;
}
if (e.Component is ToolStripItem && (MenuItemSelected || _fireComponentChanged))
{
if (!IsOnContextMenu)
{
ToolStrip mainStrip = GetMainToolStrip();
if (_designerHost.GetDesigner(mainStrip) is ToolStripDesigner mainStripDesigner && !mainStripDesigner.EditingCollection && mainStripDesigner.InsertTransaction is null)
{
_componentAddingFired = true;
Debug.Assert(_designerHost is not null, "Why didn't we get a designer host?");
mainStripDesigner.InsertTransaction = _designerHost.CreateTransaction(SR.ToolStripInsertingIntoDropDownTransaction);
}
}
else // we are on ContextMenuStrip, ToolStripDropDown or ToolStripDropDownMenu....
{
if (e.Component is ToolStripItem itemAdding && itemAdding.Owner is null)
{
_componentAddingFired = true;
Debug.Assert(_designerHost is not null, "Why didn't we get a designer host?");
_insertMenuItemTransaction = _designerHost.CreateTransaction(SR.ToolStripInsertingIntoDropDownTransaction);
}
}
}
}
/// <summary>
/// After a ToolStripItem is removed, remove it from the ToolStrip and select the next item.
/// </summary>
private void ComponentChangeSvc_ComponentRemoved(object sender, ComponentEventArgs e)
{
if (e.Component is ToolStripItem itemToBeDeleted && itemToBeDeleted.IsOnDropDown)
{
if (itemToBeDeleted.Owner is ToolStripDropDown owner)
{
// Get the ownerItem
ToolStripDropDownItem ownerItem = (ToolStripDropDownItem)owner.OwnerItem;
if (ownerItem is not null && ownerItem == MenuItem)
{
int itemIndex = ownerItem.DropDownItems.IndexOf(itemToBeDeleted);
// send notifications.
try
{
if (itemIndex != -1)
{
ownerItem.DropDownItems.Remove(itemToBeDeleted);
RaiseComponentChanged(TypeDescriptor.GetProperties(ownerItem)["DropDownItems"], null, null);
}
}
finally
{
if (_pendingTransaction is not null)
{
_pendingTransaction.Commit();
_pendingTransaction = null;
}
}
// Remove & Add the Glyphs
ResetGlyphs(ownerItem);
// select the next item or the ToolStrip itself.
if (ownerItem.DropDownItems.Count > 1)
{
itemIndex = Math.Min(ownerItem.DropDownItems.Count - 1, itemIndex);
itemIndex = Math.Max(0, itemIndex);
}
else
{
itemIndex = -1;
}
// Looks like we need to invalidate the entire
if (_toolStripAdornerWindowService is not null && _boundsToInvalidateOnRemove != Rectangle.Empty)
{
using Region regionToInvalidate = new(_boundsToInvalidateOnRemove);
regionToInvalidate.Exclude(MenuItem.DropDown.Bounds);
_toolStripAdornerWindowService.Invalidate(regionToInvalidate);
_boundsToInvalidateOnRemove = Rectangle.Empty;
}
// Select the item only if Cut/Delete is pressed.
if (KeyboardHandlingService is not null && KeyboardHandlingService.CutOrDeleteInProgress)
{
if (_selectionService is not null && !_dummyItemAdded)
{
IComponent targetSelection = (itemIndex == -1) ? ownerItem : ownerItem.DropDownItems[itemIndex];
// if the TemplateNode becomes the targetSelection, then set the targetSelection to null.
if (targetSelection is DesignerToolStripControlHost)
{
KeyboardHandlingService.SelectedDesignerControl = targetSelection;
KeyboardHandlingService.OwnerItemAfterCut = MenuItem;
_selectionService.SetSelectedComponents(null, SelectionTypes.Replace);
}
else
{
_selectionService.SetSelectedComponents(new IComponent[] { targetSelection }, SelectionTypes.Replace);
}
}
}
}
}
}
}
/// <summary>
/// Before a ToolStripItem is removed, open a transaction to batch the operation.
/// </summary>
private void ComponentChangeSvc_ComponentRemoving(object sender, ComponentEventArgs e)
{
if (_dummyItemAdded)
{
return;
}
if (e.Component is ToolStripItem itemToBeDeleted && itemToBeDeleted.IsOnDropDown && itemToBeDeleted.Placement == ToolStripItemPlacement.Main)
{
if (itemToBeDeleted.Owner is ToolStripDropDown owner)
{
// Get the ownerItem
ToolStripDropDownItem ownerItem = (ToolStripDropDownItem)owner.OwnerItem;
if (ownerItem is not null && ownerItem == MenuItem)
{
RemoveItemBodyGlyph(itemToBeDeleted);
InitializeBodyGlyphsForItems(false, ownerItem);
_boundsToInvalidateOnRemove = ownerItem.DropDown.Bounds;
// Check if the deleted item is a dropDownItem and its DropDown is Visible.
if (itemToBeDeleted is ToolStripDropDownItem dropDownItem)
{
_boundsToInvalidateOnRemove = Rectangle.Union(_boundsToInvalidateOnRemove, dropDownItem.DropDown.Bounds);
}
Debug.Assert(_designerHost is not null, "Why didn't we get a designer host?");
Debug.Assert(_pendingTransaction is null, "Adding item with pending transaction?");
try
{
_pendingTransaction = _designerHost.CreateTransaction(SR.ToolStripDesignerTransactionRemovingItem);
RaiseComponentChanging(TypeDescriptor.GetProperties(ownerItem)["DropDownItems"]);
}
catch
{
if (_pendingTransaction is not null)
{
_pendingTransaction.Cancel();
_pendingTransaction = null;
}
}
}
}
}
}
/// <summary>
/// Controls the dismissal of the drop down, here - we just cancel it
/// </summary>
private void OnDropDownClosing(object sender, ToolStripDropDownClosingEventArgs e)
{
// always dismiss this so we don't collapse the dropdown when the user clicks @ design time
e.Cancel = (e.CloseReason == ToolStripDropDownCloseReason.ItemClicked);
}
/// <summary>
/// When DropDown is disposed; nullify the dropDown.
/// </summary>
private void OnDropDownDisposed(object sender, EventArgs e)
{
if (MenuItem is not null)
{
if (MenuItem.DropDown is not null)
{
MenuItem.DropDown.Disposed -= OnDropDownDisposed;
}
// This is necessary when the MenuItem's DropDown property is SET to something other than the default DropDown.
MenuItem.DropDown = null;
}
}
/// <summary>
/// When a item is added, re-arrange the elements to make sure that the templateNode is at the end..
/// </summary>
private void OnItemAdded(object sender, ToolStripItemEventArgs e)
{
// Reshuffle the TemplateNode only for "Downward" dropdowns.
if (MenuItem.DropDownDirection is not ToolStripDropDownDirection.AboveLeft and not ToolStripDropDownDirection.AboveRight)
{
if (_typeHereNode is not null && (e.Item != _typeHereNode))
{
int currentIndexOfEditor = MenuItem.DropDown.Items.IndexOf(_typeHereNode);
if (currentIndexOfEditor >= 0 && currentIndexOfEditor < MenuItem.DropDown.Items.Count - 1)
{
// we now know the editor is there, but isn't currently at the end of the line. lets add it.
MenuItem.DropDown.ItemAdded -= OnItemAdded;
MenuItem.DropDown.SuspendLayout();
MenuItem.DropDown.Items.Remove(_typeHereNode);
MenuItem.DropDown.Items.Add(_typeHereNode);
MenuItem.DropDown.ResumeLayout();
MenuItem.DropDown.ItemAdded += OnItemAdded;
}
else
{
CreatetypeHereNode();
}
}
}
}
/// <summary>
/// Called during Undo (this is used for DropDown Property)
/// </summary>
private void OnUndone(object source, EventArgs e)
{
if (_undoingCalled)
{
// If we have Undone the SETTING of DropDown Revert back to Original state.
if (_dropDownSet && MenuItem.DropDown.IsAutoGenerated)
{
ToolStrip mainStrip = GetMainToolStrip();
if (_designerHost.GetDesigner(mainStrip) is ToolStripDesigner mainStripDesigner && mainStripDesigner.CacheItems)
{
foreach (ToolStripItem item in mainStripDesigner.Items)
{
MenuItem.DropDownItems.Insert(0, item);
}
mainStripDesigner.CacheItems = false;
}
ResetGlyphs(MenuItem);
}
// since the dropDown is closed during UnDoing .. we need to re-open the dropDown in Undone.
if (MenuItem is not null && _selectionService.GetComponentSelected(MenuItem))
{
InitializeDropDown();
MenuItem.DropDown.PerformLayout();
}
_undoingCalled = false;
_dropDownSet = false;
}
// After Redo-Undo Glyphs are broken.
if (_selectionService.GetComponentSelected(MenuItem) && !_dropDownSetFailed)
{
InitializeDropDown();
}
}
/// <summary>
/// Called during Undo (this is used for DropDown Property)
/// </summary>
private void OnUndoing(object source, EventArgs e)
{
if (_dummyItemAdded)
{
return;
}
if (!IsOnContextMenu && MenuItem.DropDown.Visible)
{
MenuItem.HideDropDown();
if (!MenuItem.DropDown.IsAutoGenerated)
{
_dropDownSet = true;
ToolStrip mainStrip = GetMainToolStrip();
if (_designerHost.GetDesigner(mainStrip) is ToolStripDesigner mainStripDesigner)
{
mainStripDesigner.CacheItems = true;
mainStripDesigner.Items.Clear();
}
}
_undoingCalled = true;
}
}
/// <summary>
/// Once a menuItem designer has selection - be sure to expand and collapse all necessary child/parent items
/// Implements the Selection Paint Logic by adding Text to Tag property. Also Hides Unnecessary DropDowns.
/// </summary>
private void OnSelectionChanged(object sender, EventArgs e)
{
// determine if we are selected
if (MenuItem is null)
{
return;
}
ISelectionService selectionSvc = sender as ISelectionService;
Debug.Assert(selectionSvc is not null, "No Selection Service !!");
if (selectionSvc is null)
{
return;
}
// ALWAYS COMMIT!!!
if (_commitedTemplateNode is not null && _commitedTemplateNode.Active)
{
_commitedTemplateNode.Commit(false, false);
}
else if (_typeHereTemplateNode is not null && _typeHereTemplateNode.Active)
{
_typeHereTemplateNode.Commit(false, false);
}
if (MenuItem.Equals(selectionSvc.PrimarySelection))
{
ArrayList origSel = ToolStripDesignerUtils.s_originalSelComps;
if (origSel is not null)
{
ToolStripDesignerUtils.InvalidateSelection(origSel, MenuItem, MenuItem.Site, false /*shift pressed*/);
}
if (IsOnContextMenu && !MenuItem.Owner.Visible)
{
ToolStripDropDown firstDropDown = GetFirstDropDown(MenuItem);
if (_designerHost.GetDesigner(firstDropDown) is ToolStripDropDownDesigner firstDropDownDesigner)
{
InitializeDropDown();
firstDropDownDesigner.ShowMenu();
firstDropDownDesigner.AddSelectionGlyphs();
}
}
else
{
InitializeDropDown();
}
// Cache original selection
ICollection originalSelComps = null;
if (_selectionService is not null)
{
originalSelComps = selectionSvc.GetSelectedComponents();
}
// Add the TemplateNode to the Selection if it is currently Selected as the GetSelectedComponents won't do it for us.
origSel = new ArrayList(originalSelComps);
if (origSel.Count == 0)
{
if (KeyboardHandlingService is not null && KeyboardHandlingService.SelectedDesignerControl is not null)
{
origSel.Add(KeyboardHandlingService.SelectedDesignerControl);
}
}
if (origSel.Count > 0)
{
ToolStripDesignerUtils.s_originalSelComps = origSel;
}
}
else
{
object selectedObj = ((ISelectionService)sender).PrimarySelection;
if (selectedObj is null)
{
if (KeyboardHandlingService is not null)
{
selectedObj = KeyboardHandlingService.SelectedDesignerControl;
}
}
if (selectedObj is ToolStripItem currentSelection)
{
ToolStripDropDown parent = currentSelection.Owner as ToolStripDropDown;
while (parent is not null)
{
if (parent.OwnerItem == MenuItem || parent.OwnerItem is null)
{
return;
}
else
{
parent = parent.OwnerItem.Owner as ToolStripDropDown;
}
}
}
if (MenuItem.DropDown.Visible)
{
// CASE : Check if the DropDown Selected is the one assigned to this MenuItem's DropDown property.
// If MenuItem.DropDown matches the currentSelection or is the First Dropdown
// of any selection THEN return
if (selectedObj is ToolStripDropDown selectedDropDown && MenuItem.DropDown == selectedDropDown)
{
return;
}
else
{
// Any ToolStripItem on the DropDown OR any of its Child DropDowns
if (selectedObj is ToolStripItem item)
{
ToolStripDropDown parent = item.Owner as ToolStripDropDown;
while (parent is not null)
{
if (parent == MenuItem.DropDown)
{
return;
}
else
{
parent = parent.OwnerItem.Owner as ToolStripDropDown;
}
}
}
}
// Close your own dropDown...
if (MenuItem.DropDown.OwnerItem == MenuItem)
{
MenuItem.HideDropDown();
}
}
}
}
/// <summary>
/// Allows a designer to filter the set of properties the component it is designing will expose through
/// the TypeDescriptor object. This method is called immediately before its corresponding "Post" method.
/// If you are overriding this method you should call the base implementation before you perform your own filtering.
/// </summary>
protected override void PreFilterProperties(IDictionary properties)
{
base.PreFilterProperties(properties);
// Handle shadowed properties
string[] shadowProps = ["Visible", "DoubleClickEnabled", "CheckOnClick", "DropDown"];
PropertyDescriptor prop;
Attribute[] empty = [];
for (int i = 0; i < shadowProps.Length; i++)
{
prop = (PropertyDescriptor)properties[shadowProps[i]];
if (prop is not null)
{
properties[shadowProps[i]] = TypeDescriptor.CreateProperty(typeof(ToolStripMenuItemDesigner), prop, empty);
}
}
}
/// <summary>
/// Resets the ToolStripMenuItem DoubleClickEnabled to be the default visible
/// </summary>
private void ResetDoubleClickEnabled() => DoubleClickEnabled = false;
/// <summary>
/// Resets the ToolStripMenuItem CheckOnClick to be the default visible
/// </summary>
private void ResetCheckOnClick() => CheckOnClick = false;
/// <summary>
/// Resets the ToolStripMenuItem CheckOnClick to be the default visible
/// </summary>
private void ResetDropDown() => DropDown = null;
/// <summary>
/// Resets the ToolStripMenuItem Visible to be the default visible
/// </summary>
private void ResetVisible() => Visible = true;
/// <summary>
/// Restores the ToolStripMenuItem Visible to be the value set in the property grid.
/// </summary>
private void RestoreVisible() => MenuItem.Visible = Visible;
/// <summary>
/// Removes the TypeHere node when the DropDownCloses.
/// </summary>
internal void RemoveTypeHereNode(ToolStripDropDownItem ownerItem)
{
// This will cause the DropDown to Relayout so that during the DropDownClosed event we won't have
// proper Bounds to Invalidate the ToolStripAdorner... So for this case do it here...
Rectangle bounds = ownerItem.DropDown.Bounds;
if (ownerItem.DropDownItems.Count > 0 && ownerItem.DropDownItems[0] is DesignerToolStripControlHost)
{
ownerItem.DropDownItems.RemoveAt(0);
}
if (_typeHereTemplateNode is not null && _typeHereTemplateNode.Active)
{
_typeHereTemplateNode.RollBack();
_typeHereTemplateNode.CloseEditor();
_typeHereTemplateNode = null;
}
if (_typeHereNode is not null)
{
_typeHereNode.Dispose();
_typeHereNode = null;
}
_toolStripAdornerWindowService?.Invalidate(bounds);
}
/// <summary>
/// This private function is called to ROLLBACK the current InSitu editing mode.
/// </summary>
private void RollBack()
{
if (_commitedEditorNode is not null)
{
int index = MenuItem.DropDownItems.IndexOf(_commitedEditorNode);
Debug.Assert(index != -1, "Invalid Index");
ToolStripDropDownItem editedItem = (ToolStripDropDownItem)MenuItem.DropDownItems[index + 1];
if (editedItem is not null)
{
editedItem.Visible = true;
}
MenuItem.DropDown.Items.Remove(_commitedEditorNode);
if (_commitedTemplateNode is not null)
{
_commitedTemplateNode.RollBack();
_commitedTemplateNode.CloseEditor();
_commitedTemplateNode = null;
}
if (_commitedEditorNode is not null)
{
_commitedEditorNode.Dispose();
_commitedEditorNode = null;
}
}
}
/// <summary>
/// Remove Body glyphs when the dropDown is closed.
/// </summary>
private void RemoveBodyGlyphs(ToolStripDropDownItem item)
{
if (item is not null)
{
foreach (ToolStripItem ddItem in item.DropDownItems)
{
ToolStripItemDesigner dropDownItemDesigner = (ToolStripItemDesigner)_designerHost.GetDesigner(ddItem);
if (dropDownItemDesigner is not null)
{
ControlBodyGlyph glyph = dropDownItemDesigner._bodyGlyph;
if (glyph is not null && _toolStripAdornerWindowService is not null && _toolStripAdornerWindowService.DropDownAdorner.Glyphs.Contains(glyph))
{
_toolStripAdornerWindowService.DropDownAdorner.Glyphs.Remove(glyph);
dropDownItemDesigner._bodyGlyph = null;
}
}
}
}
}
/// <summary>
/// Remove glyphs per item
/// </summary>
internal void RemoveItemBodyGlyph(ToolStripItem item)
{
if (item is not null)
{
ToolStripItemDesigner itemDesigner = (ToolStripItemDesigner)_designerHost.GetDesigner(item);
if (itemDesigner is not null)
{
ControlBodyGlyph glyph = itemDesigner._bodyGlyph;
if (glyph is not null && _toolStripAdornerWindowService is not null && _toolStripAdornerWindowService.DropDownAdorner.Glyphs.Contains(glyph))
{
_toolStripAdornerWindowService.DropDownAdorner.Glyphs.Remove(glyph);
itemDesigner._bodyGlyph = null;
}
}
}
}
/// <summary>
/// Helper function to remove and then re-add the glyphs.
/// </summary>
internal void ResetGlyphs(ToolStripDropDownItem item)
{
// Reset the glyphs only if the DropDown is visible.
if (item.DropDown.Visible)
{
InitializeBodyGlyphsForItems(false, item);
InitializeBodyGlyphsForItems(true, item);
}
}
/// <summary>
/// Set the Selection after a InSitu edit is complete.
/// </summary>
internal override bool SetSelection(bool enterKeyPressed)
{
if (enterKeyPressed)
{
if (!_initialized)
{
InitializeDropDown();
}
// set the selection to our new item
if (_selectionService is not null)
{
if (KeyboardHandlingService is not null)
{
int count = 0;
if (MenuItem.DropDownDirection is not ToolStripDropDownDirection.AboveLeft and not ToolStripDropDownDirection.AboveRight)
{
// index to the last item.
count = MenuItem.DropDownItems.Count;
count--;
}
_selectionService.SetSelectedComponents(new object[] { MenuItem }, SelectionTypes.Replace);
if (count >= 0)
{
KeyboardHandlingService.SelectedDesignerControl = MenuItem.DropDownItems[count];
_selectionService.SetSelectedComponents(null, SelectionTypes.Replace);
}
}
}
return true;
}
return false;
}
/// <summary>
/// Returns true if the visible property should be persisted in code gen.
/// </summary>
private bool ShouldSerializeDoubleClickEnabled() => (bool)ShadowProperties[nameof(DoubleClickEnabled)];
/// <summary>
/// Returns true if the CheckOnClick property should be persisted in code gen.
/// </summary>
private bool ShouldSerializeCheckOnClick() => (bool)ShadowProperties[nameof(CheckOnClick)];
/// <summary>
/// Returns true if the CheckOnClick property should be persisted in code gen.
/// </summary>
private bool ShouldSerializeDropDown() => (_customDropDown is not null);
/// <summary>
/// Returns true if the visible property should be persisted in code gen.
/// </summary>
private bool ShouldSerializeVisible() => !Visible;
/// <summary>
/// This Function is called thru the ToolStripEditorManager which is listening for the F2 command.
/// </summary>
internal override void ShowEditNode(bool clicked)
{
if (MenuItem is null)
{
return;
}
try
{
if (MenuItem.Owner is ToolStripDropDown dropDown)
{
_parentItem = dropDown.OwnerItem;
// need to inform the owner tha we want to enter InSitu mode
if (_designerHost is not null)
{
IDesigner designer = _designerHost.GetDesigner(_parentItem);
if (designer is ToolStripMenuItemDesigner menuItemDesigner)
{
menuItemDesigner.EnterInSituEdit(MenuItem);
}
}
}
// We come here for TOP LEVEL MENUITEM .. So call base version...
else
{
base.ShowEditNode(clicked);
}
}
catch (CheckoutException checkoutException)
{
// Do not crash on canceled checkout
if (checkoutException.Equals(CheckoutException.Canceled))
{
return;
}
else
{
throw;
}
}
}
/// <summary>
/// This Function would select all items starting form oldSelection to the Current MenuItem.
/// </summary>
private void SelectItems(ToolStripDropDownItem oldSelection, ISelectionService selSvc)
{
ToolStripDropDown ownerDropDown = (ToolStripDropDown)MenuItem.Owner;
int maxIndex = Math.Max(ownerDropDown.Items.IndexOf(oldSelection), ownerDropDown.Items.IndexOf(MenuItem));
int minIndex = Math.Min(ownerDropDown.Items.IndexOf(oldSelection), ownerDropDown.Items.IndexOf(MenuItem));
ToolStripItem[] selectedItems = new ToolStripItem[maxIndex - minIndex + 1];
int i = 0;
while (minIndex <= maxIndex)
{
selectedItems[i] = ownerDropDown.Items[minIndex];
i++;
minIndex++;
}
selSvc.SetSelectedComponents(selectedItems);
}
/// <summary>
/// Shows ALL the owner DropDowns if passed in MenuItem is Selected
/// </summary>
internal void ShowOwnerDropDown(ToolStripDropDownItem currentSelection)
{
// We MIGHT HAVE TO START TOP - DOWN Instead of BOTTOM-UP.
// Sometimes we DON'T get the Owner POPUP and hence all the popup are parented to Wrong guy.
while (currentSelection is not null && currentSelection.Owner is ToolStripDropDown dropDown)
{
ToolStripDropDown currentDropDown = dropDown;
currentSelection = (ToolStripDropDownItem)currentDropDown.OwnerItem;
if (currentSelection is not null && !currentSelection.DropDown.Visible)
{
if (_designerHost.GetDesigner(currentSelection) is ToolStripMenuItemDesigner currentSelectionDesigner)
{
currentSelectionDesigner.InitializeDropDown();
}
}
else if (currentDropDown is ContextMenuStrip && !currentDropDown.Visible)
{
// ContextMenuStrip does not have an owner item, and need be shown with different method
ToolStripDropDownDesigner dropDownDesigner = (ToolStripDropDownDesigner)_designerHost.GetDesigner(currentDropDown);
dropDownDesigner?.ShowMenu(null);
}
}
}
internal void UnHookEvents()
{
if (MenuItem is not null)
{
MenuItem.DropDown.Closing -= OnDropDownClosing;
MenuItem.DropDownOpening -= DropDownItem_DropDownOpening;
MenuItem.DropDownOpened -= DropDownItem_DropDownOpened;
MenuItem.DropDownClosed -= DropDownItem_DropDownClosed;
MenuItem.DropDown.Resize -= DropDownResize;
MenuItem.DropDown.ItemAdded -= OnItemAdded;
MenuItem.DropDown.Paint -= DropDownPaint;
MenuItem.DropDown.LocationChanged -= DropDownLocationChanged;
MenuItem.DropDown.Click -= DropDownClick;
}
}
/// <summary>
/// The glyph we put over the items. Basically this sets the hit-testable area of the item itself.
/// </summary>
internal class ToolStripDropDownGlyph : Glyph
{
private Rectangle _bounds;
internal ToolStripDropDownGlyph(Rectangle bounds, Behavior.Behavior b) : base(b)
{
_bounds = bounds;
}
/// <summary>
/// Abstract method that forces Glyph implementations to provide hit test logic.
/// Given any point - if the Glyph has decided to be involved with that location,
/// the Glyph will need to return a valid Cursor. Otherwise, returning null will cause
/// the BehaviorService to simply ignore it.
/// </summary>
public override Cursor GetHitTest(Point p)
{
if (_bounds.Contains(p))
{
return Cursors.Default;
}
return null;
}
/// <summary>
/// Overrides Glyph::Paint - this implementation does nothing.
/// </summary>
public override void Paint(PaintEventArgs pe)
{
}
}
/// <summary>
/// The transparent behavior on top of the DropDownGlyphs.
/// </summary>
internal class DropDownBehavior : ControlDesigner.TransparentBehavior
{
/// <summary>
/// Constructor that accepts the related ControlDesigner.
/// </summary>
private readonly ToolStripMenuItemDesigner _menuItemDesigner;
internal DropDownBehavior(ControlDesigner designer, ToolStripMenuItemDesigner menuItemDesigner) : base(designer)
{
_menuItemDesigner = menuItemDesigner;
}
/// <summary>
/// Drag drop support on the DropDown...
/// </summary>
public override void OnDragEnter(Glyph g, DragEventArgs e)
{
if (e.Data is ToolStripItemDataObject)
{
e.Effect = (Control.ModifierKeys == Keys.Control) ? DragDropEffects.Copy : DragDropEffects.Move;
}
else
{
base.OnDragEnter(g, e);
}
}
/// <summary>
/// Drag drop support on the DropDown...
/// </summary>
public override void OnDragOver(Glyph g, DragEventArgs e)
{
if (e.Data is ToolStripItemDataObject)
{
e.Effect = (Control.ModifierKeys == Keys.Control) ? DragDropEffects.Copy : DragDropEffects.Move;
}
else
{
base.OnDragOver(g, e);
}
}
/// <summary>
/// Drag drop support on the DropDown...
/// </summary>
public override void OnDragDrop(Glyph g, DragEventArgs e)
{
if (e.Data is ToolStripItemDataObject data)
{
ToolStripItem primaryItem = data.PrimarySelection;
IDesignerHost host = (IDesignerHost)primaryItem.Site.GetService(typeof(IDesignerHost));
ToolStripDropDown parentToolStrip = primaryItem.GetCurrentParent() as ToolStripDropDown;
ToolStripDropDownItem ownerItem = null;
if (parentToolStrip is not null)
{
ownerItem = parentToolStrip.OwnerItem as ToolStripDropDownItem;
}
Debug.Assert(ownerItem is not null, "How can ownerItem be null for a menu item on a dropdown?");
if (ownerItem is not null && host is not null)
{
string transDesc;
List<ToolStripItem> dragComponents = data.DragComponents;
int primaryIndex = -1;
bool copy = (e.Effect == DragDropEffects.Copy);
if (dragComponents.Count == 1)
{
string name = TypeDescriptor.GetComponentName(dragComponents[0]);
if (name is null || name.Length == 0)
{
name = dragComponents[0].GetType().Name;
}
transDesc = string.Format(copy ? SR.BehaviorServiceCopyControl : SR.BehaviorServiceMoveControl, name);
}
else
{
transDesc = string.Format(copy ? SR.BehaviorServiceCopyControls : SR.BehaviorServiceMoveControls, dragComponents.Count);
}
// create a transaction so this happens as an atomic unit.
DesignerTransaction changeParent = host.CreateTransaction(transDesc);
try
{
var changeService = primaryItem.Site.GetService<IComponentChangeService>();
changeService?.OnComponentChanging(ownerItem, TypeDescriptor.GetProperties(ownerItem)["DropDownItems"]);
IReadOnlyList<IComponent> components;
// If we are copying, then we want to make a copy of the components we are dragging
if (copy)
{
// Remember the primary selection if we had one
if (primaryItem is not null)
{
primaryIndex = dragComponents.IndexOf(primaryItem);
}
ToolStripKeyboardHandlingService keyboardHandlingService = (ToolStripKeyboardHandlingService)primaryItem.Site.GetService(typeof(ToolStripKeyboardHandlingService));
if (keyboardHandlingService is not null)
{
keyboardHandlingService.CopyInProgress = true;
}
components = DesignerUtils.CopyDragObjects(dragComponents, primaryItem.Site);
if (keyboardHandlingService is not null)
{
keyboardHandlingService.CopyInProgress = false;
}
if (primaryIndex != -1)
{
primaryItem = components[primaryIndex] as ToolStripItem;
}
}
else
{
components = dragComponents;
}
if (e.Effect == DragDropEffects.Move || copy)
{
// Add the item.
foreach (ToolStripItem toolItem in components)
{
parentToolStrip.Items.Add(toolItem);
}
// Open the DropDown for the PrimarySelection before the DRAG-DROP operation.
if (primaryItem is ToolStripDropDownItem dropDownItem)
{
if (host.GetDesigner(dropDownItem) is ToolStripMenuItemDesigner dropDownItemDesigner)
{
dropDownItemDesigner.InitializeDropDown();
}
}
// Set the Selection ..
_menuItemDesigner._selectionService.SetSelectedComponents(new IComponent[] { primaryItem }, SelectionTypes.Primary | SelectionTypes.Replace);
}
changeService?.OnComponentChanged(ownerItem, TypeDescriptor.GetProperties(ownerItem)["DropDownItems"]);
// fire extra changing/changed events so that the order is "restored" after undo/redo
if (copy)
{
if (changeService is not null)
{
changeService.OnComponentChanging(ownerItem, TypeDescriptor.GetProperties(ownerItem)["DropDownItems"]);
changeService.OnComponentChanged(ownerItem, TypeDescriptor.GetProperties(ownerItem)["DropDownItems"]);
}
}
// If Parent is DropDown... we have to manage the Glyphs ....
if (ownerItem is not null)
{
if (host.GetDesigner(ownerItem) is ToolStripMenuItemDesigner ownerDesigner)
{
ownerDesigner.InitializeBodyGlyphsForItems(false, ownerItem);
ownerDesigner.InitializeBodyGlyphsForItems(true, ownerItem);
}
}
// Refresh the BehaviorService.
BehaviorService bSvc = (BehaviorService)primaryItem.Site.GetService(typeof(BehaviorService));
bSvc?.SyncSelection();
}
catch
{
if (changeParent is not null)
{
changeParent.Cancel();
changeParent = null;
}
}
finally
{
changeParent?.Commit();
}
}
}
}
}
}
|