|
//------------------------------------------------------------------------------
// <copyright file="Menu.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
/*
*/
namespace System.Windows.Forms {
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System;
using System.Security.Permissions;
using System.Collections;
using System.Windows.Forms.Design;
using System.Drawing;
using Microsoft.Win32;
using System.Security;
using System.Globalization;
using System.Runtime.Versioning;
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu"]/*' />
/// <devdoc>
/// This is the base class for all menu components (MainMenu, MenuItem, and ContextMenu).
/// </devdoc>
[
ToolboxItemFilter("System.Windows.Forms"),
ListBindable(false)
]
public abstract class Menu : Component {
internal const int CHANGE_ITEMS = 0; // item(s) added or removed
internal const int CHANGE_VISIBLE = 1; // item(s) hidden or shown
internal const int CHANGE_MDI = 2; // mdi item changed
internal const int CHANGE_MERGE = 3; // mergeType or mergeOrder changed
internal const int CHANGE_ITEMADDED = 4; // mergeType or mergeOrder changed
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.FindHandle"]/*' />
/// <devdoc>
/// Used by findMenuItem
/// </devdoc>
/// <internalonly/>
public const int FindHandle = 0;
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.FindShortcut"]/*' />
/// <devdoc>
/// Used by findMenuItem
/// </devdoc>
/// <internalonly/>
public const int FindShortcut = 1;
private MenuItemCollection itemsCollection;
internal MenuItem[] items;
private int _itemCount;
internal IntPtr handle;
internal bool created;
private object userData;
private string name;
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.Menu"]/*' />
/// <devdoc>
/// This is an abstract class. Instances cannot be created, so the constructor
/// is only called from derived classes.
/// </devdoc>
/// <internalonly/>
protected Menu(MenuItem[] items) {
if (items != null) {
MenuItems.AddRange(items);
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.Handle"]/*' />
/// <devdoc>
/// The HMENU handle corresponding to this menu.
/// </devdoc>
[
Browsable(false), EditorBrowsable(EditorBrowsableState.Advanced),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
SRDescription(SR.ControlHandleDescr)
]
public IntPtr Handle {
get {
if (handle == IntPtr.Zero) handle = CreateMenuHandle();
CreateMenuItems();
return handle;
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.IsParent"]/*' />
/// <devdoc>
/// Specifies whether this menu contains any items.
/// </devdoc>
[
Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
SRDescription(SR.MenuIsParentDescr)
]
public virtual bool IsParent {
[System.Security.Permissions.SecurityPermissionAttribute(System.Security.Permissions.SecurityAction.InheritanceDemand, Flags=System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)]
get {
return null != items && ItemCount > 0;
}
}
internal int ItemCount {
get {
return _itemCount;
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MdiListItem"]/*' />
/// <devdoc>
/// The MenuItem that contains the list of MDI child windows.
/// </devdoc>
[
Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
SRDescription(SR.MenuMDIListItemDescr)
]
public MenuItem MdiListItem {
get {
for (int i = 0; i < ItemCount; i++) {
MenuItem item = items[i];
if (item.MdiList)
return item;
if (item.IsParent) {
item = item.MdiListItem;
if (item != null) return item;
}
}
return null;
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.Name"]/*' />
/// <devdoc>
/// Name of this control. The designer will set this to the same
/// as the programatic Id "(name)" of the control - however this
/// property has no bearing on the runtime aspects of this control.
/// </devdoc>
[
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
Browsable(false)
]
public string Name {
get {
return WindowsFormsUtils.GetComponentName(this, name);
}
set {
if (value == null || value.Length == 0) {
name = null;
}
else {
name = value;
}
if(Site!= null) {
Site.Name = name;
}
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItems"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
[
Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
SRDescription(SR.MenuMenuItemsDescr),
MergableProperty(false)
]
public MenuItemCollection MenuItems {
get {
if (itemsCollection == null) {
itemsCollection = new MenuItemCollection(this);
}
return itemsCollection;
}
}
internal virtual bool RenderIsRightToLeft {
get {
Debug.Assert(true, "Should never get called");
return false;
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.Tag"]/*' />
[
SRCategory(SR.CatData),
Localizable(false),
Bindable(true),
SRDescription(SR.ControlTagDescr),
DefaultValue(null),
TypeConverter(typeof(StringConverter)),
]
public object Tag {
get {
return userData;
}
set {
userData = value;
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.ClearHandles"]/*' />
/// <devdoc>
/// Notifies Menu that someone called Windows.DeleteMenu on its handle.
/// </devdoc>
/// <internalonly/>
internal void ClearHandles() {
if (handle != IntPtr.Zero) {
UnsafeNativeMethods.DestroyMenu(new HandleRef(this, handle));
}
handle = IntPtr.Zero;
if (created) {
for (int i = 0; i < ItemCount; i++) {
items[i].ClearHandles();
}
created = false;
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.CloneMenu"]/*' />
/// <devdoc>
/// Sets this menu to be an identical copy of another menu.
/// </devdoc>
[
SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly") // Shipped as is in Everett
]
protected internal void CloneMenu(Menu menuSrc) {
MenuItem[] newItems = null;
if (menuSrc.items != null) {
int count = menuSrc.MenuItems.Count;
newItems = new MenuItem[count];
for (int i = 0; i < count; i++)
newItems[i] = menuSrc.MenuItems[i].CloneMenu();
}
MenuItems.Clear();
if (newItems != null) {
MenuItems.AddRange(newItems);
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.CreateMenuHandle"]/*' />
/// <devdoc>
/// </devdoc>
/// <internalonly/>
[SecurityPermission(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
[ResourceExposure(ResourceScope.Process)]
[ResourceConsumption(ResourceScope.Process)]
protected virtual IntPtr CreateMenuHandle() {
return UnsafeNativeMethods.CreatePopupMenu();
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.CreateMenuItems"]/*' />
/// <devdoc>
/// </devdoc>
/// <internalonly/>
internal void CreateMenuItems() {
if (!created) {
for (int i = 0; i < ItemCount; i++) {
items[i].CreateMenuItem();
}
created = true;
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.DestroyMenuItems"]/*' />
/// <devdoc>
/// </devdoc>
/// <internalonly/>
internal void DestroyMenuItems() {
if (created) {
for (int i = 0; i < ItemCount; i++) {
items[i].ClearHandles();
}
while (UnsafeNativeMethods.GetMenuItemCount(new HandleRef(this, handle)) > 0) {
UnsafeNativeMethods.RemoveMenu(new HandleRef(this, handle), 0, NativeMethods.MF_BYPOSITION);
}
created = false;
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.Dispose"]/*' />
/// <devdoc>
/// Disposes of the component. Call dispose when the component is no longer needed.
/// This method removes the component from its container (if the component has a site)
/// and triggers the dispose event.
/// </devdoc>
protected override void Dispose(bool disposing) {
if (disposing) {
while (ItemCount > 0) {
MenuItem item = items[--_itemCount];
// remove the item before we dispose it so it still has valid state
// for undo/redo
//
if (item.Site != null && item.Site.Container != null) {
item.Site.Container.Remove(item);
}
item.Menu = null;
item.Dispose();
}
items = null;
}
if (handle != IntPtr.Zero) {
UnsafeNativeMethods.DestroyMenu(new HandleRef(this, handle));
this.handle = IntPtr.Zero;
if (disposing) {
ClearHandles();
}
}
base.Dispose(disposing);
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.FindMenuItem"]/*' />
/// <devdoc>
/// </devdoc>
/// <internalonly/>
public MenuItem FindMenuItem(int type, IntPtr value) {
Debug.WriteLineIf(IntSecurity.SecurityDemand.TraceVerbose, "ControlFromHandleOrLocation Demanded");
IntSecurity.ControlFromHandleOrLocation.Demand();
return FindMenuItemInternal(type, value);
}
private MenuItem FindMenuItemInternal(int type, IntPtr value) {
for (int i = 0; i < ItemCount; i++) {
MenuItem item = items[i];
switch (type) {
case FindHandle:
if (item.handle == value) return item;
break;
case FindShortcut:
if (item.Shortcut == (Shortcut)(int)value) return item;
break;
}
item = item.FindMenuItemInternal(type, value);
if (item != null) return item;
}
return null;
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.FindMergePosition"]/*' />
/// <devdoc>
/// </devdoc>
/// <internalonly/>
protected int FindMergePosition(int mergeOrder) {
int iMin, iLim, iT;
for (iMin = 0, iLim = ItemCount; iMin < iLim;) {
iT = (iMin + iLim) / 2;
if (items[iT].MergeOrder <= mergeOrder)
iMin = iT + 1;
else
iLim = iT;
}
return iMin;
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.xFindMergePosition"]/*' />
/// <devdoc>
/// </devdoc>
/// <internalonly/>
// VSWhidbey 94987: A new method for finding the approximate merge position. The original
// method assumed (incorrectly) that the MergeOrder of the target menu would be sequential
// as it's guaranteed to be in the MDI imlementation of merging container and child
// menus. However, user code can call MergeMenu independently on a source and target
// menu whose MergeOrder values are not necessarily pre-sorted.
internal int xFindMergePosition(int mergeOrder) {
int nPosition = 0;
// Iterate from beginning to end since we can't assume any sequential ordering to MergeOrder
for (int nLoop = 0; nLoop < ItemCount; nLoop++) {
if (items[nLoop].MergeOrder > mergeOrder) {
// We didn't find what we're looking for, but we've found a stopping point.
break;
}
else if (items[nLoop].MergeOrder < mergeOrder) {
// We might have found what we're looking for, but we'll have to come around again
// to know.
nPosition = nLoop + 1;
}
else if (mergeOrder == items[nLoop].MergeOrder) {
// We've found what we're looking for, so use this value for the merge order
nPosition = nLoop;
break;
}
}
return nPosition;
}
//There's a win32 problem that doesn't allow menus to cascade right to left
//unless we explicitely set the bit on the menu the first time it pops up
internal void UpdateRtl(bool setRightToLeftBit) {
foreach (MenuItem item in MenuItems) {
item.UpdateItemRtl(setRightToLeftBit);
item.UpdateRtl(setRightToLeftBit);
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.GetContextMenu"]/*' />
/// <devdoc>
/// Returns the ContextMenu that contains this menu. The ContextMenu
/// is at the top of this menu's parent chain.
/// Returns null if this menu is not contained in a ContextMenu.
/// This can occur if it's contained in a MainMenu or if it isn't
/// currently contained in any menu at all.
/// </devdoc>
public ContextMenu GetContextMenu() {
Menu menuT;
for (menuT = this; !(menuT is ContextMenu);) {
if (!(menuT is MenuItem)) return null;
menuT = ((MenuItem)menuT).Menu;
}
return(ContextMenu)menuT;
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.GetMainMenu"]/*' />
/// <devdoc>
/// Returns the MainMenu item that contains this menu. The MainMenu
/// is at the top of this menu's parent chain.
/// Returns null if this menu is not contained in a MainMenu.
/// This can occur if it's contained in a ContextMenu or if it isn't
/// currently contained in any menu at all.
/// </devdoc>
public MainMenu GetMainMenu() {
Menu menuT;
for (menuT = this; !(menuT is MainMenu);) {
if (!(menuT is MenuItem)) return null;
menuT = ((MenuItem)menuT).Menu;
}
return(MainMenu)menuT;
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.ItemsChanged"]/*' />
/// <devdoc>
/// </devdoc>
/// <internalonly/>
internal virtual void ItemsChanged(int change) {
switch (change) {
case CHANGE_ITEMS:
case CHANGE_VISIBLE:
DestroyMenuItems();
break;
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MatchKeyToMenuItem"]/*' />
/// <devdoc>
/// Walks the menu item collection, using a caller-supplied delegate to find one
/// with a matching access key. Walk starts at specified item index and performs one
/// full pass of the entire collection, looping back to the top if necessary.
///
/// Return value is intended for return from WM_MENUCHAR message. It includes both
/// index of matching item, and action for OS to take (execute or select). Zero is
/// used to indicate that no match was found (OS should ignore key and beep).
/// </devdoc>
/// <internalonly/>
private IntPtr MatchKeyToMenuItem(int startItem, char key, MenuItemKeyComparer comparer) {
int firstMatch = -1;
bool multipleMatches = false;
for (int i = 0; i < items.Length && !multipleMatches; ++i) {
int itemIndex = (startItem + i) % items.Length;
MenuItem mi = items[itemIndex];
if (mi != null && comparer(mi, key)) {
if (firstMatch < 0){
// VSWhidbey 218021 using Index doesnt respect hidden items.
firstMatch = mi.MenuIndex;
}
else {
multipleMatches = true;
}
}
}
if (firstMatch < 0)
return IntPtr.Zero;
int action = multipleMatches ? NativeMethods.MNC_SELECT : NativeMethods.MNC_EXECUTE;
return (IntPtr) NativeMethods.Util.MAKELONG(firstMatch, action);
}
/// Delegate type used by MatchKeyToMenuItem
private delegate bool MenuItemKeyComparer(MenuItem mi, char key);
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MergeMenu"]/*' />
/// <devdoc>
/// Merges another menu's items with this one's. Menu items are merged according to their
/// mergeType and mergeOrder properties. This function is typically used to
/// merge an MDI container's menu with that of its active MDI child.
/// </devdoc>
[
SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly") // Shipped as is in Everett
]
public virtual void MergeMenu(Menu menuSrc) {
int i, j;
MenuItem item;
MenuItem itemDst;
if (menuSrc == this)
throw new ArgumentException(SR.GetString(SR.MenuMergeWithSelf), "menuSrc");
if (menuSrc.items != null && items == null) {
MenuItems.Clear();
}
for (i = 0; i < menuSrc.ItemCount; i++) {
item = menuSrc.items[i];
switch (item.MergeType) {
default:
continue;
case MenuMerge.Add:
MenuItems.Add(FindMergePosition(item.MergeOrder), item.MergeMenu());
continue;
case MenuMerge.Replace:
case MenuMerge.MergeItems:
break;
}
int mergeOrder = item.MergeOrder;
// Can we find a menu item with a matching merge order?
// VSWhidbey 94987: Use new method to find the approximate merge position. The original
// method assumed (incorrectly) that the MergeOrder of the target menu would be sequential
// as it's guaranteed to be in the MDI imlementation of merging container and child
// menus. However, user code can call MergeMenu independently on a source and target
// menu whose MergeOrder values are not necessarily pre-sorted.
for (j = xFindMergePosition(mergeOrder); ; j++) {
if (j >= ItemCount) {
// A matching merge position could not be found,
// so simply append this menu item to the end.
MenuItems.Add(j, item.MergeMenu());
break;
}
itemDst = items[j];
if (itemDst.MergeOrder != mergeOrder) {
MenuItems.Add(j, item.MergeMenu());
break;
}
if (itemDst.MergeType != MenuMerge.Add) {
if (item.MergeType != MenuMerge.MergeItems
|| itemDst.MergeType != MenuMerge.MergeItems) {
itemDst.Dispose();
MenuItems.Add(j, item.MergeMenu());
}
else {
itemDst.MergeMenu(item);
}
break;
}
}
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.ProcessInitMenuPopup"]/*' />
/// <devdoc>
/// </devdoc>
/// <internalonly/>
internal virtual bool ProcessInitMenuPopup(IntPtr handle) {
MenuItem item = FindMenuItemInternal(FindHandle, handle);
if (item != null) {
item._OnInitMenuPopup(EventArgs.Empty);
item.CreateMenuItems();
return true;
}
return false;
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.ProcessCmdKey"]/*' />
/// <devdoc>
/// </devdoc>
/// <internalonly/>
[
System.Security.Permissions.SecurityPermissionAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Flags=System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode),
System.Security.Permissions.SecurityPermissionAttribute(System.Security.Permissions.SecurityAction.InheritanceDemand, Flags=System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)
]
protected internal virtual bool ProcessCmdKey(ref Message msg, Keys keyData) {
MenuItem item = FindMenuItemInternal(FindShortcut, (IntPtr)(int)keyData);
return item != null? item.ShortcutClick(): false;
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.SelectedMenuItemIndex"]/*' />
/// <devdoc>
/// Returns index of currently selected menu item in
/// this menu, or -1 if no item is currently selected.
/// </devdoc>
/// <internalonly/>
internal int SelectedMenuItemIndex {
get {
for (int i = 0; i < items.Length; ++i) {
MenuItem mi = items[i];
if (mi != null && mi.Selected)
return i;
}
return -1;
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.ToString"]/*' />
/// <devdoc>
/// Returns a string representation for this control.
/// </devdoc>
/// <internalonly/>
public override string ToString() {
string s = base.ToString();
return s + ", Items.Count: " + ItemCount.ToString(CultureInfo.CurrentCulture);
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.WmMenuChar"]/*' />
/// <devdoc>
/// Handles the WM_MENUCHAR message, forwarding it to the intended Menu
/// object. All the real work is done inside WmMenuCharInternal().
/// </devdoc>
/// <internalonly/>
internal void WmMenuChar(ref Message m) {
Menu menu = (m.LParam == handle) ? this : FindMenuItemInternal(FindHandle, m.LParam);
if (menu == null)
return;
char menuKey = Char.ToUpper((char) NativeMethods.Util.LOWORD(m.WParam), CultureInfo.CurrentCulture);
m.Result = menu.WmMenuCharInternal(menuKey);
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.WmMenuCharInternal"]/*' />
/// <devdoc>
/// Handles WM_MENUCHAR to provide access key support for owner-draw menu items (which
/// means *all* menu items on a menu when IsImageMarginPresent == true). Attempts to
/// simulate the exact behavior that the OS provides for non owner-draw menu items.
/// </devdoc>
/// <internalonly/>
internal IntPtr WmMenuCharInternal(char key) {
// Start looking just beyond the current selected item (otherwise just start at the top)
int startItem = (SelectedMenuItemIndex + 1) % items.Length;
// First, search for match among owner-draw items with explicitly defined access keys (eg. "S&ave")
IntPtr result = MatchKeyToMenuItem(startItem, key, new MenuItemKeyComparer(CheckOwnerDrawItemWithMnemonic));
// Next, search for match among owner-draw items with no access keys (looking at first char of item text)
if (result == IntPtr.Zero)
result = MatchKeyToMenuItem(startItem, key, new MenuItemKeyComparer(CheckOwnerDrawItemNoMnemonic));
return result;
}
/// MenuItemKeyComparer delegate used by WmMenuCharInternal
private bool CheckOwnerDrawItemWithMnemonic(MenuItem mi, char key) {
return mi.OwnerDraw &&
mi.Mnemonic == key;
}
/// MenuItemKeyComparer delegate used by WmMenuCharInternal
private bool CheckOwnerDrawItemNoMnemonic(MenuItem mi, char key) {
return mi.OwnerDraw &&
mi.Mnemonic == 0 &&
mi.Text.Length > 0 &&
Char.ToUpper(mi.Text[0], CultureInfo.CurrentCulture) == key;
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
[ListBindable(false)]
public class MenuItemCollection : IList {
private Menu owner;
/// A caching mechanism for key accessor
/// We use an index here rather than control so that we don't have lifetime
/// issues by holding on to extra references.
private int lastAccessedIndex = -1;
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.MenuItemCollection"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public MenuItemCollection(Menu owner) {
this.owner = owner;
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.this"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public virtual MenuItem this[int index] {
get {
if (index < 0 || index >= owner.ItemCount)
throw new ArgumentOutOfRangeException("index", SR.GetString(SR.InvalidArgument, "index", (index).ToString(CultureInfo.CurrentCulture)));
return owner.items[index];
}
// set not supported
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="MenuItemCollection.IList.this"]/*' />
/// <internalonly/>
object IList.this[int index] {
get {
return this[index];
}
set {
throw new NotSupportedException();
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.this"]/*' />
/// <devdoc>
/// <para>Retrieves the child control with the specified key.</para>
/// </devdoc>
public virtual MenuItem this[string key] {
get {
// We do not support null and empty string as valid keys.
if (string.IsNullOrEmpty(key)){
return null;
}
// Search for the key in our collection
int index = IndexOfKey(key);
if (IsValidIndex(index)) {
return this[index];
}
else {
return null;
}
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.Count"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public int Count {
get {
return owner.ItemCount;
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="MenuItemCollection.ICollection.SyncRoot"]/*' />
/// <internalonly/>
object ICollection.SyncRoot {
get {
return this;
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="MenuItemCollection.ICollection.IsSynchronized"]/*' />
/// <internalonly/>
bool ICollection.IsSynchronized {
get {
return false;
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="MenuItemCollection.IList.IsFixedSize"]/*' />
/// <internalonly/>
bool IList.IsFixedSize {
get {
return false;
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.IsReadOnly"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public bool IsReadOnly {
get {
return false;
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.Add"]/*' />
/// <devdoc>
/// Adds a new MenuItem to the end of this menu with the specified caption.
/// </devdoc>
public virtual MenuItem Add(string caption) {
MenuItem item = new MenuItem(caption);
Add(item);
return item;
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.Add1"]/*' />
/// <devdoc>
/// Adds a new MenuItem to the end of this menu with the specified caption,
/// and click handler.
/// </devdoc>
public virtual MenuItem Add(string caption, EventHandler onClick) {
MenuItem item = new MenuItem(caption, onClick);
Add(item);
return item;
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.Add2"]/*' />
/// <devdoc>
/// Adds a new MenuItem to the end of this menu with the specified caption,
/// click handler, and items.
/// </devdoc>
public virtual MenuItem Add(string caption, MenuItem[] items) {
MenuItem item = new MenuItem(caption, items);
Add(item);
return item;
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.Add3"]/*' />
/// <devdoc>
/// Adds a MenuItem to the end of this menu
/// MenuItems can only be contained in one menu at a time, and may not be added
/// more than once to the same menu.
/// </devdoc>
public virtual int Add(MenuItem item) {
return Add(owner.ItemCount, item);
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.Add4"]/*' />
/// <devdoc>
/// Adds a MenuItem to this menu at the specified index. The item currently at
/// that index, and all items after it, will be moved up one slot.
/// MenuItems can only be contained in one menu at a time, and may not be added
/// more than once to the same menu.
/// </devdoc>
public virtual int Add(int index, MenuItem item) {
// MenuItems can only belong to one menu at a time
if (item.Menu != null) {
// First check that we're not adding ourself, i.e. walk
// the parent chain for equality
if (owner is MenuItem) {
MenuItem parent = (MenuItem)owner;
while (parent != null) {
if (parent.Equals(item)) {
throw new ArgumentException(SR.GetString(SR.MenuItemAlreadyExists, item.Text), "item");
}
if (parent.Parent is MenuItem)
parent = (MenuItem)parent.Parent;
else
break;
}
}
//if we're re-adding an item back to the same collection
//the target index needs to be decremented since we're
//removing an item from the collection
if (item.Menu.Equals(owner) && index > 0) {
index--;
}
item.Menu.MenuItems.Remove(item);
}
// Validate our index
if (index < 0 || index > owner.ItemCount) {
throw new ArgumentOutOfRangeException("index", SR.GetString(SR.InvalidArgument,"index",(index).ToString(CultureInfo.CurrentCulture)));
}
if (owner.items == null || owner.items.Length == owner.ItemCount) {
MenuItem[] newItems = new MenuItem[owner.ItemCount < 2? 4: owner.ItemCount * 2];
if (owner.ItemCount > 0) System.Array.Copy(owner.items, 0, newItems, 0, owner.ItemCount);
owner.items = newItems;
}
System.Array.Copy(owner.items, index, owner.items, index + 1, owner.ItemCount - index);
owner.items[index] = item;
owner._itemCount++;
item.Menu = owner;
owner.ItemsChanged(CHANGE_ITEMS);
if (owner is MenuItem) {
((MenuItem) owner).ItemsChanged(CHANGE_ITEMADDED, item);
}
return index;
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.AddRange"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public virtual void AddRange(MenuItem[] items) {
if (items == null) {
throw new ArgumentNullException("items");
}
foreach(MenuItem item in items) {
Add(item);
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="MenuItemCollection.IList.Add"]/*' />
/// <internalonly/>
int IList.Add(object value) {
if (value is MenuItem) {
return Add((MenuItem)value);
}
else {
throw new ArgumentException(SR.GetString(SR.MenuBadMenuItem), "value");
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.Contains"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public bool Contains(MenuItem value) {
return IndexOf(value) != -1;
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="MenuItemCollection.IList.Contains"]/*' />
/// <internalonly/>
bool IList.Contains(object value) {
if (value is MenuItem) {
return Contains((MenuItem)value);
}
else {
return false;
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.ContainsKey"]/*' />
/// <devdoc>
/// <para>Returns true if the collection contains an item with the specified key, false otherwise.</para>
/// </devdoc>
public virtual bool ContainsKey(string key) {
return IsValidIndex(IndexOfKey(key));
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.Find"]/*' />
/// <devdoc>
/// <para>Searches for Controls by their Name property, builds up an array
/// of all the controls that match.
/// </para>
/// </devdoc>
public MenuItem [] Find(string key, bool searchAllChildren) {
if ((key == null) || (key.Length == 0)) {
throw new System.ArgumentNullException("key", SR.GetString(SR.FindKeyMayNotBeEmptyOrNull));
}
ArrayList foundMenuItems = FindInternal(key, searchAllChildren, this, new ArrayList());
// Make this a stongly typed collection.
MenuItem[] stronglyTypedfoundMenuItems = new MenuItem[foundMenuItems.Count];
foundMenuItems.CopyTo(stronglyTypedfoundMenuItems, 0);
return stronglyTypedfoundMenuItems;
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.FindInternal"]/*' />
/// <devdoc>
/// <para>Searches for Controls by their Name property, builds up an array list
/// of all the controls that match.
/// </para>
/// </devdoc>
/// <internalonly/>
private ArrayList FindInternal(string key, bool searchAllChildren, MenuItemCollection menuItemsToLookIn, ArrayList foundMenuItems) {
if ((menuItemsToLookIn == null) || (foundMenuItems == null)) {
return null; //
}
// Perform breadth first search - as it's likely people will want controls belonging
// to the same parent close to each other.
for (int i = 0; i < menuItemsToLookIn.Count; i++) {
if (menuItemsToLookIn[i] == null){
continue;
}
if (WindowsFormsUtils.SafeCompareStrings(menuItemsToLookIn[i].Name, key, /* ignoreCase = */ true)) {
foundMenuItems.Add(menuItemsToLookIn[i]);
}
}
// Optional recurive search for controls in child collections.
if (searchAllChildren){
for (int i = 0; i < menuItemsToLookIn.Count; i++) {
if (menuItemsToLookIn[i] == null){
continue;
}
if ((menuItemsToLookIn[i].MenuItems != null) && menuItemsToLookIn[i].MenuItems.Count > 0){
// if it has a valid child collecion, append those results to our collection
foundMenuItems = FindInternal(key, searchAllChildren, menuItemsToLookIn[i].MenuItems, foundMenuItems);
}
}
}
return foundMenuItems;
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.IndexOf"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public int IndexOf(MenuItem value) {
for(int index=0; index < Count; ++index) {
if (this[index] == value) {
return index;
}
}
return -1;
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="MenuItemCollection.IList.IndexOf"]/*' />
/// <internalonly/>
int IList.IndexOf(object value) {
if (value is MenuItem) {
return IndexOf((MenuItem)value);
}
else {
return -1;
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.this"]/*' />
/// <devdoc>
/// <para>The zero-based index of the first occurrence of value within the entire CollectionBase, if found; otherwise, -1.</para>
/// </devdoc>
public virtual int IndexOfKey(String key) {
// Step 0 - Arg validation
if (string.IsNullOrEmpty(key)){
return -1; // we dont support empty or null keys.
}
// step 1 - check the last cached item
if (IsValidIndex(lastAccessedIndex))
{
if (WindowsFormsUtils.SafeCompareStrings(this[lastAccessedIndex].Name, key, /* ignoreCase = */ true)) {
return lastAccessedIndex;
}
}
// step 2 - search for the item
for (int i = 0; i < this.Count; i ++) {
if (WindowsFormsUtils.SafeCompareStrings(this[i].Name, key, /* ignoreCase = */ true)) {
lastAccessedIndex = i;
return i;
}
}
// step 3 - we didn't find it. Invalidate the last accessed index and return -1.
lastAccessedIndex = -1;
return -1;
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="MenuItemCollection.IList.Insert"]/*' />
/// <internalonly/>
void IList.Insert(int index, object value) {
if (value is MenuItem) {
Add(index, (MenuItem)value);
}
else {
throw new ArgumentException(SR.GetString(SR.MenuBadMenuItem),"value");
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.IsValidIndex"]/*' />
/// <devdoc>
/// <para>Determines if the index is valid for the collection.</para>
/// </devdoc>
/// <internalonly/>
private bool IsValidIndex(int index) {
return ((index >= 0) && (index < this.Count));
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.Clear"]/*' />
/// <devdoc>
/// Removes all existing MenuItems from this menu
/// </devdoc>
public virtual void Clear() {
if (owner.ItemCount > 0) {
for (int i = 0; i < owner.ItemCount; i++) {
owner.items[i].Menu = null;
}
owner._itemCount = 0;
owner.items = null;
owner.ItemsChanged(CHANGE_ITEMS);
if (owner is MenuItem) {
((MenuItem)(owner)).UpdateMenuItem(true);
}
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.CopyTo"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public void CopyTo(Array dest, int index) {
if (owner.ItemCount > 0) {
System.Array.Copy(owner.items, 0, dest, index, owner.ItemCount);
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.GetEnumerator"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public IEnumerator GetEnumerator() {
return new WindowsFormsUtils.ArraySubsetEnumerator(owner.items, owner.ItemCount);
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.RemoveAt"]/*' />
/// <devdoc>
/// Removes the item at the specified index in this menu. All subsequent
/// items are moved up one slot.
/// </devdoc>
public virtual void RemoveAt(int index) {
if (index < 0 || index >= owner.ItemCount) {
throw new ArgumentOutOfRangeException("index", SR.GetString(SR.InvalidArgument,"index",(index).ToString(CultureInfo.CurrentCulture)));
}
MenuItem item = owner.items[index];
item.Menu = null;
owner._itemCount--;
System.Array.Copy(owner.items, index + 1, owner.items, index, owner.ItemCount - index);
owner.items[owner.ItemCount] = null;
owner.ItemsChanged(CHANGE_ITEMS);
//if the last item was removed, clear the collection
//
if (owner.ItemCount == 0) {
Clear();
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.RemoveByKey"]/*' />
/// <devdoc>
/// <para>Removes the menu iteml with the specified key.</para>
/// </devdoc>
public virtual void RemoveByKey(string key) {
int index = IndexOfKey(key);
if (IsValidIndex(index)) {
RemoveAt(index);
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="Menu.MenuItemCollection.Remove"]/*' />
/// <devdoc>
/// Removes the specified item from this menu. All subsequent
/// items are moved down one slot.
/// </devdoc>
public virtual void Remove(MenuItem item) {
if (item.Menu == owner) {
RemoveAt(item.Index);
}
}
/// <include file='doc\Menu.uex' path='docs/doc[@for="MenuItemCollection.IList.Remove"]/*' />
/// <internalonly/>
void IList.Remove(object value) {
if (value is MenuItem) {
Remove((MenuItem)value);
}
}
}
}
}
|