diff --git a/src/BetterControls/BetterMenu/BetterContextMenu.cs b/src/BetterControls/BetterMenu/BetterContextMenu.cs
new file mode 100644
index 0000000..2a723c1
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterContextMenu.cs
@@ -0,0 +1,45 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System.ComponentModel;
+using System.Drawing;
+
+namespace BetterControls
+{
+ ///
+ /// Represents a context menu.
+ ///
+ [ToolboxItem(true)]
+ [DesignTimeVisible(true)]
+ [ToolboxBitmap(typeof(BetterToolbar), "BetterContextMenuIcon.bmp")]
+ public class BetterContextMenu : BetterMenuRoot
+ {
+ ///
+ /// Initialize a new instance of .
+ ///
+ public BetterContextMenu() { }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenu.Events.cs b/src/BetterControls/BetterMenu/BetterMenu.Events.cs
new file mode 100644
index 0000000..e40688b
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenu.Events.cs
@@ -0,0 +1,93 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System;
+
+namespace BetterControls
+{
+ ///
+ /// Wrapper of the Windows Menu classes.
+ ///
+ partial class BetterMenu
+ {
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnAutoSizeItemsChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ AutoSizeItemsChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnCustomStatusWidthChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ CustomStatusWidthChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnDefaultButtonChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ DefaultButtonChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This event is raised has been changed.
+ ///
+ public event EventHandler AutoSizeItemsChanged;
+
+ ///
+ /// This event is raised has been changed.
+ ///
+ public event EventHandler CustomStatusWidthChanged;
+
+ ///
+ /// This event is raised has been changed.
+ ///
+ public event EventHandler DefaultButtonChanged;
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenu.Initialization.cs b/src/BetterControls/BetterMenu/BetterMenu.Initialization.cs
new file mode 100644
index 0000000..cd21c34
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenu.Initialization.cs
@@ -0,0 +1,91 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System;
+
+namespace BetterControls
+{
+ ///
+ /// Wrapper of the Windows Menu classes.
+ ///
+ partial class BetterMenu
+ {
+ ///
+ ///
+ ///
+ protected override void CreateHandle()
+ {
+ if (!IsHandleCreated)
+ _handle = UnsafeNativeMethods.CreatePopupMenu();
+
+ base.CreateHandle();
+ }
+
+ ///
+ ///
+ ///
+ protected override void DestroyHandle()
+ {
+ if (IsHandleCreated)
+ {
+ UnsafeNativeMethods.DestroyMenu(GetHandleRef());
+ _handle = IntPtr.Zero;
+
+ foreach (BetterMenuItem item in Items)
+ item.DestroyHandle();
+ }
+
+ base.DestroyHandle();
+ }
+
+ ///
+ ///
+ ///
+ ///
+ protected override void OnHandleCreated(EventArgs e)
+ {
+ base.OnHandleCreated(e);
+
+ InitializeItems();
+ }
+
+ ///
+ /// Initializes items in the menu.
+ ///
+ private void InitializeItems()
+ {
+ foreach (BetterMenuItem item in Items)
+ {
+ if (!item.Visible)
+ continue;
+
+ NativeMethods.MENUITEMINFO_T structure = item.ComputeMenuItemInfoT();
+
+ UnsafeNativeMethods.InsertMenuItem(GetHandleRef(), -1, true, structure);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenu.cs b/src/BetterControls/BetterMenu/BetterMenu.cs
new file mode 100644
index 0000000..f5e30b0
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenu.cs
@@ -0,0 +1,294 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using BetterControls.ComponentModel;
+using System;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+
+namespace BetterControls
+{
+ ///
+ /// Wrapper of the Windows Menu classes.
+ ///
+ [ToolboxItem(false)]
+ [DesignTimeVisible(false)]
+ [Designer("BetterMenuDesigner")]
+ public abstract partial class BetterMenu : BetterMenuBase
+ {
+ ///
+ /// Initialize a new instance of .
+ ///
+ private protected BetterMenu()
+ {
+ MenuReferences.Add(this, MenuReference);
+
+ _items = new BetterMenuItemCollection(this);
+ _menuReference = new BetterMenuReference(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The owner menu as an instance of .
+ private protected BetterMenu(BetterMenu ownerMenu)
+ : base(ownerMenu)
+ { }
+
+ private bool _autoSizeItems = true;
+ private int _customStatusWidth;
+ private BetterMenuButton _defaultButton;
+ private BetterMenuItemCollection _items;
+ private BetterMenuReference _menuReference;
+
+ ///
+ /// Gets or sets a value indicating whether or not the width of the status column of items should be automatically computed.
+ ///
+ [Category(Categories.Behavior)]
+ [Description("Value indicating whether or not the width of the status column of items should be automatically computed.")]
+ [DefaultValue(true)]
+ [Localizable(false)]
+ public virtual bool AutoSizeItems
+ {
+ get => _autoSizeItems;
+ set
+ {
+ if (AutoSizeItems != value)
+ {
+ _autoSizeItems = value;
+
+ OnAutoSizeItemsChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the width of the status column of items. This value will be ignored unless is set to .
+ ///
+ [Category(Categories.Behavior)]
+ [Description("The width of the status column of items.")]
+ [DefaultValue(0)]
+ [Localizable(false)]
+ public virtual int CustomStatusWidth
+ {
+ get => _customStatusWidth;
+ set
+ {
+ if (CustomStatusWidth != value)
+ {
+ _customStatusWidth = value;
+
+ OnCustomStatusWidthChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the default button in the menu items of this menu item. This item will usually appear as bold.
+ ///
+ [Browsable(false)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public BetterMenuButton DefaultButton
+ {
+ get => _defaultButton;
+ set
+ {
+ if (DefaultButton != value)
+ {
+ _defaultButton = value;
+
+ if (DefaultButton != null)
+ {
+ UnsafeNativeMethods.SetMenuDefaultItem(GetHandleRef(), DefaultButton.UniqueIdentifier, false);
+ }
+ else
+ {
+ UnsafeNativeMethods.SetMenuDefaultItem(GetHandleRef(), -1, false);
+ }
+
+ OnDefaultButtonChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets the focused push button in the menu items of this menu item.
+ ///
+ [Browsable(false)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public BetterMenuButton FocusedPushbutton => _focusedButton;
+
+ ///
+ /// Gets the collection of menu items for this menu.
+ ///
+ [Category(Categories.Behavior)]
+ [Description("The collection of menu items for this menu.")]
+ [DefaultValue(null)]
+ [Localizable(false)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
+ public virtual BetterMenuItemCollection Items => _items;
+
+ ///
+ /// Gets the menu reference for this menu as an instance of .
+ ///
+ internal BetterMenuReference MenuReference => _menuReference;
+
+ ///
+ /// Gets the root menu as an instance of .
+ ///
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual BetterMenuRoot MenuRoot
+ {
+ get
+ {
+ if (OwnerMenu != null)
+ return OwnerMenu.MenuRoot;
+
+ return null;
+ }
+ }
+
+ ///
+ /// Gets the unique identifier of this menu item, relative to the the entire process scope.
+ ///
+ [Browsable(false)]
+ public int UniqueIdentifier
+ {
+ get
+ {
+ if (MenuReference != null)
+ return MenuReference.UniqueIdentifier;
+
+ return -1;
+ }
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ #region Private Member Fields
+
+ public IntPtr _handle;
+ internal BetterMenuButton _focusedButton;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ ///
+ ///
+ [Browsable(false)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public override IntPtr Handle
+ {
+ get
+ {
+ IntPtr handle = base.Handle;
+
+
+
+ return _handle;
+ }
+ }
+
+ ///
+ ///
+ ///
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public override bool IsHandleCreated => _handle != IntPtr.Zero;
+
+ ///
+ /// Gets an exhaustive collection of menu items directly or indirectly under the root menu.
+ ///
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ static internal BetterMenuReferenceCollection MenuReferences { get; } = new BetterMenuReferenceCollection();
+
+ #endregion
+
+ #region Miscellaneous
+
+ ///
+ ///
+ ///
+ ///
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && Items != null)
+ {
+ int index = Items.Count - 1;
+
+ while (index >= 0)
+ {
+ BetterMenu menu = Items[index--];
+
+ if (menu.Site != null && menu.Site.Container != null)
+ {
+ menu.Site.Container.Remove(menu);
+ }
+
+ //menu.OwnerControl2 = null;
+ menu.Dispose();
+ }
+
+ _items = null;
+ }
+ if (IsHandleCreated)
+ {
+ UnsafeNativeMethods.DestroyMenu(new HandleRef(this, Handle));
+
+ _handle = IntPtr.Zero;
+ }
+
+ base.Dispose(disposing);
+ }
+
+
+
+
+
+
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenuBase.cs b/src/BetterControls/BetterMenu/BetterMenuBase.cs
new file mode 100644
index 0000000..4a75e29
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenuBase.cs
@@ -0,0 +1,78 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+
+namespace BetterControls
+{
+ ///
+ /// Wrapper of the Windows Menu classes.
+ ///
+ public abstract class BetterMenuBase : HandledComponentCollectionElement
+ {
+ ///
+ /// Initialize a new instance of .
+ ///
+ private protected BetterMenuBase() { }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The owner menu as an instance of .
+ private protected BetterMenuBase(BetterMenu ownerMenu)
+ : base(ownerMenu)
+ { }
+
+ ///
+ /// Gets the owner menu as an instance of .
+ ///
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public BetterMenu OwnerMenu
+ {
+ get
+ {
+ if (OwnerElement != null)
+ return (BetterMenu)OwnerElement;
+
+ return null;
+ }
+ }
+
+ ///
+ /// Gets a for the current component.
+ ///
+ /// An instance of .
+ protected internal virtual HandleRef GetHandleRef()
+ {
+ if (!IsHandleCreated)
+ return default;
+
+ return new HandleRef(this, Handle);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenuButtonClickEventArgs.cs b/src/BetterControls/BetterMenu/BetterMenuButtonClickEventArgs.cs
new file mode 100644
index 0000000..08e271e
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenuButtonClickEventArgs.cs
@@ -0,0 +1,55 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System;
+
+namespace BetterControls
+{
+ ///
+ /// Event arguments for when a direct or indirect descendant menu button of a menu root is clicked.
+ ///
+ public class BetterMenuButtonClickEventArgs : EventArgs
+ {
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The menu item that was clicked.
+ public BetterMenuButtonClickEventArgs(BetterMenuButton menuItem)
+ {
+ MenuItem = menuItem ?? throw new ArgumentNullException(nameof(menuItem));
+ }
+
+ ///
+ /// Gets the menu item that was clicked.
+ ///
+ public BetterMenuButton MenuItem { get; }
+
+ ///
+ /// Gets a value indicating whether or not the menu item that was clicked is a direct descendant of the menu root.
+ ///
+ public bool DirectDescendant => MenuItem.OwnerMenu == MenuItem.MenuRoot;
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenuButtonFocusedEventArgs.cs b/src/BetterControls/BetterMenu/BetterMenuButtonFocusedEventArgs.cs
new file mode 100644
index 0000000..ccf1231
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenuButtonFocusedEventArgs.cs
@@ -0,0 +1,55 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System;
+
+namespace BetterControls
+{
+ ///
+ /// Event arguments for when a direct or indirect descendant menu button of a menu root is focused.
+ ///
+ public class BetterMenuButtonFocusedEventArgs : EventArgs
+ {
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The menu item that was focused.
+ public BetterMenuButtonFocusedEventArgs(BetterMenuButton menuItem)
+ {
+ MenuItem = menuItem ?? throw new ArgumentNullException(nameof(menuItem));
+ }
+
+ ///
+ /// Gets the menu item that was focused.
+ ///
+ public BetterMenuButton MenuItem { get; }
+
+ ///
+ /// Gets a value indicating whether or not the menu item that was focused is a direct descendant of the menu root.
+ ///
+ public bool DirectDescendant => MenuItem.OwnerMenu == MenuItem.MenuRoot;
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenuButtonImageAlign.cs b/src/BetterControls/BetterMenu/BetterMenuButtonImageAlign.cs
new file mode 100644
index 0000000..1280728
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenuButtonImageAlign.cs
@@ -0,0 +1,71 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+namespace BetterControls
+{
+ ///
+ /// The different alignments for images within menu items.
+ ///
+ public enum BetterMenuButtonImageAlign
+ {
+ ///
+ /// Top-left alignment.
+ ///
+ TopLeft = 0,
+ ///
+ /// Top-middle alignment.
+ ///
+ TopMiddle = 1,
+ ///
+ /// Top-right alignment.
+ ///
+ TopRight = 2,
+ ///
+ /// Middle-left alignment.
+ ///
+ MiddleLeft = 3,
+ ///
+ /// Middle-middle alignment.
+ ///
+ MiddleMiddle = 4,
+ ///
+ /// Middle-right alignment.
+ ///
+ MiddleRight = 5,
+ ///
+ /// Bottom-left alignment.
+ ///
+ BottomLeft = 6,
+ ///
+ /// Bottom-middle alignment.
+ ///
+ BottomMiddle = 7,
+ ///
+ /// Bottom-right alignment.
+ ///
+ BottomRight = 8
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenuButtonImageIndexer.cs b/src/BetterControls/BetterMenu/BetterMenuButtonImageIndexer.cs
new file mode 100644
index 0000000..f92879f
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenuButtonImageIndexer.cs
@@ -0,0 +1,71 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using BetterControls.Drawing;
+using System;
+using System.ComponentModel;
+using System.Windows.Forms;
+
+namespace BetterControls
+{
+ ///
+ /// Used to provide a mechanism for a menu button to support using both image indexes and image keys.
+ ///
+ public class BetterMenuButtonImageIndexer : ImageIndexer
+ {
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The parent toolbar item as an instance of .
+ public BetterMenuButtonImageIndexer(BetterMenuButton ownerMenuButton)
+ {
+ _ownerMenuButton = ownerMenuButton ?? throw new ArgumentNullException(nameof(ownerMenuButton));
+ }
+
+ private BetterMenuButton _ownerMenuButton;
+
+ ///
+ /// Gets the owner menu item as an instance of .
+ ///
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public BetterMenuButton OwnerMenuButton => _ownerMenuButton;
+
+ ///
+ ///
+ ///
+ protected override ImageList ImageList
+ {
+ get
+ {
+ if (OwnerMenuButton != null)
+ return OwnerMenuButton.MenuRoot.ImageList;
+
+ return null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenuExtensions.cs b/src/BetterControls/BetterMenu/BetterMenuExtensions.cs
new file mode 100644
index 0000000..ba035cc
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenuExtensions.cs
@@ -0,0 +1,50 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System;
+
+namespace BetterControls
+{
+ ///
+ /// Extension methods for menus.
+ ///
+ public static class BetterMenuExtensions
+ {
+ ///
+ /// Adds a to the collection of menu items.
+ ///
+ /// The collection of menu items to add to.
+ public static void AddSeparator(this BetterMenuItemCollection items)
+ {
+ if (items is null)
+ {
+ throw new ArgumentNullException(nameof(items));
+ }
+
+ items.Add(new BetterMenuSeparator());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenuHighlightBehavior.cs b/src/BetterControls/BetterMenu/BetterMenuHighlightBehavior.cs
new file mode 100644
index 0000000..e2b8ea2
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenuHighlightBehavior.cs
@@ -0,0 +1,21 @@
+namespace BetterControls
+{
+ ///
+ /// Highlight behavior for when a menu is shown.
+ ///
+ public enum BetterMenuHighlightBehavior
+ {
+ ///
+ /// Doesn't change the highlight behavior. Menu items with set to will continue to highlight.
+ ///
+ Default = 0,
+ ///
+ /// Highlights the first item in addition to any other items configured to highlight. Menu items with set to will continue to highlight.
+ ///
+ HighlightFirstItem = 1,
+ ///
+ /// Highlights the first item only. Menu items with set to will not continue to highlight.
+ ///
+ HighlightFirstItemExclusive = 2
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenuItemChangeType.cs b/src/BetterControls/BetterMenu/BetterMenuItemChangeType.cs
new file mode 100644
index 0000000..e9f6314
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenuItemChangeType.cs
@@ -0,0 +1,11 @@
+namespace BetterControls
+{
+ ///
+ /// The different ways a menu item can be changed.
+ ///
+ public enum BetterMenuItemChangeType
+ {
+ SoftChange = 0,
+ HardChange = 1
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenuItemCheckStyle.cs b/src/BetterControls/BetterMenu/BetterMenuItemCheckStyle.cs
new file mode 100644
index 0000000..1c9271d
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenuItemCheckStyle.cs
@@ -0,0 +1,47 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+namespace BetterControls
+{
+ ///
+ /// The different check styles for a better menu item.
+ ///
+ public enum BetterMenuItemCheckStyle
+ {
+ ///
+ /// The item is not checked.
+ ///
+ Disabled = 0,
+ ///
+ /// The item is checked with a check mark.
+ ///
+ Check = 1,
+ ///
+ /// The item is checked with a radio style check mark.
+ ///
+ Radio = 2
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenuItemCollection.cs b/src/BetterControls/BetterMenu/BetterMenuItemCollection.cs
new file mode 100644
index 0000000..e70861f
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenuItemCollection.cs
@@ -0,0 +1,372 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using BetterControls.Collections;
+using System;
+using System.Collections;
+using System.ComponentModel;
+using System.Drawing;
+using System.Drawing.Design;
+using System.Windows.Forms;
+
+namespace BetterControls
+{
+ ///
+ /// Represents a collection of .
+ ///
+ [Editor("BetterMenuItemCollectionEditor", typeof(UITypeEditor))]
+ public class BetterMenuItemCollection : ElementCollection, IEnumerable
+ {
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The owner component as an instance of .
+ internal BetterMenuItemCollection(BetterMenu ownerMenu)
+ : base(ownerMenu)
+ { }
+
+ ///
+ /// Gets the owner menu as an instance of .
+ ///
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected BetterMenu OwnerMenu
+ {
+ get
+ {
+ if (OwnerElement != null)
+ return (BetterMenu)OwnerElement;
+
+ return null;
+ }
+ }
+
+ ///
+ /// Adds a new instance of to the collection.
+ ///
+ /// The text of the menu item.
+ public void Add(string text)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ Add(new BetterMenuPushButton()
+ {
+ Text = text
+ });
+ }
+
+ ///
+ /// Adds a new instance of to the collection.
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ public void Add(string text, string description)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ if (description is null)
+ {
+ throw new ArgumentNullException(nameof(description));
+ }
+
+ Add(new BetterMenuPushButton()
+ {
+ Text = text,
+ Description = description
+ });
+ }
+
+ ///
+ /// Adds a new instance of to the collection.
+ ///
+ /// The text of the menu item.
+ /// The index of the image from the parent most menu image list to be shown in the menu item.
+ public void Add(string text, int imageIndex)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ Add(new BetterMenuPushButton()
+ {
+ Text = text,
+ ImageIndex = imageIndex
+ });
+ }
+
+ ///
+ /// Adds a new instance of to the collection.
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ /// The index of the image from the parent most menu image list to be shown in the menu item.
+ public void Add(string text, string description, int imageIndex)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ if (description is null)
+ {
+ throw new ArgumentNullException(nameof(description));
+ }
+
+ Add(new BetterMenuPushButton()
+ {
+ Text = text,
+ Description = description,
+ ImageIndex = imageIndex
+ });
+ }
+
+ ///
+ /// Adds a new instance of to the collection.
+ ///
+ /// The text of the menu item.
+ /// The image to be shown in the menu item.
+ public void Add(string text, Image image)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ Add(new BetterMenuPushButton()
+ {
+ Text = text,
+ Image = image
+ });
+ }
+
+ ///
+ /// Adds a new instance of to the collection.
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ /// The image to be shown in the menu item.
+ public void Add(string text, string description, Image image)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ if (description is null)
+ {
+ throw new ArgumentNullException(nameof(description));
+ }
+
+ Add(new BetterMenuPushButton()
+ {
+ Text = text,
+ Description = description,
+ Image = image
+ });
+ }
+
+ ///
+ /// Adds a new instance of to the collection.
+ ///
+ /// The text of the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public void Add(string text, Shortcut shortcut)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ Add(new BetterMenuPushButton()
+ {
+ Text = text,
+ Shortcut = shortcut
+ });
+ }
+
+ ///
+ /// Adds a new instance of to the collection.
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public void Add(string text, string description, Shortcut shortcut)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ if (description is null)
+ {
+ throw new ArgumentNullException(nameof(description));
+ }
+
+ Add(new BetterMenuPushButton()
+ {
+ Text = text,
+ Description = description,
+ Shortcut = shortcut
+ });
+ }
+
+ ///
+ /// Adds a new instance of to the collection.
+ ///
+ /// The text of the menu item.
+ /// The index of the image from the parent most menu image list to be shown in the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public void Add(string text, int imageIndex, Shortcut shortcut)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ Add(new BetterMenuPushButton()
+ {
+ Text = text,
+ ImageIndex = imageIndex,
+ Shortcut = shortcut
+ });
+ }
+
+ ///
+ /// Adds a new instance of to the collection.
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ /// The index of the image from the parent most menu image list to be shown in the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public void Add(string text, string description, int imageIndex, Shortcut shortcut)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ if (description is null)
+ {
+ throw new ArgumentNullException(nameof(description));
+ }
+
+ Add(new BetterMenuPushButton()
+ {
+ Text = text,
+ Description = description,
+ ImageIndex = imageIndex,
+ Shortcut = shortcut
+ });
+ }
+
+ ///
+ /// Adds a new instance of to the collection.
+ ///
+ /// The text of the menu item.
+ /// The image to be shown in the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public void Add(string text, Image image, Shortcut shortcut)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ Add(new BetterMenuPushButton()
+ {
+ Text = text,
+ Image = image,
+ Shortcut = shortcut
+ });
+ }
+
+ ///
+ /// Adds a new instance of to the collection.
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ /// The image to be shown in the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public void Add(string text, string description, Image image, Shortcut shortcut)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ if (description is null)
+ {
+ throw new ArgumentNullException(nameof(description));
+ }
+
+ Add(new BetterMenuPushButton()
+ {
+ Text = text,
+ Description = description,
+ Image = image,
+ Shortcut = shortcut
+ });
+ }
+
+
+
+
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override bool Remove(BetterMenuItem item)
+ {
+ if (item is null)
+ {
+ throw new ArgumentNullException(nameof(item));
+ }
+
+ item.MenuReference.Dispose();
+ BetterMenu.MenuReferences.Remove(item);
+
+ return base.Remove(item);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public override object Clone()
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenuItemPaintStatusEventArgs.cs b/src/BetterControls/BetterMenu/BetterMenuItemPaintStatusEventArgs.cs
new file mode 100644
index 0000000..eb6f7b5
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenuItemPaintStatusEventArgs.cs
@@ -0,0 +1,63 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System;
+using System.Drawing;
+
+namespace BetterControls
+{
+ ///
+ /// Event arguments for when the status part of each menu item is painted.
+ ///
+ public class BetterMenuItemPaintStatusEventArgs
+ {
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The graphics associated with this paint operation.
+ /// The outer bounds of this paint operation.
+ internal BetterMenuItemPaintStatusEventArgs(Graphics graphics, Rectangle bounds)
+ {
+ Graphics = graphics ?? throw new ArgumentNullException(nameof(graphics));
+ Bounds = bounds;
+ }
+
+ ///
+ /// Gets the device context associated with this paint operation.
+ ///
+ public IDeviceContext DeviceContext => Graphics;
+
+ ///
+ /// Gets the graphics associated with this paint operation.
+ ///
+ public Graphics Graphics { get; }
+
+ ///
+ /// Gets the outer bounds of this paint operation.
+ ///
+ public Rectangle Bounds { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenuReference.cs b/src/BetterControls/BetterMenu/BetterMenuReference.cs
new file mode 100644
index 0000000..e3559db
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenuReference.cs
@@ -0,0 +1,79 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System;
+using System.Windows.Forms;
+
+namespace BetterControls
+{
+ ///
+ /// Used to create a low level reference of a specified menu.
+ ///
+ internal class BetterMenuReference : ICommandExecutor, IDisposable
+ {
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The menu associated with this menu reference.
+ public BetterMenuReference(BetterMenu menu)
+ {
+ _menu = menu ?? throw new ArgumentNullException(nameof(menu));
+
+ _globalCommand = new GlobalCommand(this);
+ }
+
+ public BetterMenu _menu;
+ private GlobalCommand _globalCommand;
+
+ ///
+ /// Gets the associated with this menu reference.
+ ///
+ public BetterMenu Menu => _menu;
+
+ ///
+ /// Gets the unique identifier of this menu item, relative to the the entire process scope.
+ ///
+ public int UniqueIdentifier => _globalCommand.Identifier;
+
+ ///
+ ///
+ ///
+ void ICommandExecutor.Execute()
+ {
+ if (_menu is BetterMenuButton button)
+ button.PerformClick();
+ }
+
+ ///
+ ///
+ ///
+ public void Dispose()
+ {
+ if (_globalCommand != null)
+ _globalCommand.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenuReferenceCollection.cs b/src/BetterControls/BetterMenu/BetterMenuReferenceCollection.cs
new file mode 100644
index 0000000..2476434
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenuReferenceCollection.cs
@@ -0,0 +1,8 @@
+using System.Collections.Generic;
+
+namespace BetterControls
+{
+ internal class BetterMenuReferenceCollection : Dictionary
+ {
+ }
+}
diff --git a/src/BetterControls/BetterMenu/BetterMenuRoot.Events.cs b/src/BetterControls/BetterMenu/BetterMenuRoot.Events.cs
new file mode 100644
index 0000000..81e4cf0
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenuRoot.Events.cs
@@ -0,0 +1,112 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System;
+
+namespace BetterControls
+{
+ ///
+ /// Extend this class to create a root menu.
+ ///
+ partial class BetterMenuRoot
+ {
+ ///
+ /// This method is raised after the menu has collapsed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnCollapse(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ Collapse?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when a direct or indirect descendant menu button is clicked.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnMenuItemClick(BetterMenuButtonClickEventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ MenuItemClick?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when a direct or indirect descendant menu button is focused.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnMenuItemFocused(BetterMenuButtonFocusedEventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ MenuItemFocused?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised before the menu is shown.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnPopup(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ Popup?.Invoke(this, e);
+ }
+
+ ///
+ /// This event is raised after the menu has collapsed.
+ ///
+ public event EventHandler Collapse;
+
+ ///
+ /// This event is raised when a direct or indirect descendant menu button is clicked.
+ ///
+ public event EventHandler MenuItemClick;
+
+ ///
+ /// This event is raised when a direct or indirect descendant menu button is focused.
+ ///
+ public event EventHandler MenuItemFocused;
+
+ ///
+ /// This event is raised before the menu is shown.
+ ///
+ public event EventHandler Popup;
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenuRoot.cs b/src/BetterControls/BetterMenu/BetterMenuRoot.cs
new file mode 100644
index 0000000..e6bebdb
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenuRoot.cs
@@ -0,0 +1,255 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using BetterControls.ComponentModel;
+using System;
+using System.ComponentModel;
+using System.Drawing;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+
+namespace BetterControls
+{
+ ///
+ /// Extend this class to create a root menu.
+ ///
+ public abstract partial class BetterMenuRoot : BetterMenu
+ {
+ ///
+ /// Initialize a new instance of .
+ ///
+ public BetterMenuRoot() { }
+
+ private ImageList _imageList;
+ private BetterMenuHighlightBehavior _showHighlightBehavior = BetterMenuHighlightBehavior.Default;
+ private Control _sourceControl;
+ private BetterMenuWndProcSubclass _wndProcSubclass;
+
+ ///
+ /// Gets the image list for the menu root.
+ ///
+ [Category(Categories.Appearance)]
+ [Description("The image list for the menu root.")]
+ [DefaultValue(null)]
+ [Localizable(false)]
+ public virtual ImageList ImageList
+ {
+ get => _imageList;
+ set
+ {
+ if (ImageList != value)
+ {
+ _imageList = value;
+ }
+ }
+ }
+
+ ///
+ ///
+ ///
+ public override BetterMenuRoot MenuRoot => this;
+
+ ///
+ /// Gets or sets a value from indicating the highlight behavior when this root menu is shown.
+ ///
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ internal virtual BetterMenuHighlightBehavior ShowHighlightBehavior
+ {
+ get => _showHighlightBehavior;
+ set
+ {
+ if (ShowHighlightBehavior != value)
+ {
+ _showHighlightBehavior = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets the control that the menu is opened against.
+ ///
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public Control SourceControl => _sourceControl;
+
+ ///
+ /// This method is raised when a direct or indirect descendant menu button is clicked.
+ ///
+ /// The menu item that was clicked.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void DescendantClicked(BetterMenuButton menuItem)
+ {
+ if (menuItem is null)
+ {
+ throw new ArgumentNullException(nameof(menuItem));
+ }
+
+ OnMenuItemClick(new BetterMenuButtonClickEventArgs(menuItem));
+ }
+
+ ///
+ /// This method is raised when a direct or indirect descendant menu button is focused.
+ ///
+ /// The menu item that was focused.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void DescendantFocused(BetterMenuButton menuItem)
+ {
+ if (menuItem is null)
+ {
+ throw new ArgumentNullException(nameof(menuItem));
+ }
+
+ OnMenuItemFocused(new BetterMenuButtonFocusedEventArgs(menuItem));
+ }
+
+ ///
+ /// Displays the menu at the specified position.
+ ///
+ /// The control that owns the menu.
+ /// The position relative to the screen that the menu should be shown from.
+ public virtual void Show(Control control, Point position)
+ {
+ if (control is null)
+ {
+ throw new ArgumentNullException(nameof(control));
+ }
+
+ int flags = NativeMethods.TPM_VERTICAL | NativeMethods.TPM_RIGHTBUTTON;
+
+ Show(control, position, flags, BetterMenuHighlightBehavior.Default);
+ }
+
+ ///
+ /// Displays the menu at the specified position.
+ ///
+ /// The control that owns the menu.
+ /// The position relative to the screen that the menu should be shown from.
+ /// The direction relative to the position that the menu should be shown from.
+ public virtual void Show(Control control, Point position, LeftRightAlignment alignment)
+ {
+ if (control is null)
+ {
+ throw new ArgumentNullException(nameof(control));
+ }
+
+ int flags = NativeMethods.TPM_VERTICAL | NativeMethods.TPM_RIGHTBUTTON;
+
+ if (alignment == LeftRightAlignment.Left)
+ {
+ flags |= NativeMethods.TPM_RIGHTALIGN;
+ }
+ else
+ {
+ flags |= NativeMethods.TPM_LEFTALIGN;
+ }
+
+ Show(control, position, flags, BetterMenuHighlightBehavior.Default);
+ }
+
+ ///
+ /// Displays the menu at the specified position.
+ ///
+ /// The control that owns the menu.
+ /// The position relative to the screen that the menu should be shown from.
+ /// Value from indicating the highlight behavior when this root menu is shown.
+ public virtual void Show(Control control, Point position, BetterMenuHighlightBehavior highlightBehavior)
+ {
+ if (control is null)
+ {
+ throw new ArgumentNullException(nameof(control));
+ }
+
+ int flags = NativeMethods.TPM_VERTICAL | NativeMethods.TPM_RIGHTBUTTON;
+
+ Show(control, position, flags, highlightBehavior);
+ }
+
+ ///
+ /// Displays the menu at the specified position.
+ ///
+ /// The control that owns the menu.
+ /// The position relative to the screen that the menu should be shown from.
+ /// The direction relative to the position that the menu should be shown from.
+ /// Value from indicating the highlight behavior when this root menu is shown.
+ public virtual void Show(Control control, Point position, LeftRightAlignment alignment, BetterMenuHighlightBehavior highlightBehavior)
+ {
+ if (control is null)
+ {
+ throw new ArgumentNullException(nameof(control));
+ }
+
+ int flags = NativeMethods.TPM_VERTICAL | NativeMethods.TPM_RIGHTBUTTON;
+
+ if (alignment == LeftRightAlignment.Left)
+ {
+ flags |= NativeMethods.TPM_RIGHTALIGN;
+ }
+ else
+ {
+ flags |= NativeMethods.TPM_LEFTALIGN;
+ }
+
+ Show(control, position, flags, highlightBehavior);
+ }
+
+ ///
+ /// Displays the menu at the specified position.
+ ///
+ /// The control that owns the menu.
+ /// The position relative to the screen that the menu should be shown from.
+ /// The native flags used to control the behavior of the menu.
+ /// Value from indicating the highlight behavior when this root menu is shown.
+ private protected virtual void Show(Control control, Point position, int flags, BetterMenuHighlightBehavior highlightBehavior)
+ {
+ if (control is null)
+ {
+ throw new ArgumentNullException(nameof(control));
+ }
+
+ _sourceControl = control;
+ _wndProcSubclass = new BetterMenuWndProcSubclass(SourceControl);
+
+ position = control.PointToScreen(position);
+
+ ShowHighlightBehavior = highlightBehavior;
+ RecreateHandle();
+
+ OnPopup(new EventArgs());
+
+ SafeNativeMethods.TrackPopupMenuEx(GetHandleRef(), flags, position.X, position.Y, new HandleRef(control, control.Handle), null);
+
+ ShowHighlightBehavior = BetterMenuHighlightBehavior.Default;
+
+ OnCollapse(new EventArgs());
+
+ _wndProcSubclass.ReleaseHandle();
+ _wndProcSubclass = null;
+ _sourceControl = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenuWndProcSubclass.MessageLoop.cs b/src/BetterControls/BetterMenu/BetterMenuWndProcSubclass.MessageLoop.cs
new file mode 100644
index 0000000..6a9e9f7
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenuWndProcSubclass.MessageLoop.cs
@@ -0,0 +1,87 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System.Windows.Forms;
+
+namespace BetterControls
+{
+ ///
+ /// Used to intercept the message loop of the source control of a menu when opened.
+ ///
+ partial class BetterMenuWndProcSubclass
+ {
+ ///
+ ///
+ ///
+ ///
+ protected override void WndProc(ref Message m)
+ {
+ switch (m.Msg)
+ {
+ case NativeMethods.WM_MENUSELECT:
+ if (WmMenuSelect(ref m))
+ return;
+
+ break;
+ }
+
+ base.WndProc(ref m);
+ }
+
+ ///
+ /// Handles the WM_MENUSELECT message.
+ ///
+ private protected virtual bool WmMenuSelect(ref Message m)
+ {
+ // If the menu item is a popup item (i.e. it has sub-items) then the data sent
+ // with WM_MENUSELECT to identify which menu was selected differs than any other
+ // menu item. An index to the popup item is sent instead of command identifier.
+ int identifier = NativeMethods.Util.LOWORD(m.WParam);
+ int flags = NativeMethods.Util.HIWORD(m.WParam);
+
+ if ((flags & NativeMethods.MF_POPUP) != 0)
+ {
+ // The item that was selected has sub-items; therefore, we have to identify
+ // it recursively using the internal menu tree.
+ BetterMenuItem item = GetMenuItemByIndex(m.LParam, identifier);
+
+ if (item is BetterMenuButton button)
+ button.ItemFocused();
+ }
+ else
+ {
+ // The item that was selected does not have any sub-items; therefore, we can
+ // identify it using its command identifier.
+ GlobalCommand globalCommand = GlobalCommand.GetCommandExecutor(identifier);
+
+ if (globalCommand != null && globalCommand.CommandExecutor is BetterMenuReference menuReference && menuReference.Menu is BetterMenuButton button)
+ button.ItemFocused();
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/BetterMenuWndProcSubclass.cs b/src/BetterControls/BetterMenu/BetterMenuWndProcSubclass.cs
new file mode 100644
index 0000000..023f27d
--- /dev/null
+++ b/src/BetterControls/BetterMenu/BetterMenuWndProcSubclass.cs
@@ -0,0 +1,90 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+
+namespace BetterControls
+{
+ ///
+ /// Used to intercept the message loop of the source control of a menu when opened.
+ ///
+ internal partial class BetterMenuWndProcSubclass : NativeWindow
+ {
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The control whose window procedure should be subclassed.
+ public BetterMenuWndProcSubclass(Control control)
+ {
+ if (control is null)
+ {
+ throw new ArgumentNullException(nameof(control));
+ }
+
+ AssignHandle(control.Handle);
+ }
+
+ ///
+ /// Gets a drop-down menu item by its index.
+ ///
+ /// The handle of the menu.
+ /// The index of the item in the menu to get.
+ /// The menu item as an instance of .
+ private BetterMenuItem GetMenuItemByIndex(IntPtr handle, int index)
+ {
+ int identifier = UnsafeNativeMethods.GetMenuItemID(new HandleRef(null, handle), index);
+
+ if (identifier == -1)
+ {
+ // This menu item has sub-items. We have to recurse through the menu tree until we find an
+ // item that does not have any sub-items, and then iterate back up its menu tree based on its
+ // stored parent information.
+ IntPtr childHandle = UnsafeNativeMethods.GetSubMenu(new HandleRef(null, handle), index);
+ int childMenuCount = UnsafeNativeMethods.GetMenuItemCount(new HandleRef(null, childHandle));
+
+ for (int i = 0; i < childMenuCount;)
+ {
+ BetterMenuItem item = GetMenuItemByIndex(childHandle, i++);
+
+ if (item != null && item.OwnerElement != null && item.OwnerElement is BetterMenuItem parentItem)
+ return parentItem;
+ }
+ }
+ else
+ {
+ // This menu item does not have any sub-items.
+ GlobalCommand globalCommand = GlobalCommand.GetCommandExecutor(identifier);
+
+ if (globalCommand != null && globalCommand.CommandExecutor is BetterMenuReference menuReference && menuReference.Menu is BetterMenuItem item)
+ return item;
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/Items/BetterMenuButton.Events.cs b/src/BetterControls/BetterMenu/Items/BetterMenuButton.Events.cs
new file mode 100644
index 0000000..509e678
--- /dev/null
+++ b/src/BetterControls/BetterMenu/Items/BetterMenuButton.Events.cs
@@ -0,0 +1,527 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+
+namespace BetterControls
+{
+ ///
+ /// Extend this class to create a menu item that is a button.
+ ///
+ partial class BetterMenuButton
+ {
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnAutoSizeChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ AutoSizeChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnCheckedChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ CheckedChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnCheckStyleChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ CheckStyleChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when the menu item is clicked.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnClick(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ Click?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnCustomHeightChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ CustomHeightChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnDescriptionChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ DescriptionChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnEnabledChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ EnabledChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// this method is raised when the menu item is focused.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnFocused(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ Focused?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnHighlightChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ HighlightChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnImageChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ ImageChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnImageAlignChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ ImageAlignChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnImageIndexChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ ImageIndexChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnImageKeyChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ ImageKeyChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnOwnerDrawChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ OwnerDrawChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when the status part of this item is painted.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnPaintStatus(BetterMenuItemPaintStatusEventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ if (OwnerMenu is null)
+ return;
+
+ Image image = Image;
+
+ if (image is null && MenuRoot.ImageList != null && ImageIndexer.ComputedIndex != -1)
+ image = MenuRoot.ImageList.Images[ImageIndexer.ComputedIndex];
+
+ if (UseShieldImage)
+ image = IconUtilities.GetShieldImage();
+
+ if (image != null)
+ {
+ int croppedWidth = image.Width;
+ int croppedHeight = image.Height;
+
+ if (croppedWidth > e.Bounds.Width)
+ croppedWidth = e.Bounds.Width;
+ if (croppedHeight > e.Bounds.Height)
+ croppedHeight = e.Bounds.Height;
+
+ Bitmap croppedImage = new Bitmap(croppedWidth, croppedHeight);
+
+ using (Graphics graphics = Graphics.FromImage(croppedImage))
+ graphics.DrawImage(image, -0, -0);
+
+ int x = 0;
+ int y = 0;
+
+ switch (ImageAlign)
+ {
+ case BetterMenuButtonImageAlign.TopMiddle:
+ x = (e.Bounds.Width - croppedWidth) / 2;
+ break;
+ case BetterMenuButtonImageAlign.TopRight:
+ x = e.Bounds.Width - croppedWidth;
+ break;
+ case BetterMenuButtonImageAlign.MiddleLeft:
+ y = (e.Bounds.Height - croppedHeight) / 2;
+ break;
+ case BetterMenuButtonImageAlign.MiddleMiddle:
+ x = (e.Bounds.Width - croppedWidth) / 2;
+ y = (e.Bounds.Height - croppedHeight) / 2;
+ break;
+ case BetterMenuButtonImageAlign.MiddleRight:
+ x = e.Bounds.Width - croppedWidth;
+ y = (e.Bounds.Height - croppedHeight) / 2;
+ break;
+ case BetterMenuButtonImageAlign.BottomLeft:
+ y = e.Bounds.Height - croppedHeight;
+ break;
+ case BetterMenuButtonImageAlign.BottomMiddle:
+ x = (e.Bounds.Width - croppedWidth) / 2;
+ y = e.Bounds.Height - croppedHeight;
+ break;
+ case BetterMenuButtonImageAlign.BottomRight:
+ x = e.Bounds.Width - croppedWidth;
+ y = e.Bounds.Height - croppedHeight;
+ break;
+ }
+
+ e.Graphics.DrawImage(croppedImage, x, y, croppedWidth, croppedHeight);
+ }
+
+ OnPaintCheck(e);
+
+ PaintStatus?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when the check image of this item is painted.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnPaintCheck(BetterMenuItemPaintStatusEventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ if (CheckStyle == BetterMenuItemCheckStyle.Disabled)
+ return;
+
+ Bitmap checkBitmap = (Bitmap)Helper.VisualStyleRendererToImage(null, new Rectangle(0, 0, 16, 16), CheckStyle == BetterMenuItemCheckStyle.Check);
+
+ int x = 0;
+ int y = 0;
+
+ switch (CheckAlign)
+ {
+ case BetterMenuButtonImageAlign.TopMiddle:
+ x = (e.Bounds.Width - checkBitmap.Width) / 2;
+ break;
+ case BetterMenuButtonImageAlign.TopRight:
+ x = e.Bounds.Width - checkBitmap.Width;
+ break;
+ case BetterMenuButtonImageAlign.MiddleLeft:
+ y = (e.Bounds.Height - checkBitmap.Height) / 2;
+ break;
+ case BetterMenuButtonImageAlign.MiddleMiddle:
+ x = (e.Bounds.Width - checkBitmap.Width) / 2;
+ y = (e.Bounds.Height - checkBitmap.Height) / 2;
+ break;
+ case BetterMenuButtonImageAlign.MiddleRight:
+ x = e.Bounds.Width - checkBitmap.Width;
+ y = (e.Bounds.Height - checkBitmap.Height) / 2;
+ break;
+ case BetterMenuButtonImageAlign.BottomLeft:
+ y = e.Bounds.Height - checkBitmap.Height;
+ break;
+ case BetterMenuButtonImageAlign.BottomMiddle:
+ x = (e.Bounds.Width - checkBitmap.Width) / 2;
+ y = e.Bounds.Height - checkBitmap.Height;
+ break;
+ case BetterMenuButtonImageAlign.BottomRight:
+ x = e.Bounds.Width - checkBitmap.Width;
+ y = e.Bounds.Height - checkBitmap.Height;
+ break;
+ }
+
+ e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
+ e.Graphics.DrawImage(checkBitmap, x, y, checkBitmap.Width, checkBitmap.Height);
+
+ PaintCheck?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnShortcutChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ ShortcutChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnShowShortcutChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ ShowShortcutChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnTextChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ TextChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnUseShieldImageChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ UseShieldImageChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This event is raised when has been changed.
+ ///
+ public event EventHandler AutoSizeChanged;
+
+ ///
+ /// This event is raised when has been changed.
+ ///
+ public event EventHandler CheckedChanged;
+
+ ///
+ /// This event is raised when has been changed.
+ ///
+ public event EventHandler CheckStyleChanged;
+
+ ///
+ /// This event is raised when the menu item is clicked.
+ ///
+ public event EventHandler Click;
+
+ ///
+ /// This event is raised when has been changed.
+ ///
+ public event EventHandler CustomHeightChanged;
+
+ ///
+ /// This event is raised when has been changed.
+ ///
+ public event EventHandler DescriptionChanged;
+
+ ///
+ /// This event is raised when has been changed.
+ ///
+ public event EventHandler EnabledChanged;
+
+ ///
+ /// This event is raised when the menu item is focused.
+ ///
+ public event EventHandler Focused;
+
+ ///
+ /// This event is raised when has been changed.
+ ///
+ public event EventHandler HighlightChanged;
+
+ ///
+ /// This event is raised when has been changed.
+ ///
+ public event EventHandler ImageChanged;
+
+ ///
+ /// This event is raised when has been changed.
+ ///
+ public event EventHandler ImageAlignChanged;
+
+ ///
+ /// This event is raised when has been changed.
+ ///
+ public event EventHandler ImageIndexChanged;
+
+ ///
+ /// This event is raised when has been changed.
+ ///
+ public event EventHandler ImageKeyChanged;
+
+ ///
+ /// This event is raised when has been changed.
+ ///
+ public event EventHandler OwnerDrawChanged;
+
+ ///
+ /// This event is raised when the status part of this item is painted.
+ ///
+ public event EventHandler PaintStatus;
+
+ ///
+ /// This event is raised when the status part of this item is painted.
+ ///
+ public event EventHandler PaintCheck;
+
+ ///
+ /// This event is raised when has been changed.
+ ///
+ public event EventHandler ShortcutChanged;
+
+ ///
+ /// This event is raised when has been changed.
+ ///
+ public event EventHandler ShowShortcutChanged;
+
+ ///
+ /// This event is raised when has been changed.
+ ///
+ public event EventHandler TextChanged;
+
+ ///
+ /// This event is raised when has been changed.
+ ///
+ public event EventHandler UseShieldImageChanged;
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/Items/BetterMenuButton.NativeStructures.cs b/src/BetterControls/BetterMenu/Items/BetterMenuButton.NativeStructures.cs
new file mode 100644
index 0000000..d9a89c4
--- /dev/null
+++ b/src/BetterControls/BetterMenu/Items/BetterMenuButton.NativeStructures.cs
@@ -0,0 +1,257 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using BetterControls.VisualStyles;
+using System;
+using System.ComponentModel;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+using System.Windows.Forms.VisualStyles;
+
+namespace BetterControls
+{
+ ///
+ /// Extend this class to create a menu item that is a button.
+ ///
+ partial class BetterMenuButton
+ {
+ ///
+ ///
+ ///
+ ///
+ internal override NativeMethods.MENUITEMINFO_T ComputeMenuItemInfoT()
+ {
+ NativeMethods.MENUITEMINFO_T structure = base.ComputeMenuItemInfoT();
+
+ if (!Enabled)
+ structure.fState |= NativeMethods.MFS_DISABLED;
+ if (Checked)
+ structure.fState |= NativeMethods.MFS_CHECKED;
+ if (CheckStyle == BetterMenuItemCheckStyle.Radio)
+ structure.fType |= NativeMethods.MFT_RADIOCHECK;
+ if (OwnerDraw)
+ structure.fType |= NativeMethods.MFT_OWNERDRAW;
+
+ if (OwnerMenu == MenuRoot && MenuRoot.ShowHighlightBehavior == BetterMenuHighlightBehavior.HighlightFirstItem && DisplayedItemIndex == 0)
+ {
+ structure.fState |= NativeMethods.MFS_HILITE;
+ }
+ else if (OwnerMenu == MenuRoot && MenuRoot.ShowHighlightBehavior == BetterMenuHighlightBehavior.HighlightFirstItemExclusive)
+ {
+ if (DisplayedItemIndex == 0)
+ structure.fState |= NativeMethods.MFS_HILITE;
+ }
+ else if (Highlight)
+ {
+ structure.fState |= NativeMethods.MFS_HILITE;
+ }
+
+ structure.fMask |= NativeMethods.MIIM_BITMAP | NativeMethods.MIIM_STRING;
+
+ // The image in the native structure is also used as a mechanism to set the height of each item
+ // and the width of the status area.
+ using (Bitmap bitmap = CreateStatusImage())
+ structure.hbmpItem = bitmap.GetHbitmap(Color.FromArgb(0, 0, 0, 0));
+
+ string text = Text;
+
+ if (string.IsNullOrEmpty(text))
+ text = " ";
+
+ structure.dwTypeData = text;
+
+ // Setup the text for the menu item. If a shortkey key is specified and showing shortcuts is enabled, then the shortcut
+ // string literal is appended to the end of the text. Item text should not be empty, so this is checked.
+ if (ShowShortcut && Shortcut != Shortcut.None && Items.IsEmpty)
+ structure.dwTypeData += "\t" + TypeDescriptor.GetConverter(typeof(Keys)).ConvertToString((Keys)(int)Shortcut);
+
+ return structure;
+ }
+
+
+
+ ///
+ /// Creates the status image for this item.
+ ///
+ /// The created image as an instance of .
+ private protected virtual Bitmap CreateStatusImage()
+ {
+ if (OwnerMenu is null)
+ return null;
+
+ int statusColumnWidth = 16;
+ int itemHeight = 16;
+
+ if (!OwnerMenu.AutoSizeItems && OwnerMenu.CustomStatusWidth != 0)
+ statusColumnWidth = OwnerMenu.CustomStatusWidth;
+
+ if (!AutoSize && CustomHeight != 0)
+ itemHeight = CustomHeight;
+
+ int computedWidth = statusColumnWidth;
+
+ if (!Application.RenderWithVisualStyles)
+ computedWidth += 5;
+
+ Bitmap bitmap = new Bitmap(computedWidth, itemHeight, PixelFormat.Format32bppPArgb);
+
+ using (Graphics graphics = Graphics.FromImage(bitmap))
+ OnPaintStatus(new BetterMenuItemPaintStatusEventArgs(graphics, new Rectangle(0, 0, statusColumnWidth, itemHeight)));
+
+ bitmap.MakeTransparent();
+
+ return bitmap;
+ }
+
+
+
+
+ public class Helper
+ {
+ #region Win32 Native APIs
+ internal class NativeMethods
+ {
+ // CreateDIBSection funcation iUsage value
+ internal const int DIB_RGB_COLORS = 0x00;
+ internal const int DIB_PAL_COLORS = 0x01;
+ internal const int DIB_PAL_INDICES = 0x02;
+
+ [DllImport("gdi32.dll", CharSet = CharSet.Unicode)]
+ internal static extern bool DeleteObject(IntPtr hObject);
+
+ [DllImport("user32.dll", CharSet = CharSet.Unicode)]
+ internal static extern int InvalidateRect(IntPtr hwnd, IntPtr rect, int bErase);
+
+ [DllImport("user32.dll", CharSet = CharSet.Unicode)]
+ internal static extern IntPtr GetDC(IntPtr hwnd);
+
+ [DllImport("gdi32.dll", CharSet = CharSet.Unicode)]
+ internal static extern IntPtr CreateCompatibleDC(IntPtr hdc);
+
+ [DllImport("user32.dll", CharSet = CharSet.Unicode)]
+ internal static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);
+
+ [DllImport("gdi32.dll", CharSet = CharSet.Unicode)]
+ internal static extern int DeleteDC(IntPtr hdc);
+
+ [DllImport("gdi32.dll", CharSet = CharSet.Unicode)]
+ internal static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
+
+ [DllImport("gdi32.dll", CharSet = CharSet.Unicode)]
+ internal static extern IntPtr CreateDIBSection(IntPtr hdc, ref BITMAPINFO bmi, uint iUsage,
+ out IntPtr bits, IntPtr hSection, uint dwOffset);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "RtlMoveMemory")]
+ internal static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct BITMAPINFO
+ {
+ public Int32 biSize;
+ public Int32 biWidth;
+ public Int32 biHeight;
+ public Int16 biPlanes;
+ public Int16 biBitCount;
+ public Int32 biCompression;
+ public Int32 biSizeImage;
+ public Int32 biXPelsPerMeter;
+ public Int32 biYPelsPerMeter;
+ public Int32 biClrUsed;
+ public Int32 biClrImportant;
+ }
+ }
+ #endregion
+
+ public static Image VisualStyleRendererToImage(VisualStyleRenderer renderer, Rectangle bounds, bool check)
+ {
+ if (ToolStripManager.VisualStylesEnabled)
+ {
+ using (Bitmap bit = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb))
+ {
+ NativeMethods.BITMAPINFO bmi = new NativeMethods.BITMAPINFO();
+
+ bmi.biWidth = bit.Width;
+ bmi.biHeight = -bit.Height;
+ bmi.biPlanes = 1;
+ bmi.biBitCount = 32;
+ bmi.biXPelsPerMeter = (int)bit.HorizontalResolution;
+ bmi.biYPelsPerMeter = (int)bit.VerticalResolution;
+ bmi.biSize = Marshal.SizeOf(typeof(NativeMethods.BITMAPINFO));
+
+ IntPtr bits;
+ IntPtr bmp = NativeMethods.CreateDIBSection(IntPtr.Zero, ref bmi,
+ NativeMethods.DIB_RGB_COLORS, out bits, IntPtr.Zero, 0);
+
+ IntPtr dc = NativeMethods.GetDC(IntPtr.Zero);
+ IntPtr hdc = NativeMethods.CreateCompatibleDC(dc);
+ NativeMethods.SelectObject(hdc, bmp);
+
+ using (Graphics g = Graphics.FromHdc(hdc))
+ {
+ if (check)
+ {
+ MenuItemCheckRenderer.DrawCheck(g, new Point(0, 0), true);
+ }
+ else
+ {
+ MenuItemRadioRenderer.DrawRadio(g, new Point(0, 0), true);
+ }
+ }
+
+ Bitmap image = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppPArgb);
+
+ using (Bitmap tempImage = new Bitmap(bounds.Width, bounds.Height, bounds.Width * 4,
+ PixelFormat.Format32bppPArgb, bits))
+ {
+ BitmapData tempBitmapData = tempImage.LockBits(bounds, ImageLockMode.ReadOnly,
+ PixelFormat.Format32bppPArgb);
+ BitmapData bitmapData = image.LockBits(bounds, ImageLockMode.WriteOnly,
+ PixelFormat.Format32bppPArgb);
+
+ NativeMethods.CopyMemory(bitmapData.Scan0, tempBitmapData.Scan0,
+ (uint)tempBitmapData.Stride * (uint)tempBitmapData.Height);
+
+ tempImage.UnlockBits(tempBitmapData);
+ image.UnlockBits(bitmapData);
+ }
+
+ NativeMethods.DeleteObject(bmp);
+ NativeMethods.DeleteDC(hdc);
+ NativeMethods.ReleaseDC(IntPtr.Zero, dc);
+
+ return image;
+ }
+ }
+ else
+ {
+ return new Bitmap(bounds.Width, bounds.Height);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/Items/BetterMenuButton.cs b/src/BetterControls/BetterMenu/Items/BetterMenuButton.cs
new file mode 100644
index 0000000..82e6767
--- /dev/null
+++ b/src/BetterControls/BetterMenu/Items/BetterMenuButton.cs
@@ -0,0 +1,763 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using BetterControls.ComponentModel;
+using BetterControls.Drawing;
+using System;
+using System.ComponentModel;
+using System.Drawing;
+using System.Drawing.Design;
+using System.Drawing.Imaging;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+
+namespace BetterControls
+{
+ ///
+ /// Extend this class to create a menu item that is a button.
+ ///
+ public abstract partial class BetterMenuButton : BetterMenuItem
+ {
+ ///
+ /// Initialize a new instance of .
+ ///
+ public BetterMenuButton()
+ {
+ _imageIndexer = new BetterMenuButtonImageIndexer(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ public BetterMenuButton(string text)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ Text = text;
+
+ _imageIndexer = new BetterMenuButtonImageIndexer(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ public BetterMenuButton(string text, string description)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ if (description is null)
+ {
+ throw new ArgumentNullException(nameof(description));
+ }
+
+ Text = text;
+ Description = description;
+
+ _imageIndexer = new BetterMenuButtonImageIndexer(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The index of the image from the parent most menu image list to be shown in the menu item.
+ public BetterMenuButton(string text, int imageIndex)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ Text = text;
+ ImageIndex = imageIndex;
+
+ _imageIndexer = new BetterMenuButtonImageIndexer(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ /// The index of the image from the parent most menu image list to be shown in the menu item.
+ public BetterMenuButton(string text, string description, int imageIndex)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ if (description is null)
+ {
+ throw new ArgumentNullException(nameof(description));
+ }
+
+ Text = text;
+ Description = description;
+ ImageIndex = imageIndex;
+
+ _imageIndexer = new BetterMenuButtonImageIndexer(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The image to be shown in the menu item.
+ public BetterMenuButton(string text, Image image)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ Text = text;
+ Image = image;
+
+ _imageIndexer = new BetterMenuButtonImageIndexer(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ /// The image to be shown in the menu item.
+ public BetterMenuButton(string text, string description, Image image)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ if (description is null)
+ {
+ throw new ArgumentNullException(nameof(description));
+ }
+
+ Text = text;
+ Description = description;
+ Image = image;
+
+ _imageIndexer = new BetterMenuButtonImageIndexer(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public BetterMenuButton(string text, Shortcut shortcut)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ Text = text;
+ Shortcut = shortcut;
+
+ _imageIndexer = new BetterMenuButtonImageIndexer(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public BetterMenuButton(string text, string description, Shortcut shortcut)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ if (description is null)
+ {
+ throw new ArgumentNullException(nameof(description));
+ }
+
+ Text = text;
+ Description = description;
+ Shortcut = shortcut;
+
+ _imageIndexer = new BetterMenuButtonImageIndexer(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The index of the image from the parent most menu image list to be shown in the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public BetterMenuButton(string text, int imageIndex, Shortcut shortcut)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ Text = text;
+ ImageIndex = imageIndex;
+ Shortcut = shortcut;
+
+ _imageIndexer = new BetterMenuButtonImageIndexer(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ /// The index of the image from the parent most menu image list to be shown in the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public BetterMenuButton(string text, string description, int imageIndex, Shortcut shortcut)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ if (description is null)
+ {
+ throw new ArgumentNullException(nameof(description));
+ }
+
+ Text = text;
+ Description = description;
+ ImageIndex = imageIndex;
+ Shortcut = shortcut;
+
+ _imageIndexer = new BetterMenuButtonImageIndexer(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The image to be shown in the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public BetterMenuButton(string text, Image image, Shortcut shortcut)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ Text = text;
+ Image = image;
+ Shortcut = shortcut;
+
+ _imageIndexer = new BetterMenuButtonImageIndexer(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ /// The image to be shown in the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public BetterMenuButton(string text, string description, Image image, Shortcut shortcut)
+ {
+ if (text is null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ if (description is null)
+ {
+ throw new ArgumentNullException(nameof(description));
+ }
+
+ Text = text;
+ Description = description;
+ Image = image;
+ Shortcut = shortcut;
+
+ _imageIndexer = new BetterMenuButtonImageIndexer(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The owner menu as an instance of .
+ private protected BetterMenuButton(BetterMenu ownerMenu)
+ : base(ownerMenu)
+ { }
+
+ private bool _autoSize = true;
+ private bool _checked = false;
+ private BetterMenuButtonImageAlign _checkAlign = BetterMenuButtonImageAlign.MiddleRight;
+ private BetterMenuItemCheckStyle _checkStyle = BetterMenuItemCheckStyle.Disabled;
+ private int _customHeight = 0;
+ private string _description = string.Empty;
+ private bool _enabled = true;
+ private bool _highlight = false;
+ private Image _image;
+ private BetterMenuButtonImageAlign _imageAlign = BetterMenuButtonImageAlign.MiddleRight;
+ private BetterMenuButtonImageIndexer _imageIndexer;
+ private bool _ownerDraw = false;
+ private Shortcut _shortcut = Shortcut.None;
+ private bool _showShortcut = true;
+ private string _text = string.Empty;
+ private bool _useShieldImage = false;
+
+ ///
+ /// Gets or sets a value indicating whether or not the menu item should be auto-sized.
+ ///
+ [Category(Categories.Behavior)]
+ [Description("Value indicating whether or not the menu item should be auto-sized.")]
+ [DefaultValue(true)]
+ [Localizable(false)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
+ public virtual bool AutoSize
+ {
+ get => _autoSize;
+ set
+ {
+ if (AutoSize != value)
+ {
+ _autoSize = value;
+
+ PerformItemChanged(CollectionElementItemChangedFlags.None);
+
+ OnAutoSizeChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether or not the menu item is checked.
+ ///
+ [Category(Categories.Appearance)]
+ [Description("Value indicating whether or not the menu item is checked.")]
+ [DefaultValue(false)]
+ [Localizable(false)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
+ public virtual bool Checked
+ {
+ get => _checked;
+ set
+ {
+ if (Checked != value)
+ {
+ if (value is true && Items.Count != 0)
+ return;
+
+ _checked = value;
+
+ if (CheckStyle == BetterMenuItemCheckStyle.Disabled && Checked == true)
+ {
+ CheckStyle = BetterMenuItemCheckStyle.Check;
+ }
+ else if (CheckStyle != BetterMenuItemCheckStyle.Disabled && Checked == false)
+ {
+ CheckStyle = BetterMenuItemCheckStyle.Disabled;
+ }
+
+ OnCheckedChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets a value from indicating the alignment of the check when specified.
+ ///
+ [Category(Categories.Appearance)]
+ [Description("Value indicating the alignment of the check when specified.")]
+ [DefaultValue(typeof(BetterMenuButtonImageAlign), "MiddleRight")]
+ [Localizable(false)]
+ public virtual BetterMenuButtonImageAlign CheckAlign
+ {
+ get => _checkAlign;
+ set
+ {
+ if (CheckAlign != value)
+ {
+ _checkAlign = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets a value from indicating the check style of the item when checked.
+ ///
+ [Localizable(true)]
+ [DefaultValue(typeof(BetterMenuItemCheckStyle), "Disabled")]
+ [Category(Categories.Appearance)]
+ [Description("Value indicating the check style of the item when checked.")]
+ public virtual BetterMenuItemCheckStyle CheckStyle
+ {
+ get => _checkStyle;
+ set
+ {
+ if (CheckStyle != value)
+ {
+ _checkStyle = value;
+
+ if (CheckStyle == BetterMenuItemCheckStyle.Check || CheckStyle == BetterMenuItemCheckStyle.Radio)
+ {
+ _checked = true;
+ }
+ else
+ {
+ _checked = false;
+ }
+
+ OnCheckStyleChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating the item height.
+ ///
+ [Category(Categories.Appearance)]
+ [Description("Value indicating the item height.")]
+ [DefaultValue(0)]
+ [Localizable(false)]
+ public virtual int CustomHeight
+ {
+ get => _customHeight;
+ set
+ {
+ if (_customHeight != value)
+ {
+ if (value < 0 || value > 256)
+ throw new ArgumentOutOfRangeException(nameof(value), "ItemHeight must not be less than 0 or more than 256.");
+
+ _customHeight = value;
+
+ OnCustomHeightChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the description of the menu item.
+ ///
+ [Category(Categories.Data)]
+ [Description("The description of the menu item.")]
+ [DefaultValue("")]
+ [Localizable(false)]
+ public virtual string Description
+ {
+ get => _description;
+ set
+ {
+ if (Description != value)
+ {
+ _description = value;
+
+ OnDescriptionChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether or not the menu item is enabled.
+ ///
+ [Category(Categories.Behavior)]
+ [Description("Value indicating whether or not the menu item is enabled.")]
+ [DefaultValue(true)]
+ [Localizable(false)]
+ public virtual bool Enabled
+ {
+ get => _enabled;
+ set
+ {
+ if (Enabled != value)
+ {
+ _enabled = value;
+
+ OnEnabledChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether or not this menu item should be highlighted when opened.
+ ///
+ [Category(Categories.Behavior)]
+ [Description("Value indicating whether or not this menu item should be highlighted when opened.")]
+ [DefaultValue(false)]
+ [Localizable(false)]
+ public virtual bool Highlight
+ {
+ get => _highlight;
+ set
+ {
+ if (Highlight != value)
+ {
+ _highlight = value;
+
+ OnHighlightChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the image to be shown in the menu item.
+ ///
+ [Category(Categories.Appearance)]
+ [Description("The image to be shown in the menu item.")]
+ [DefaultValue(null)]
+ [Localizable(false)]
+ public virtual Image Image
+ {
+ get => _image;
+ set
+ {
+ if (Image != value)
+ {
+ _image = value;
+
+ OnImageChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets a value from indicating the alignment of an image when specified.
+ ///
+ [Category(Categories.Appearance)]
+ [Description("Value indicating the alignment of an image when specified.")]
+ [DefaultValue(typeof(BetterMenuButtonImageAlign), "MiddleRight")]
+ [Localizable(false)]
+ public virtual BetterMenuButtonImageAlign ImageAlign
+ {
+ get => _imageAlign; set
+ {
+ if (ImageAlign != value)
+ {
+ _imageAlign = value;
+
+ OnImageAlignChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the index of the image from the menu image list to be shown in the menu item.
+ ///
+ [Category(Categories.Appearance)]
+ [Description("The index of the image from the menu image list to be shown in the menu item.")]
+ [DefaultValue(-1)]
+ [Localizable(false)]
+ [Editor("ImageIndexEditor", typeof(UITypeEditor))]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
+ [TypeConverter(typeof(NoneExcludedImageIndexConverter))]
+ [RelatedImageList("OwnerToolbar.ImageList")]
+ public virtual int ImageIndex
+ {
+ get => ImageIndexer.Index;
+ set
+ {
+ if (ImageIndex != value)
+ {
+ ImageIndexer.Index = value;
+
+ OnImageIndexChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets the image indexer used to provide a mechanism for a toolbar to support using both image indexes and image keys.
+ ///
+ [Browsable(false)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ private protected BetterMenuButtonImageIndexer ImageIndexer => _imageIndexer;
+
+ ///
+ /// Gets or sets the key of the image from the menu image list to be shown in the menu item.
+ ///
+ [Category(Categories.Appearance)]
+ [Description("The key of the image from the menu image list to be shown in the menu item.")]
+ [DefaultValue("")]
+ [Localizable(false)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
+ [TypeConverter(typeof(ImageKeyConverter))]
+ [RelatedImageList("OwnerToolbar.ImageList")]
+ public virtual string ImageKey
+ {
+ get => ImageIndexer.Key;
+ set
+ {
+ if (ImageKey != value)
+ {
+ ImageIndexer.Key = value;
+
+ OnImageKeyChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets the mnemonic of the menu item text, if any.
+ ///
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public char Mnemonic => StringUtilities.GetMnemonic(Text, true);
+
+ ///
+ /// Gets or sets a value indicating whether or not owner draw is enabled for the menu item.
+ ///
+ [Category(Categories.Behavior)]
+ [Description("Value indicating whether or not owner draw is enabled for the menu item.")]
+ [DefaultValue(false)]
+ [Localizable(false)]
+ public bool OwnerDraw
+ {
+ get => _ownerDraw;
+ set
+ {
+ if (OwnerDraw != value)
+ {
+ _ownerDraw = value;
+
+ OnOwnerDrawChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the shortcut key combination associated with the menu item.
+ ///
+ [Category(Categories.Behavior)]
+ [Description("The shortcut key combination associated with the menu item.")]
+ [DefaultValue(typeof(Shortcut), "None")]
+ [Localizable(false)]
+ public virtual Shortcut Shortcut
+ {
+ get => _shortcut;
+ set
+ {
+ if (Shortcut != value)
+ {
+ _shortcut = value;
+
+ OnShortcutChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether or not the shortcut is shown in the menu item.
+ ///
+ [Category(Categories.Behavior)]
+ [Description("Value indicating whether or not the shortcut is shown in the menu item.")]
+ [DefaultValue(true)]
+ [Localizable(false)]
+ public virtual bool ShowShortcut
+ {
+ get => _showShortcut;
+ set
+ {
+ if (ShowShortcut != value)
+ {
+ _showShortcut = value;
+
+ OnShowShortcutChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the text of the menu item.
+ ///
+ [Category(Categories.Appearance)]
+ [Description("The text of the menu item.")]
+ [DefaultValue("")]
+ [Localizable(false)]
+ public virtual string Text
+ {
+ get => _text;
+ set
+ {
+ if (Text != value)
+ {
+ _text = value;
+
+ OnTextChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether or not the menu item should use the system shield image. This will take precedence over any value given in , or .
+ ///
+ [Category(Categories.Appearance)]
+ [Description("Value indicating whether or not the menu item should use the system shield image.")]
+ [DefaultValue(false)]
+ [Localizable(false)]
+ public virtual bool UseShieldImage
+ {
+ get => _useShieldImage;
+ set
+ {
+ if (UseShieldImage != value)
+ {
+ _useShieldImage = value;
+
+ OnUseShieldImageChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Simulates clicking the menu item.
+ ///
+ public void PerformClick()
+ {
+ if (Enabled)
+ {
+ MenuRoot.DescendantClicked(this);
+ OnClick(EventArgs.Empty);
+ }
+ }
+
+ ///
+ /// This method is raised when this item is focused.
+ ///
+ internal void ItemFocused()
+ {
+ MenuRoot.DescendantFocused(this);
+ OnFocused(new EventArgs());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/Items/BetterMenuItem.NativeStructures.cs b/src/BetterControls/BetterMenu/Items/BetterMenuItem.NativeStructures.cs
new file mode 100644
index 0000000..cf5ffc3
--- /dev/null
+++ b/src/BetterControls/BetterMenu/Items/BetterMenuItem.NativeStructures.cs
@@ -0,0 +1,73 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System;
+using System.ComponentModel;
+
+namespace BetterControls
+{
+ ///
+ /// Extend this class to create a menu item.
+ ///
+ partial class BetterMenuItem
+ {
+ ///
+ /// Computes the native structure as an instance of .
+ ///
+ /// An instance of .
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ internal virtual NativeMethods.MENUITEMINFO_T ComputeMenuItemInfoT()
+ {
+ NativeMethods.MENUITEMINFO_T structure = new NativeMethods.MENUITEMINFO_T();
+
+ structure.fMask = NativeMethods.MIIM_ID | NativeMethods.MIIM_STATE | NativeMethods.MIIM_SUBMENU | NativeMethods.MIIM_DATA | NativeMethods.MIIM_FTYPE;
+ structure.wID = UniqueIdentifier;
+
+ if (Items.Count != 0)
+ {
+ bool visible = true;
+
+ foreach (BetterMenu menu in Items)
+ {
+ if (menu is BetterMenuButton menuButton && !menuButton.Visible)
+ {
+ visible = false;
+
+ break;
+ }
+ }
+
+ if(visible)
+ structure.hSubMenu = Handle;
+ }
+
+ if (!Visible)
+ structure.fState |= NativeMethods.MFS_HIDDEN;
+
+ return structure;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/Items/BetterMenuItem.cs b/src/BetterControls/BetterMenu/Items/BetterMenuItem.cs
new file mode 100644
index 0000000..2da1fe3
--- /dev/null
+++ b/src/BetterControls/BetterMenu/Items/BetterMenuItem.cs
@@ -0,0 +1,92 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using BetterControls.ComponentModel;
+using System.ComponentModel;
+
+namespace BetterControls
+{
+ ///
+ /// Extend this class to create a menu item.
+ ///
+ public abstract partial class BetterMenuItem : BetterMenu
+ {
+ ///
+ /// Initialize a new instance of .
+ ///
+ private protected BetterMenuItem() { }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The owner menu as an instance of .
+ private protected BetterMenuItem(BetterMenu ownerMenu)
+ : base(ownerMenu)
+ { }
+
+ private bool _visible = true;
+
+ ///
+ /// Gets the displayed index of this item in the menu.
+ ///
+ [Browsable(false)]
+ public virtual int DisplayedItemIndex => ItemIndex;
+
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public override BetterMenuItemCollection Items => base.Items;
+
+ ///
+ /// Gets the collection of menu items for this menu.
+ ///
+ [Category(Categories.Behavior)]
+ [Description("The collection of menu items for this menu.")]
+ [DefaultValue(null)]
+ [Localizable(false)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
+ public BetterMenuItemCollection SubItems => Items;
+
+ ///
+ /// Gets or sets a value indicating whether or not the menu item is visible.
+ ///
+ [Category(Categories.Behavior)]
+ [Localizable(true)]
+ [DefaultValue(true)]
+ [Description("Value indicating whether or not the menu item is visible.")]
+ public bool Visible
+ {
+ get => _visible;
+ set
+ {
+ if (Visible != value)
+ {
+ _visible = value;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/Items/BetterMenuPushButton.cs b/src/BetterControls/BetterMenu/Items/BetterMenuPushButton.cs
new file mode 100644
index 0000000..ef40226
--- /dev/null
+++ b/src/BetterControls/BetterMenu/Items/BetterMenuPushButton.cs
@@ -0,0 +1,166 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System.Drawing;
+using System.Windows.Forms;
+
+namespace BetterControls
+{
+ ///
+ /// Represents a menu push button.
+ ///
+ public class BetterMenuPushButton : BetterMenuButton
+ {
+ ///
+ /// Initialize a new instance of .
+ ///
+ public BetterMenuPushButton() { }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ public BetterMenuPushButton(string text)
+ : base(text)
+ { }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ public BetterMenuPushButton(string text, string description)
+ : base(text, description)
+ { }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The index of the image from the parent most menu image list to be shown in the menu item.
+ public BetterMenuPushButton(string text, int imageIndex)
+ : base(text, imageIndex)
+ { }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ /// The index of the image from the parent most menu image list to be shown in the menu item.
+ public BetterMenuPushButton(string text, string description, int imageIndex)
+ : base(text, description, imageIndex)
+ { }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The image to be shown in the menu item.
+ public BetterMenuPushButton(string text, Image image)
+ : base(text, image)
+ { }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ /// The image to be shown in the menu item.
+ public BetterMenuPushButton(string text, string description, Image image)
+ : base(text, description, image)
+ { }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public BetterMenuPushButton(string text, Shortcut shortcut)
+ : base(text, shortcut)
+ { }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public BetterMenuPushButton(string text, string description, Shortcut shortcut)
+ : base(text, description, shortcut)
+ { }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The index of the image from the parent most menu image list to be shown in the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public BetterMenuPushButton(string text, int imageIndex, Shortcut shortcut)
+ : base(text, imageIndex, shortcut)
+ { }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ /// The index of the image from the parent most menu image list to be shown in the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public BetterMenuPushButton(string text, string description, int imageIndex, Shortcut shortcut)
+ : base(text, description, imageIndex, shortcut)
+ { }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The image to be shown in the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public BetterMenuPushButton(string text, Image image, Shortcut shortcut)
+ : base(text, image, shortcut)
+ { }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the menu item.
+ /// The description of the menu item.
+ /// The image to be shown in the menu item.
+ /// The shortcut key combination associated with the menu item.
+ public BetterMenuPushButton(string text, string description, Image image, Shortcut shortcut)
+ : base(text, description, image, shortcut)
+ { }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The owner menu as an instance of .
+ private protected BetterMenuPushButton(BetterMenu ownerMenu)
+ : base(ownerMenu)
+ { }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/Items/BetterMenuSeparator.NativeStructures.cs b/src/BetterControls/BetterMenu/Items/BetterMenuSeparator.NativeStructures.cs
new file mode 100644
index 0000000..6c2d611
--- /dev/null
+++ b/src/BetterControls/BetterMenu/Items/BetterMenuSeparator.NativeStructures.cs
@@ -0,0 +1,47 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+namespace BetterControls
+{
+ ///
+ /// Represents a menu separator.
+ ///
+ partial class BetterMenuSeparator
+ {
+ ///
+ ///
+ ///
+ ///
+ internal override NativeMethods.MENUITEMINFO_T ComputeMenuItemInfoT()
+ {
+ NativeMethods.MENUITEMINFO_T menuItem = base.ComputeMenuItemInfoT();
+
+ menuItem.fType |= NativeMethods.MFT_SEPARATOR;
+
+ return menuItem;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterMenu/Items/BetterMenuSeparator.cs b/src/BetterControls/BetterMenu/Items/BetterMenuSeparator.cs
new file mode 100644
index 0000000..693824e
--- /dev/null
+++ b/src/BetterControls/BetterMenu/Items/BetterMenuSeparator.cs
@@ -0,0 +1,53 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System.ComponentModel;
+
+namespace BetterControls
+{
+ ///
+ /// Represents a menu separator.
+ ///
+ public partial class BetterMenuSeparator : BetterMenuItem
+ {
+ ///
+ /// Initialize a new instance of .
+ ///
+ public BetterMenuSeparator() { }
+
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override BetterMenuItemCollection Items => base.Items;
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The owner menu as an instance of .
+ private protected BetterMenuSeparator(BetterMenu ownerMenu)
+ : base(ownerMenu)
+ { }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterToolbar/BetterToolbar.Collection.cs b/src/BetterControls/BetterToolbar/BetterToolbar.Collection.cs
index 7312abb..ba7745a 100644
--- a/src/BetterControls/BetterToolbar/BetterToolbar.Collection.cs
+++ b/src/BetterControls/BetterToolbar/BetterToolbar.Collection.cs
@@ -77,9 +77,9 @@ private protected void Insert(BetterToolbarItem item)
if (IsHandleCreated)
{
NativeMethods.TBBUTTON structure = item.ComputeTbButton();
- UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), NativeMethods.TB_INSERTBUTTON, item.UniqueIdentifier, ref structure);
+ UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.TB_INSERTBUTTON, item.UniqueIdentifier, ref structure);
- SendMessage(NativeMethods.TB_AUTOSIZE, 0, 0);
+ UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.TB_AUTOSIZE, 0, 0);
UpdateItemDimensions();
}
}
@@ -114,12 +114,12 @@ internal void PerformItemsChanged(CollectionElementItemChangedFlags flags, param
throw new ArgumentNullException(nameof(items));
}
- SendMessage(NativeMethods.TB_AUTOSIZE, 0, 0);
+ UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.TB_AUTOSIZE, 0, 0);
if (!AutoSizeItems)
{
- UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), NativeMethods.TB_SETBUTTONSIZE, 0, NativeMethods.Util.MAKELPARAM(0, ItemHeight));
+ UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.TB_SETBUTTONSIZE, 0, NativeMethods.Util.MAKELPARAM(0, ItemHeight));
}
- SendMessage(NativeMethods.TB_AUTOSIZE, 0, 0);
+ UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.TB_AUTOSIZE, 0, 0);
if ((flags & CollectionElementItemChangedFlags.RecreateHandle) != 0)
RecreateHandle();
diff --git a/src/BetterControls/BetterToolbar/BetterToolbar.Events.cs b/src/BetterControls/BetterToolbar/BetterToolbar.Events.cs
new file mode 100644
index 0000000..ae2d901
--- /dev/null
+++ b/src/BetterControls/BetterToolbar/BetterToolbar.Events.cs
@@ -0,0 +1,270 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System;
+using System.ComponentModel;
+
+namespace BetterControls
+{
+ ///
+ /// Wrapper of the Windows Toolbar classes.
+ ///
+ partial class BetterToolbar
+ {
+ ///
+ /// This method is raised before a drop-down button has been clicked with the intention of showing the drop-down menu.
+ ///
+ /// The event arguments as an instance of
+ protected virtual void OnBeforeMenuDroppedDown(BetterToolbarMenuDroppedDownEventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ BeforeMenuDroppedDown?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised after a drop-down button has been clicked and has shown the drop-down menu.
+ ///
+ /// The event arguments as an instance of
+ protected virtual void OnAfterMenuDroppedDown(BetterToolbarMenuDroppedDownEventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ AfterMenuDroppedDown?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnAppearanceChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ AppearanceChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnBorderStyleChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ BorderStyleChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnDividerChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ DividerChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnDropDownArrowsChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ DropDownArrowsChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnImageListChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ ImageListChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnImageSizeChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ ImageSizeChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnShowToolTipsChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ ShowToolTipsChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnTextAlignChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ TextAlignChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This method is raised when has been changed.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnWrappableChanged(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ WrappableChanged?.Invoke(this, e);
+ }
+
+ ///
+ /// This event is raised before a drop-down button has been clicked with the intention of showing the drop-down menu.
+ ///
+ public event EventHandler BeforeMenuDroppedDown;
+
+ ///
+ /// This event is raised after a drop-down button has been clicked and has shown the drop-down menu.
+ ///
+ public event EventHandler AfterMenuDroppedDown;
+
+ ///
+ /// This event is raised has been changed.
+ ///
+ public event EventHandler AppearanceChanged;
+
+ ///
+ /// This event is raised has been changed.
+ ///
+ public event EventHandler BorderStyleChanged;
+
+ ///
+ /// This event is raised has been changed.
+ ///
+ public event EventHandler DividerChanged;
+
+ ///
+ /// This event is raised has been changed.
+ ///
+ public event EventHandler DropDownArrowsChanged;
+
+ ///
+ /// This event is raised after has been changed.
+ ///
+ public event EventHandler ImageListChanged;
+
+ ///
+ /// This event is raised after has been changed.
+ ///
+ public event EventHandler ImageSizeChanged;
+
+ ///
+ /// This event is raised after has been changed.
+ ///
+ public event EventHandler ShowToolTipsChanged;
+
+ ///
+ /// This event is raised after has been changed.
+ ///
+ public event EventHandler TextAlignChanged;
+
+ ///
+ /// This event is raised after has been changed.
+ ///
+ public event EventHandler WrappableChanged;
+
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new event EventHandler ImeModeChanged
+ {
+ add => base.ImeModeChanged += value;
+ remove => base.ImeModeChanged -= value;
+ }
+
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new event EventHandler ForeColorChanged
+ {
+ add => base.ForeColorChanged += value;
+ remove => base.ForeColorChanged -= value;
+ }
+
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new event EventHandler RightToLeftChanged
+ {
+ add => base.RightToLeftChanged += value;
+ remove => base.RightToLeftChanged -= value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterToolbar/BetterToolbar.MessageLoop.cs b/src/BetterControls/BetterToolbar/BetterToolbar.MessageLoop.cs
new file mode 100644
index 0000000..d400cc6
--- /dev/null
+++ b/src/BetterControls/BetterToolbar/BetterToolbar.MessageLoop.cs
@@ -0,0 +1,263 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+
+namespace BetterControls
+{
+ ///
+ /// Wrapper of the Windows Toolbar classes.
+ ///
+ partial class BetterToolbar
+ {
+ ///
+ ///
+ ///
+ ///
+ protected override void WndProc(ref Message m)
+ {
+ switch (m.Msg)
+ {
+ case NativeMethods.WM_COMMAND + NativeMethods.WM_REFLECT:
+ if (WmReflectCommand(ref m))
+ return;
+
+ break;
+ case NativeMethods.WM_NOTIFY:
+ if (WmNotify(ref m))
+ return;
+
+ break;
+ case NativeMethods.WM_NOTIFY + NativeMethods.WM_REFLECT:
+ if (WmReflectNotify(ref m))
+ return;
+
+ break;
+ }
+
+ base.WndProc(ref m);
+ }
+
+ ///
+ /// Handles the WM_COMMAND + WM_REFLECT message.
+ ///
+ private protected virtual bool WmReflectCommand(ref Message m)
+ {
+ int index = NativeMethods.Util.LOWORD(m.WParam);
+
+ if (Items[index] is BetterToolbarClickableButton button)
+ button.PerformClick();
+
+ ResetMouseEventArgs();
+
+ return false;
+ }
+
+ /////
+ ///// Handles the WM_MENUSELECT message.
+ /////
+ //private protected virtual bool WmMenuSelect(ref Message m)
+ //{
+ // // If the menu item is a popup item (i.e. it has sub-items) then the data sent
+ // // with WM_MENUSELECT to identify which menu was selected differs than any other
+ // // menu item. An index to the popup item is sent instead of command identifier.
+ // int identifier = NativeMethods.Util.LOWORD(m.WParam);
+ // int flags = NativeMethods.Util.HIWORD(m.WParam);
+
+ // if ((flags & NativeMethods.MF_POPUP) != 0)
+ // {
+ // // The item that was selected has sub-items; therefore, we have to identify
+ // // it recursively using the internal menu tree.
+ // BetterMenuItem item = GetMenuItemByIndex(m.LParam, identifier);
+
+ // if (item is BetterMenuButton button)
+ // button.ItemFocused();
+ // }
+ // else
+ // {
+ // // The item that was selected does not have any sub-items; therefore, we can
+ // // identify it using its command identifier.
+ // GlobalCommand globalCommand = GlobalCommand.GetCommandExecutor(identifier);
+
+ // if (globalCommand != null && globalCommand.CommandExecutor is BetterMenuReference menuReference && menuReference.Menu is BetterMenuButton button)
+ // button.ItemFocused();
+ // }
+
+ // return false;
+ //}
+
+ ///
+ /// Handles the WM_NOTIFY message.
+ ///
+ private protected virtual bool WmNotify(ref Message m) => WmReflectNotify(ref m);
+
+ ///
+ /// Handles the WM_NOTIFY + WM_REFLECT message.
+ ///
+ private protected virtual unsafe bool WmReflectNotify(ref Message m)
+ {
+ NativeMethods.NMHDR* note = (NativeMethods.NMHDR*)m.LParam;
+
+ switch (note->code)
+ {
+ case NativeMethods.TBN_QUERYINSERT:
+ m.Result = (IntPtr)1;
+ break;
+
+ case NativeMethods.TBN_DROPDOWN:
+ TbnDropDown(ref m);
+ break;
+ case NativeMethods.TTN_NEEDTEXTA:
+ TtnNeedTextA(ref m);
+ m.Result = (IntPtr)1;
+ break;
+
+ case NativeMethods.TTN_NEEDTEXTW:
+ if (Marshal.SystemDefaultCharSize == 2)
+ {
+ TtnNeedTextW(ref m);
+ m.Result = (IntPtr)1;
+ }
+ break;
+ case NativeMethods.TBN_HOTITEMCHANGE:
+ TbnHotItemChange(ref m);
+ break;
+ case NativeMethods.NM_CUSTOMDRAW:
+ if (!AutoSizeItems)
+ UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), NativeMethods.TB_SETBUTTONSIZE, 0, NativeMethods.Util.MAKELPARAM(0, ItemHeight));
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Handles the TBN_DROPDOWN message within the WM_NOTIFY and WM_NOTIFY + WM_REFLECT messages.
+ ///
+ private protected virtual bool TbnDropDown(ref Message m)
+ {
+ NativeMethods.NMTOOLBAR note = (NativeMethods.NMTOOLBAR)m.GetLParam(typeof(NativeMethods.NMTOOLBAR));
+
+ BetterToolbarItem item = Items.GetItemByUniqueIdentifier(note.iItem);
+
+ if (item != null && item is BetterToolbarDropDownButton button)
+ PerformDropDown(button, false);
+
+ return false;
+ }
+
+ ///
+ /// Handles the TTN_NEEDTEXTA message within the WM_NOTIFY and WM_NOTIFY + WM_REFLECT messages.
+ ///
+ private protected virtual bool TtnNeedTextA(ref Message m) => TtnNeedTextW(ref m);
+
+ ///
+ /// Handles the TTN_NEEDTEXTW message within the WM_NOTIFY and WM_NOTIFY + WM_REFLECT messages.
+ ///
+ private protected virtual bool TtnNeedTextW(ref Message m)
+ {
+ NativeMethods.TOOLTIPTEXT toolTipText = (NativeMethods.TOOLTIPTEXT)m.GetLParam(typeof(NativeMethods.TOOLTIPTEXT));
+
+ BetterToolbarItem item = Items.GetItemByUniqueIdentifier((int)toolTipText.hdr.idFrom);
+
+ // Only items that are buttons (i.e. not a separator) are capable
+ // of showing tool tips.
+ if (item != null && item is BetterToolbarButton button)
+ {
+ toolTipText.hinst = IntPtr.Zero;
+
+ if (button.ToolTipText != null)
+ toolTipText.lpszText = GetToolTipText(button);
+
+ if (string.IsNullOrEmpty(toolTipText.lpszText))
+ toolTipText.lpszText = null;
+
+ if (RightToLeft == RightToLeft.Yes)
+ toolTipText.uFlags |= NativeMethods.TTF_RTLREADING;
+
+ Marshal.StructureToPtr(toolTipText, m.LParam, false);
+ }
+
+ return false;
+ }
+
+ ///
+ /// Handles the TBN_HOTITEMCHANGE message within the WM_NOTIFY and WM_NOTIFY + WM_REFLECT messages.
+ ///
+ private protected virtual bool TbnHotItemChange(ref Message m)
+ {
+ // Should we set the hot item?
+ NativeMethods.NMTBHOTITEM nmTbHotItem = (NativeMethods.NMTBHOTITEM)m.GetLParam(typeof(NativeMethods.NMTBHOTITEM));
+
+ BetterToolbarItem item = Items.GetItemByUniqueIdentifier(nmTbHotItem.idNew);
+
+ if (item != null && item is BetterToolbarButton button)
+ {
+ if (NativeMethods.HICF_ENTERING == (nmTbHotItem.dwFlags & NativeMethods.HICF_ENTERING))
+ {
+ _hotButton = button;
+ }
+ else if (NativeMethods.HICF_LEAVING == (nmTbHotItem.dwFlags & NativeMethods.HICF_LEAVING))
+ {
+ _hotButton = null;
+ }
+ else if (NativeMethods.HICF_MOUSE == (nmTbHotItem.dwFlags & NativeMethods.HICF_MOUSE))
+ {
+ _hotButton = button;
+ }
+ else if (NativeMethods.HICF_ARROWKEYS == (nmTbHotItem.dwFlags & NativeMethods.HICF_ARROWKEYS))
+ {
+ _hotButton = button;
+ }
+ else if (NativeMethods.HICF_ACCELERATOR == (nmTbHotItem.dwFlags & NativeMethods.HICF_ACCELERATOR))
+ {
+ _hotButton = button;
+ }
+ else if (NativeMethods.HICF_DUPACCEL == (nmTbHotItem.dwFlags & NativeMethods.HICF_DUPACCEL))
+ {
+ _hotButton = button;
+ }
+ else if (NativeMethods.HICF_RESELECT == (nmTbHotItem.dwFlags & NativeMethods.HICF_RESELECT))
+ {
+ _hotButton = button;
+ }
+ else if (NativeMethods.HICF_LMOUSE == (nmTbHotItem.dwFlags & NativeMethods.HICF_LMOUSE))
+ {
+ _hotButton = button;
+ }
+ else if (NativeMethods.HICF_TOGGLEDROPDOWN == (nmTbHotItem.dwFlags & NativeMethods.HICF_TOGGLEDROPDOWN))
+ {
+ _hotButton = button;
+ }
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterToolbar/BetterToolbar.cs b/src/BetterControls/BetterToolbar/BetterToolbar.cs
index 7aa0592..fcf4a05 100644
--- a/src/BetterControls/BetterToolbar/BetterToolbar.cs
+++ b/src/BetterControls/BetterToolbar/BetterToolbar.cs
@@ -26,7 +26,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
using BetterControls.ComponentModel;
using System;
-using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
@@ -49,7 +48,6 @@ public partial class BetterToolbar : BetterToolbarBase, ISupportInitialize
///
public BetterToolbar()
{
- SetStyle(ControlStyles.UserPaint, false);
SetStyle(ControlStyles.FixedHeight, AutoSize);
SetStyle(ControlStyles.FixedWidth, false);
@@ -463,7 +461,7 @@ public virtual ImageList MenuImageList
///
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- public virtual int Rows => unchecked((int)(long)SendMessage(NativeMethods.TB_GETROWS, 0, 0));
+ public virtual int Rows => unchecked((int)(long)UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.TB_GETROWS, 0, 0));
///
/// Gets or sets a value indicating whether or not tool tips should be shown.
@@ -539,6 +537,13 @@ public virtual bool Wrappable
}
}
+
+
+
+
+
+
+
///
///
///
@@ -546,46 +551,59 @@ protected override CreateParams CreateParams
{
get
{
- CreateParams cp = base.CreateParams;
+ CreateParams parameters = base.CreateParams;
- cp.ClassName = NativeMethods.WC_TOOLBAR;
- cp.Style |= NativeMethods.CCS_NOPARENTALIGN | NativeMethods.CCS_NORESIZE;
+ parameters.ClassName = NativeMethods.WC_TOOLBAR;
+ parameters.Style |= NativeMethods.CCS_NOPARENTALIGN | NativeMethods.CCS_NORESIZE;
if (!Divider)
- cp.Style |= NativeMethods.CCS_NODIVIDER;
+ parameters.Style |= NativeMethods.CCS_NODIVIDER;
if (Wrappable)
- cp.Style |= NativeMethods.TBSTYLE_WRAPPABLE;
+ parameters.Style |= NativeMethods.TBSTYLE_WRAPPABLE;
if (ShowToolTips && !DesignMode)
- cp.Style |= NativeMethods.TBSTYLE_TOOLTIPS;
+ parameters.Style |= NativeMethods.TBSTYLE_TOOLTIPS;
- cp.ExStyle &= ~NativeMethods.WS_EX_CLIENTEDGE;
- cp.Style &= ~NativeMethods.WS_BORDER;
+ parameters.ExStyle &= ~NativeMethods.WS_EX_CLIENTEDGE;
+ parameters.Style &= ~NativeMethods.WS_BORDER;
if (BorderStyle == BorderStyle.Fixed3D)
- cp.ExStyle |= NativeMethods.WS_EX_CLIENTEDGE;
+ parameters.ExStyle |= NativeMethods.WS_EX_CLIENTEDGE;
else if (BorderStyle == BorderStyle.FixedSingle)
- cp.Style |= NativeMethods.WS_BORDER;
+ parameters.Style |= NativeMethods.WS_BORDER;
if (Appearance == BetterToolbarAppearance.Flat)
- cp.Style |= NativeMethods.TBSTYLE_FLAT;
+ parameters.Style |= NativeMethods.TBSTYLE_FLAT;
if (TextAlign == BetterToolbarTextAlign.Right)
- cp.Style |= NativeMethods.TBSTYLE_LIST;
+ parameters.Style |= NativeMethods.TBSTYLE_LIST;
- return cp;
+ return parameters;
}
}
///
///
///
- protected override void CreateHandle()
+ private protected override void InitializeCommonControls()
{
- InitializeCommonControls();
+ if (RecreatingHandle)
+ return;
+
+ IntPtr userCookie = UnsafeNativeMethods.ThemingScope.Activate();
+
+ try
+ {
+ NativeMethods.INITCOMMONCONTROLSEX initializeCommonControls = new NativeMethods.INITCOMMONCONTROLSEX();
+ initializeCommonControls.dwICC = NativeMethods.ICC_BAR_CLASSES;
- base.CreateHandle();
+ SafeNativeMethods.InitCommonControlsEx(initializeCommonControls);
+ }
+ finally
+ {
+ UnsafeNativeMethods.ThemingScope.Deactivate(userCookie);
+ }
}
///
@@ -598,20 +616,20 @@ protected override void OnHandleCreated(EventArgs e)
// Setup the toolbar based on the state of properties. These properties may have been set before
// a handle was created, so setting them would have had no effect on the toolbar.
- SendMessage(NativeMethods.TB_BUTTONSTRUCTSIZE, Marshal.SizeOf(typeof(NativeMethods.TBBUTTON)), 0);
+ UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.TB_BUTTONSTRUCTSIZE, Marshal.SizeOf(typeof(NativeMethods.TBBUTTON)), 0);
// The default is no drop-down arrows, so this is only necessary if drop-down arrows are enabled.
if (DropDownArrows)
- UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), NativeMethods.TB_SETEXTENDEDSTYLE, 0, NativeMethods.TBSTYLE_EX_DRAWDDARROWS);
+ UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.TB_SETEXTENDEDSTYLE, 0, NativeMethods.TBSTYLE_EX_DRAWDDARROWS);
if (ImageList != null)
{
- SendMessage(NativeMethods.TB_SETIMAGELIST, 0, ImageList.Handle);
+ UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.TB_SETIMAGELIST, 0, ImageList.Handle);
}
else
{
- SendMessage(NativeMethods.TB_SETIMAGELIST, 0, IntPtr.Zero);
- UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), NativeMethods.WM_USER + 32, 0, NativeMethods.Util.MAKELONG(ImageSize.Width, ImageSize.Height));
+ UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.TB_SETIMAGELIST, 0, IntPtr.Zero);
+ UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.WM_USER + 32, 0, NativeMethods.Util.MAKELONG(ImageSize.Width, ImageSize.Height));
}
InitializeItems();
@@ -624,28 +642,6 @@ protected override void OnHandleCreated(EventArgs e)
EndUpdate();
}
- ///
- /// Initializes common controls for the toolbar classes.
- ///
- private void InitializeCommonControls()
- {
- if (!RecreatingHandle)
- {
- IntPtr userCookie = UnsafeNativeMethods.ThemingScope.Activate();
-
- try
- {
- NativeMethods.INITCOMMONCONTROLSEX initializeCommonControls = new NativeMethods.INITCOMMONCONTROLSEX();
- initializeCommonControls.dwICC = NativeMethods.ICC_BAR_CLASSES;
-
- SafeNativeMethods.InitCommonControlsEx(initializeCommonControls);
- }
- finally
- {
- UnsafeNativeMethods.ThemingScope.Deactivate(userCookie);
- }
- }
- }
///
/// Initializes items on the toolbar.
@@ -661,11 +657,11 @@ private void InitializeItems()
{
NativeMethods.TBBUTTON structure = item.ComputeTbButton();
- Marshal.StructureToPtr(structure, (IntPtr)checked((long)items + (structureSize * item.ItemIndex)), true);
+ Marshal.StructureToPtr(structure, (IntPtr)checked((long)items + (structureSize * item.DisplayedItemIndex)), true);
}
- SendMessage(NativeMethods.TB_ADDBUTTONS, Items.Count, items);
- SendMessage(NativeMethods.TB_AUTOSIZE, 0, 0);
+ UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.TB_ADDBUTTONS, Items.Count, items);
+ UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.TB_AUTOSIZE, 0, 0);
UpdateItemDimensions();
AdjustSize(Dock);
@@ -708,7 +704,7 @@ protected override void Dispose(bool disposing)
private protected void PerformCancelMode()
{
if (IsHandleCreated)
- SendMessage(NativeMethods.WM_CANCELMODE, 0, 0);
+ UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.WM_CANCELMODE, 0, 0);
}
///
@@ -723,10 +719,16 @@ private protected void PerformCancelMode()
public void FocusButton(BetterToolbarButton button)
{
if (IsHandleCreated)
+ {
if (button is null)
- SendMessage(NativeMethods.TB_SETHOTITEM, -1, 0);
+ {
+ UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.TB_SETHOTITEM, -1, 0);
+ }
else
- SendMessage(NativeMethods.TB_SETHOTITEM, button.ItemIndex, 0);
+ {
+ UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.TB_SETHOTITEM, button.DisplayedItemIndex, 0);
+ }
+ }
}
///
@@ -833,16 +835,16 @@ private BetterMenuItem GetMenuItemByIndex(IntPtr handle, int index)
{
BetterMenuItem item = GetMenuItemByIndex(childHandle, i++);
- if (item != null && item.Parent != null && item.Parent is BetterMenuItem parentItem)
+ if (item != null && item.OwnerElement != null && item.OwnerElement is BetterMenuItem parentItem)
return parentItem;
}
}
else
{
// This menu item does not have any sub-items.
- ICommandExecutor commandExecutor = Command.GetItem(identifier);
+ GlobalCommand globalCommand = GlobalCommand.GetCommandExecutor(identifier);
- if (commandExecutor != null && commandExecutor is BetterMenuReference menuReference && menuReference.Menu is BetterMenuItem item)
+ if (globalCommand != null && globalCommand.CommandExecutor is BetterMenuReference menuReference && menuReference.Menu is BetterMenuItem item)
return item;
}
@@ -862,7 +864,7 @@ protected internal IntPtr AddString(string text)
}
if (text.Length > 0)
- return SendMessage(NativeMethods.TB_ADDSTRING, 0, text + '\0'.ToString());
+ return UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.TB_ADDSTRING, 0, text + '\0'.ToString());
return (IntPtr)(-1);
}
@@ -894,7 +896,7 @@ private protected virtual void PerformDropDownItemSelect(BetterMenuButton item)
throw new ArgumentNullException(nameof(item));
}
- item.PerformSelect();
+ item.ItemFocused();
}
///
@@ -972,6 +974,7 @@ protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
{
_currentScaleDX = factor.Width;
_currentScaleDY = factor.Height;
+
base.ScaleControl(factor, specified);
}
@@ -1022,15 +1025,6 @@ protected override void SetBoundsCore(int x, int y, int width, int height, Bound
base.SetBoundsCore(x, y, width, height, specified);
}
- ///
- ///
- ///
- ///
- protected override void OnSizeChanged(EventArgs e)
- {
- base.OnSizeChanged(e);
- }
-
///
///
///
@@ -1039,10 +1033,7 @@ protected override void OnSizeChanged(EventArgs e)
///
///
///
- void ISupportInitialize.EndInit()
- {
- EndUpdate();
- }
+ void ISupportInitialize.EndInit() => EndUpdate();
///
/// Ensures that the dimensions for each item in the toolbar are correct.
@@ -1081,247 +1072,8 @@ public void UpdateItemDimensions()
AdjustSize(Dock);
}
- ///
- /// This method is raised before a drop-down button has been clicked with the intention of showing the drop-down menu.
- ///
- /// The event arguments as an instance of
- protected virtual void OnBeforeMenuDroppedDown(BetterToolbarMenuDroppedDownEventArgs e)
- {
- if (e is null)
- {
- throw new ArgumentNullException(nameof(e));
- }
-
- BeforeMenuDroppedDown?.Invoke(this, e);
- }
-
- ///
- /// This method is raised after a drop-down button has been clicked and has shown the drop-down menu.
- ///
- /// The event arguments as an instance of
- protected virtual void OnAfterMenuDroppedDown(BetterToolbarMenuDroppedDownEventArgs e)
- {
- if (e is null)
- {
- throw new ArgumentNullException(nameof(e));
- }
-
- AfterMenuDroppedDown?.Invoke(this, e);
- }
-
- ///
- /// This method is raised when has been changed.
- ///
- /// The event arguments as an instance of .
- protected virtual void OnAppearanceChanged(EventArgs e)
- {
- if (e is null)
- {
- throw new ArgumentNullException(nameof(e));
- }
-
- AppearanceChanged?.Invoke(this, e);
- }
-
- ///
- /// This method is raised when has been changed.
- ///
- /// The event arguments as an instance of .
- protected virtual void OnBorderStyleChanged(EventArgs e)
- {
- if (e is null)
- {
- throw new ArgumentNullException(nameof(e));
- }
-
- BorderStyleChanged?.Invoke(this, e);
- }
-
- ///
- /// This method is raised when has been changed.
- ///
- /// The event arguments as an instance of .
- protected virtual void OnDividerChanged(EventArgs e)
- {
- if (e is null)
- {
- throw new ArgumentNullException(nameof(e));
- }
-
- DividerChanged?.Invoke(this, e);
- }
-
- ///
- /// This method is raised when has been changed.
- ///
- /// The event arguments as an instance of .
- protected virtual void OnDropDownArrowsChanged(EventArgs e)
- {
- if (e is null)
- {
- throw new ArgumentNullException(nameof(e));
- }
-
- DropDownArrowsChanged?.Invoke(this, e);
- }
-
- ///
- /// This method is raised when has been changed.
- ///
- /// The event arguments as an instance of .
- protected virtual void OnImageListChanged(EventArgs e)
- {
- if (e is null)
- {
- throw new ArgumentNullException(nameof(e));
- }
-
- ImageListChanged?.Invoke(this, e);
- }
-
- ///
- /// This method is raised when has been changed.
- ///
- /// The event arguments as an instance of .
- protected virtual void OnImageSizeChanged(EventArgs e)
- {
- if (e is null)
- {
- throw new ArgumentNullException(nameof(e));
- }
-
- ImageSizeChanged?.Invoke(this, e);
- }
-
- ///
- /// This method is raised when has been changed.
- ///
- /// The event arguments as an instance of .
- protected virtual void OnShowToolTipsChanged(EventArgs e)
- {
- if (e is null)
- {
- throw new ArgumentNullException(nameof(e));
- }
-
- ShowToolTipsChanged?.Invoke(this, e);
- }
-
- ///
- /// This method is raised when has been changed.
- ///
- /// The event arguments as an instance of .
- protected virtual void OnTextAlignChanged(EventArgs e)
- {
- if (e is null)
- {
- throw new ArgumentNullException(nameof(e));
- }
-
- TextAlignChanged?.Invoke(this, e);
- }
-
- ///
- /// This method is raised when has been changed.
- ///
- /// The event arguments as an instance of .
- protected virtual void OnWrappableChanged(EventArgs e)
- {
- if (e is null)
- {
- throw new ArgumentNullException(nameof(e));
- }
-
- WrappableChanged?.Invoke(this, e);
- }
-
- ///
- /// This event is raised before a drop-down button has been clicked with the intention of showing the drop-down menu.
- ///
- public event EventHandler BeforeMenuDroppedDown;
-
- ///
- /// This event is raised after a drop-down button has been clicked and has shown the drop-down menu.
- ///
- public event EventHandler AfterMenuDroppedDown;
-
- ///
- /// This event is raised has been changed.
- ///
- public event EventHandler AppearanceChanged;
-
- ///
- /// This event is raised has been changed.
- ///
- public event EventHandler BorderStyleChanged;
-
- ///
- /// This event is raised has been changed.
- ///
- public event EventHandler DividerChanged;
+
- ///
- /// This event is raised has been changed.
- ///
- public event EventHandler DropDownArrowsChanged;
-
- ///
- /// This event is raised after has been changed.
- ///
- public event EventHandler ImageListChanged;
-
- ///
- /// This event is raised after has been changed.
- ///
- public event EventHandler ImageSizeChanged;
-
- ///
- /// This event is raised after has been changed.
- ///
- public event EventHandler ShowToolTipsChanged;
-
- ///
- /// This event is raised after has been changed.
- ///
- public event EventHandler TextAlignChanged;
-
- ///
- /// This event is raised after has been changed.
- ///
- public event EventHandler WrappableChanged;
-
- ///
- /// Occurs when the property has changed.
- ///
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new event EventHandler ImeModeChanged
- {
- add => base.ImeModeChanged += value;
- remove => base.ImeModeChanged -= value;
- }
-
- ///
- /// Occurs when the property has changed.
- ///
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new event EventHandler ForeColorChanged
- {
- add => base.ForeColorChanged += value;
- remove => base.ForeColorChanged -= value;
- }
-
- ///
- /// Occurs when the property value changes.
- ///
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new event EventHandler RightToLeftChanged
- {
- add => base.RightToLeftChanged += value;
- remove => base.RightToLeftChanged -= value;
- }
///
/// This event handler is raised when the handle associated with image list associated with this toolbar is recreated.
@@ -1359,237 +1111,5 @@ private void DetachImageListHandle(object sender, EventArgs e)
ImageList = null;
}
-
- private HashSet constants = new HashSet();
-
- ///
- ///
- ///
- ///
- protected override void WndProc(ref Message m)
- {
- switch (m.Msg)
- {
- case NativeMethods.WM_COMMAND + NativeMethods.WM_REFLECT:
- if (WmReflectCommand(ref m))
- return;
-
- break;
- case NativeMethods.WM_MENUSELECT:
- if (WmMenuSelect(ref m))
- return;
-
- break;
- case NativeMethods.WM_NOTIFY:
- if (WmNotify(ref m))
- return;
-
- break;
- case NativeMethods.WM_NOTIFY + NativeMethods.WM_REFLECT:
- if (WmReflectNotify(ref m))
- return;
-
- break;
- }
-
- base.WndProc(ref m);
- }
-
- ///
- /// Handles the WM_COMMAND + WM_REFLECT message.
- ///
- private protected virtual bool WmReflectCommand(ref Message m)
- {
- int index = NativeMethods.Util.LOWORD(m.WParam);
-
- if (Items[index] is BetterToolbarClickableButton button)
- button.PerformClick();
-
- ResetMouseEventArgs();
-
- return false;
- }
-
- ///
- /// Handles the WM_MENUSELECT message.
- ///
- private protected virtual bool WmMenuSelect(ref Message m)
- {
- // If the menu item is a popup item (i.e. it has sub-items) then the data sent
- // with WM_MENUSELECT to identify which menu was selected differs than any other
- // menu item. An index to the popup item is sent instead of command identifier.
- int identifier = NativeMethods.Util.LOWORD(m.WParam);
- int flags = NativeMethods.Util.HIWORD(m.WParam);
-
- if ((flags & NativeMethods.MF_POPUP) != 0)
- {
- // The item that was selected has sub-items; therefore, we have to identify
- // it recursively using the internal menu tree.
- BetterMenuItem item = GetMenuItemByIndex(m.LParam, identifier);
-
- if (item is BetterMenuButton button)
- button.PerformSelect();
- }
- else
- {
- // The item that was selected does not have any sub-items; therefore, we can
- // identify it using its command identifier.
- ICommandExecutor commandExecutor = Command.GetItem(identifier);
-
- if (commandExecutor != null && commandExecutor is BetterMenuReference menuReference && menuReference.Menu is BetterMenuButton button)
- button.PerformSelect();
- }
-
- return false;
- }
-
- ///
- /// Handles the WM_NOTIFY message.
- ///
- private protected virtual bool WmNotify(ref Message m) => WmReflectNotify(ref m);
-
- ///
- /// Handles the WM_NOTIFY + WM_REFLECT message.
- ///
- private protected virtual unsafe bool WmReflectNotify(ref Message m)
- {
- NativeMethods.NMHDR* note = (NativeMethods.NMHDR*)m.LParam;
-
- switch (note->code)
- {
- case NativeMethods.TBN_QUERYINSERT:
- m.Result = (IntPtr)1;
- break;
-
- case NativeMethods.TBN_DROPDOWN:
- TbnDropDown(ref m);
- break;
- case NativeMethods.TTN_NEEDTEXTA:
- TtnNeedTextA(ref m);
- m.Result = (IntPtr)1;
- break;
-
- case NativeMethods.TTN_NEEDTEXTW:
- if (Marshal.SystemDefaultCharSize == 2)
- {
- TtnNeedTextW(ref m);
- m.Result = (IntPtr)1;
- }
- break;
- case NativeMethods.TBN_HOTITEMCHANGE:
- TbnHotItemChange(ref m);
- break;
- case NativeMethods.NM_CUSTOMDRAW:
- if (!AutoSizeItems)
- UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), NativeMethods.TB_SETBUTTONSIZE, 0, NativeMethods.Util.MAKELPARAM(0, ItemHeight));
-
- return true;
- }
-
- return false;
- }
-
- ///
- /// Handles the TBN_DROPDOWN message within the WM_NOTIFY and WM_NOTIFY + WM_REFLECT messages.
- ///
- private protected virtual bool TbnDropDown(ref Message m)
- {
- NativeMethods.NMTOOLBAR note = (NativeMethods.NMTOOLBAR)m.GetLParam(typeof(NativeMethods.NMTOOLBAR));
-
- BetterToolbarItem item = Items.GetItemByUniqueIdentifier(note.iItem);
-
- if (item != null && item is BetterToolbarDropDownButton button)
- PerformDropDown(button, false);
-
- return false;
- }
-
- ///
- /// Handles the TTN_NEEDTEXTA message within the WM_NOTIFY and WM_NOTIFY + WM_REFLECT messages.
- ///
- private protected virtual bool TtnNeedTextA(ref Message m) => TtnNeedTextW(ref m);
-
- ///
- /// Handles the TTN_NEEDTEXTW message within the WM_NOTIFY and WM_NOTIFY + WM_REFLECT messages.
- ///
- private protected virtual bool TtnNeedTextW(ref Message m)
- {
- NativeMethods.TOOLTIPTEXT toolTipText = (NativeMethods.TOOLTIPTEXT)m.GetLParam(typeof(NativeMethods.TOOLTIPTEXT));
-
- BetterToolbarItem item = Items.GetItemByUniqueIdentifier((int)toolTipText.hdr.idFrom);
-
- // Only items that are buttons (i.e. not a separator) are capable
- // of showing tool tips.
- if (item != null && item is BetterToolbarButton button)
- {
- toolTipText.hinst = IntPtr.Zero;
-
- if (button.ToolTipText != null)
- toolTipText.lpszText = GetToolTipText(button);
-
- if (string.IsNullOrEmpty(toolTipText.lpszText))
- toolTipText.lpszText = null;
-
- if (RightToLeft == RightToLeft.Yes)
- toolTipText.uFlags |= NativeMethods.TTF_RTLREADING;
-
- Marshal.StructureToPtr(toolTipText, m.LParam, false);
- }
-
- return false;
- }
-
- ///
- /// Handles the TBN_HOTITEMCHANGE message within the WM_NOTIFY and WM_NOTIFY + WM_REFLECT messages.
- ///
- private protected virtual bool TbnHotItemChange(ref Message m)
- {
- // Should we set the hot item?
- NativeMethods.NMTBHOTITEM nmTbHotItem = (NativeMethods.NMTBHOTITEM)m.GetLParam(typeof(NativeMethods.NMTBHOTITEM));
-
- BetterToolbarItem item = Items.GetItemByUniqueIdentifier(nmTbHotItem.idNew);
-
- if (item != null && item is BetterToolbarButton button)
- {
- if (NativeMethods.HICF_ENTERING == (nmTbHotItem.dwFlags & NativeMethods.HICF_ENTERING))
- {
- _hotButton = button;
- }
- else if (NativeMethods.HICF_LEAVING == (nmTbHotItem.dwFlags & NativeMethods.HICF_LEAVING))
- {
- _hotButton = null;
- }
- else if (NativeMethods.HICF_MOUSE == (nmTbHotItem.dwFlags & NativeMethods.HICF_MOUSE))
- {
- _hotButton = button;
- }
- else if (NativeMethods.HICF_ARROWKEYS == (nmTbHotItem.dwFlags & NativeMethods.HICF_ARROWKEYS))
- {
- _hotButton = button;
- }
- else if (NativeMethods.HICF_ACCELERATOR == (nmTbHotItem.dwFlags & NativeMethods.HICF_ACCELERATOR))
- {
- _hotButton = button;
- }
- else if (NativeMethods.HICF_DUPACCEL == (nmTbHotItem.dwFlags & NativeMethods.HICF_DUPACCEL))
- {
- _hotButton = button;
- }
- else if (NativeMethods.HICF_RESELECT == (nmTbHotItem.dwFlags & NativeMethods.HICF_RESELECT))
- {
- _hotButton = button;
- }
- else if (NativeMethods.HICF_LMOUSE == (nmTbHotItem.dwFlags & NativeMethods.HICF_LMOUSE))
- {
- _hotButton = button;
- }
- else if (NativeMethods.HICF_TOGGLEDROPDOWN == (nmTbHotItem.dwFlags & NativeMethods.HICF_TOGGLEDROPDOWN))
- {
- _hotButton = button;
- }
- }
-
- return false;
- }
}
}
\ No newline at end of file
diff --git a/src/BetterControls/BetterToolbar/BetterToolbarBase.Events.cs b/src/BetterControls/BetterToolbar/BetterToolbarBase.Events.cs
new file mode 100644
index 0000000..2adb543
--- /dev/null
+++ b/src/BetterControls/BetterToolbar/BetterToolbarBase.Events.cs
@@ -0,0 +1,60 @@
+using System;
+using System.ComponentModel;
+using System.Windows.Forms;
+
+namespace BetterControls
+{
+ ///
+ /// Wrapper of the Windows Toolbar classes.
+ ///
+ partial class BetterToolbarBase
+ {
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new event PaintEventHandler Paint
+ {
+ add => base.Paint += value;
+ remove => base.Paint -= value;
+ }
+
+ [Browsable(true)]
+ [EditorBrowsable(EditorBrowsableState.Always)]
+ public new event EventHandler AutoSizeChanged
+ {
+ add => base.AutoSizeChanged += value;
+ remove => base.AutoSizeChanged -= value;
+ }
+
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new event EventHandler BackColorChanged
+ {
+ add => base.BackColorChanged += value;
+ remove => base.BackColorChanged -= value;
+ }
+
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new event EventHandler BackgroundImageChanged
+ {
+ add => base.BackgroundImageChanged += value;
+ remove => base.BackgroundImageChanged -= value;
+ }
+
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new event EventHandler BackgroundImageLayoutChanged
+ {
+ add => base.BackgroundImageLayoutChanged += value;
+ remove => base.BackgroundImageLayoutChanged -= value;
+ }
+
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new event EventHandler TextChanged
+ {
+ add => base.TextChanged += value;
+ remove => base.TextChanged -= value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterToolbar/BetterToolbarBase.cs b/src/BetterControls/BetterToolbar/BetterToolbarBase.cs
index 51059d5..eb9a537 100644
--- a/src/BetterControls/BetterToolbar/BetterToolbarBase.cs
+++ b/src/BetterControls/BetterToolbar/BetterToolbarBase.cs
@@ -1,23 +1,19 @@
-using System;
-using System.ComponentModel;
+using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace BetterControls
{
- [DesignerCategory("Code")]
- public abstract class BetterToolbarBase : BetterControl
+ ///
+ /// Wrapper of the Windows Toolbar classes.
+ ///
+ public abstract partial class BetterToolbarBase : BetterCommonControl
{
///
/// Initialize a new instance of .
///
protected BetterToolbarBase() { }
-
- #region Hidden Properties
-
- ///
- ///
- ///
+
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override RightToLeft RightToLeft
@@ -25,10 +21,7 @@ public override RightToLeft RightToLeft
get => base.RightToLeft;
set => base.RightToLeft = value;
}
-
- ///
- ///
- ///
+
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override Color BackColor
@@ -36,10 +29,7 @@ public override Color BackColor
get => base.BackColor;
set => base.BackColor = value;
}
-
- ///
- ///
- ///
+
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override Image BackgroundImage
@@ -47,10 +37,7 @@ public override Image BackgroundImage
get => base.BackgroundImage;
set => base.BackgroundImage = value;
}
-
- ///
- ///
- ///
+
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override ImageLayout BackgroundImageLayout
@@ -58,10 +45,7 @@ public override ImageLayout BackgroundImageLayout
get => base.BackgroundImageLayout;
set => base.BackgroundImageLayout = value;
}
-
- ///
- ///
- ///
+
[Bindable(false)]
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
@@ -71,20 +55,14 @@ public override string Text
get => base.Text;
set => base.Text = value;
}
-
- ///
- ///
- ///
+
[EditorBrowsable(EditorBrowsableState.Never)]
protected override bool DoubleBuffered
{
get => base.DoubleBuffered;
set => base.DoubleBuffered = value;
}
-
- ///
- ///
- ///
+
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override Color ForeColor
@@ -92,10 +70,7 @@ public override Color ForeColor
get => base.ForeColor;
set => base.ForeColor = value;
}
-
- ///
- ///
- ///
+
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new ImeMode ImeMode
@@ -103,74 +78,5 @@ public override Color ForeColor
get => base.ImeMode;
set => base.ImeMode = value;
}
-
- #endregion
-
- #region Hide Events
-
- ///
- ///
- ///
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new event PaintEventHandler Paint
- {
- add => base.Paint += value;
- remove => base.Paint -= value;
- }
-
- ///
- ///
- ///
- [Browsable(true)]
- [EditorBrowsable(EditorBrowsableState.Always)]
- public new event EventHandler AutoSizeChanged
- {
- add => base.AutoSizeChanged += value;
- remove => base.AutoSizeChanged -= value;
- }
-
- ///
- ///
- ///
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new event EventHandler BackColorChanged
- {
- add => base.BackColorChanged += value;
- remove => base.BackColorChanged -= value;
- }
-
- ///
- ///
- ///
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new event EventHandler BackgroundImageChanged
- {
- add => base.BackgroundImageChanged += value;
- remove => base.BackgroundImageChanged -= value;
- }
-
- ///
- ///
- ///
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new event EventHandler BackgroundImageLayoutChanged
- {
- add => base.BackgroundImageLayoutChanged += value;
- remove => base.BackgroundImageLayoutChanged -= value;
- }
-
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new event EventHandler TextChanged
- {
- add => base.TextChanged += value;
- remove => base.TextChanged -= value;
- }
-
- #endregion
}
}
\ No newline at end of file
diff --git a/src/BetterControls/BetterToolbar/BetterToolbarDropDownMenu.cs b/src/BetterControls/BetterToolbar/BetterToolbarDropDownMenu.cs
new file mode 100644
index 0000000..7942d90
--- /dev/null
+++ b/src/BetterControls/BetterToolbar/BetterToolbarDropDownMenu.cs
@@ -0,0 +1,48 @@
+using System.ComponentModel;
+using System.Windows.Forms;
+
+namespace BetterControls
+{
+ ///
+ /// Menu specifically designed for use with drop-down toolbar buttons.
+ ///
+ [ToolboxItem(true)]
+ [DesignTimeVisible(true)]
+ public class BetterToolbarDropDownMenu : BetterMenuRoot
+ {
+ ///
+ /// Initialize a new instance of .
+ ///
+ public BetterToolbarDropDownMenu() { }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The drop-down button that is associated with this drop-down menu.
+ public BetterToolbarDropDownMenu(BetterToolbarDropDownButton item)
+ {
+ ParentControl = item;
+ }
+
+ public object ParentControl { get; set; }
+
+ ///
+ ///
+ ///
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override ImageList ImageList
+ {
+ get
+ {
+ if (ParentControl is BetterToolbarDropDownButton item && item.OwnerElement is BetterToolbar toolbar)
+ return toolbar.MenuImageList;
+
+ return null;
+ }
+ set { }
+ }
+
+ public void Recreate() => RecreateHandle();
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterToolbar/BetterToolbarExtensions.cs b/src/BetterControls/BetterToolbar/BetterToolbarExtensions.cs
index e77e5e1..09902c4 100644
--- a/src/BetterControls/BetterToolbar/BetterToolbarExtensions.cs
+++ b/src/BetterControls/BetterToolbar/BetterToolbarExtensions.cs
@@ -25,7 +25,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
*/
using System;
-using System.Windows.Forms;
namespace BetterControls
{
@@ -35,7 +34,7 @@ namespace BetterControls
public static class BetterToolbarExtensions
{
///
- /// Adds a to a collection of toolbar items.
+ /// Adds a to the collection of toolbar items.
///
/// The collection of toolbar items to add to.
public static void AddSeparator(this BetterToolbarItemCollection items)
diff --git a/src/BetterControls/BetterToolbar/BetterToolbarItemCollection.cs b/src/BetterControls/BetterToolbar/BetterToolbarItemCollection.cs
index bbe7e47..d237d9c 100644
--- a/src/BetterControls/BetterToolbar/BetterToolbarItemCollection.cs
+++ b/src/BetterControls/BetterToolbar/BetterToolbarItemCollection.cs
@@ -42,13 +42,15 @@ public class BetterToolbarItemCollection : ElementCollection,
///
/// Initialize a new instance of .
///
- /// The parent toolbar as an instance of .
+ /// The owner control as an instance of .
internal BetterToolbarItemCollection(BetterToolbar ownerToolbar)
: base(ownerToolbar)
{
+ _uniqueItems = new HashSet();
_uniqueIdentifierItems = new Dictionary();
}
+ private HashSet _uniqueItems;
private Dictionary _uniqueIdentifierItems;
///
@@ -153,6 +155,22 @@ public virtual void Add(string text, string description, int imageIndex)
});
}
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override void InsertRange(int index, params BetterToolbarItem[] items)
+ {
+ foreach (BetterToolbarItem item in items)
+ {
+ if (_uniqueItems.Contains(item))
+ throw new ArgumentException("Cannot add or insert the item in more than one place. You must first remove it from its current location or clone it.");
+ }
+
+ base.InsertRange(index, items);
+ }
+
///
/// Gets a from the collection by its identifier.
///
@@ -187,6 +205,7 @@ private protected override void PerformItemsAdded(int startIndex, params BetterT
item.SetUniqueIdentifier(item.ItemIndex);
+ _uniqueItems.Add(item);
_uniqueIdentifierItems.Add(item.UniqueIdentifier, item);
}
}
@@ -209,6 +228,7 @@ private protected override void PerformItemsRemoved(int startIndex, params Bette
{
BetterToolbarItem item = items[i];
+ _uniqueItems.Remove(item);
_uniqueIdentifierItems.Remove(item.UniqueIdentifier);
item.ResetUniqueIdentifier();
diff --git a/src/BetterControls/BetterToolbar/BetterToolbarMenuDroppedDownEventArgs.cs b/src/BetterControls/BetterToolbar/BetterToolbarMenuDroppedDownEventArgs.cs
new file mode 100644
index 0000000..3dc37f6
--- /dev/null
+++ b/src/BetterControls/BetterToolbar/BetterToolbarMenuDroppedDownEventArgs.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BetterControls
+{
+ public class BetterToolbarMenuDroppedDownEventArgs
+ {
+ public BetterToolbarMenuDroppedDownEventArgs(BetterToolbarDropDownButton item)
+ {
+ Item = item ?? throw new ArgumentNullException(nameof(item));
+ }
+
+ public BetterToolbarDropDownButton Item { get; }
+ }
+}
diff --git a/src/BetterControls/BetterToolbar/Items/BetterToolbarButton.NativeStructure.cs b/src/BetterControls/BetterToolbar/Items/BetterToolbarButton.NativeStructure.cs
index c8626fe..48b25a1 100644
--- a/src/BetterControls/BetterToolbar/Items/BetterToolbarButton.NativeStructure.cs
+++ b/src/BetterControls/BetterToolbar/Items/BetterToolbarButton.NativeStructure.cs
@@ -1,4 +1,30 @@
-using System.Runtime.InteropServices;
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using System.Runtime.InteropServices;
namespace BetterControls
{
diff --git a/src/BetterControls/BetterToolbar/Items/BetterToolbarButton.cs b/src/BetterControls/BetterToolbar/Items/BetterToolbarButton.cs
index 912b9c8..a9e4d2d 100644
--- a/src/BetterControls/BetterToolbar/Items/BetterToolbarButton.cs
+++ b/src/BetterControls/BetterToolbar/Items/BetterToolbarButton.cs
@@ -125,8 +125,8 @@ private protected BetterToolbarButton(BetterToolbar ownerToolbar)
///
/// Gets or sets the description of the button.
///
- [Category(Categories.Appearance)]
- [Description("The text of the toolbar item.")]
+ [Category(Categories.Data)]
+ [Description("The description of the toolbar item.")]
[DefaultValue("")]
[Localizable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
@@ -165,10 +165,10 @@ public virtual bool Enabled
}
///
- /// Gets or sets the index of the image from the menu image list to be shown in the menu item.
+ /// Gets or sets the index of the image from the toolbar image list to be shown in the toolbar item.
///
[Category(Categories.Appearance)]
- [Description("The index of the image from the menu image list to be shown in the menu item.")]
+ [Description("The index of the image from the toolbar image list to be shown in the toolbar item.")]
[DefaultValue(-1)]
[Localizable(false)]
[Editor("ImageIndexEditor", typeof(UITypeEditor))]
@@ -435,6 +435,22 @@ public virtual void Focus()
OwnerToolbar.FocusButton(this);
}
+ private protected override void ConfigureClone(BetterToolbarItem item)
+ {
+ base.ConfigureClone(item);
+
+ BetterToolbarButton button = (BetterToolbarButton)item;
+
+ button.Description = Description;
+ button.Enabled = Enabled;
+ button.ImageIndex = ImageIndex;
+ button.ImageKey = ImageKey;
+ button.Pressed = Pressed;
+ button.ShowImage = ShowImage;
+ button.Text = Text;
+ button.ToolTipText = ToolTipText;
+ }
+
///
///
///
diff --git a/src/BetterControls/BetterToolbar/Items/BetterToolbarClickableButton.Events.cs b/src/BetterControls/BetterToolbar/Items/BetterToolbarClickableButton.Events.cs
new file mode 100644
index 0000000..e6ca135
--- /dev/null
+++ b/src/BetterControls/BetterToolbar/Items/BetterToolbarClickableButton.Events.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace BetterControls
+{
+ ///
+ /// Extend this class to create a toolbar item that is a clickable button.
+ ///
+ partial class BetterToolbarClickableButton
+ {
+ ///
+ /// This method is raised when the toolbar button is clicked.
+ ///
+ /// The event arguments as an instance of .
+ protected virtual void OnClick(EventArgs e)
+ {
+ if (e is null)
+ {
+ throw new ArgumentNullException(nameof(e));
+ }
+
+ Click?.Invoke(this, e);
+ }
+
+ ///
+ /// This event is raised when the toolbar button is clicked.
+ ///
+ public event EventHandler Click;
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterToolbar/Items/BetterToolbarClickableButton.cs b/src/BetterControls/BetterToolbar/Items/BetterToolbarClickableButton.cs
index 2666331..026d1a1 100644
--- a/src/BetterControls/BetterToolbar/Items/BetterToolbarClickableButton.cs
+++ b/src/BetterControls/BetterToolbar/Items/BetterToolbarClickableButton.cs
@@ -5,7 +5,7 @@ namespace BetterControls
///
/// Extend this class to create a toolbar item that is a clickable button.
///
- public abstract class BetterToolbarClickableButton : BetterToolbarButton
+ public abstract partial class BetterToolbarClickableButton : BetterToolbarButton
{
///
/// Initialize a new instance of .
@@ -75,24 +75,5 @@ public void PerformClick()
OnClick(EventArgs.Empty);
}
}
-
- ///
- /// This method is raised when the toolbar button is clicked.
- ///
- /// The event arguments as an instance of .
- protected virtual void OnClick(EventArgs e)
- {
- if (e is null)
- {
- throw new ArgumentNullException(nameof(e));
- }
-
- Click?.Invoke(this, e);
- }
-
- ///
- /// This event is raised when the toolbar button is clicked.
- ///
- public event EventHandler Click;
}
}
\ No newline at end of file
diff --git a/src/BetterControls/BetterToolbar/Items/BetterToolbarDropDownButton.NativeStructure.cs b/src/BetterControls/BetterToolbar/Items/BetterToolbarDropDownButton.NativeStructure.cs
new file mode 100644
index 0000000..0d610f7
--- /dev/null
+++ b/src/BetterControls/BetterToolbar/Items/BetterToolbarDropDownButton.NativeStructure.cs
@@ -0,0 +1,60 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+namespace BetterControls
+{
+ ///
+ /// Represents a toolbar drop-down button.
+ ///
+ partial class BetterToolbarDropDownButton
+ {
+ ///
+ ///
+ ///
+ ///
+ internal override NativeMethods.TBBUTTON ComputeTbButton()
+ {
+ NativeMethods.TBBUTTON structure = base.ComputeTbButton();
+
+ structure.fsStyle = NativeMethods.BTNS_DROPDOWN;
+
+ return structure;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ internal override NativeMethods.TBBUTTONINFO ComputeTbButtonInfo()
+ {
+ NativeMethods.TBBUTTONINFO button = base.ComputeTbButtonInfo();
+
+ button.fsStyle |= NativeMethods.BTNS_DROPDOWN;
+
+ return button;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterToolbar/Items/BetterToolbarDropDownButton.cs b/src/BetterControls/BetterToolbar/Items/BetterToolbarDropDownButton.cs
new file mode 100644
index 0000000..5448428
--- /dev/null
+++ b/src/BetterControls/BetterToolbar/Items/BetterToolbarDropDownButton.cs
@@ -0,0 +1,140 @@
+/* COPYRIGHT NOTICE
+
+MIT License
+
+Copyright (c) 2022 SharpVNC Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+using BetterControls.ComponentModel;
+using System.ComponentModel;
+
+namespace BetterControls
+{
+ ///
+ /// Represents a toolbar drop-down button.
+ ///
+ public partial class BetterToolbarDropDownButton : BetterToolbarButton
+ {
+ ///
+ /// Initialize a new instance of .
+ ///
+ public BetterToolbarDropDownButton()
+ {
+ _dropDownMenu = new BetterToolbarDropDownMenu(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the button.
+ public BetterToolbarDropDownButton(string text)
+ : base(text)
+ {
+ _dropDownMenu = new BetterToolbarDropDownMenu(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the button.
+ /// The description of the button.
+ public BetterToolbarDropDownButton(string text, string description)
+ : base(text, description)
+ {
+ _dropDownMenu = new BetterToolbarDropDownMenu(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the button.
+ /// The index of the image from the toolbar image list to be shown in the button.
+ public BetterToolbarDropDownButton(string text, int imageIndex)
+ : base(text, imageIndex)
+ {
+ _dropDownMenu = new BetterToolbarDropDownMenu(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The text of the button.
+ /// The description of the button.
+ /// The index of the image from the toolbar image list to be shown in the button.
+ public BetterToolbarDropDownButton(string text, string description, int imageIndex)
+ : base(text, description, imageIndex)
+ {
+ _dropDownMenu = new BetterToolbarDropDownMenu(this);
+ }
+
+ ///
+ /// Initialize a new instance of .
+ ///
+ /// The owner toolbar as an instance of .
+ private protected BetterToolbarDropDownButton(BetterToolbar ownerToolbar)
+ : base(ownerToolbar)
+ {
+ _dropDownMenu = new BetterToolbarDropDownMenu(this);
+ }
+
+ private BetterToolbarDropDownMenu _dropDownMenu;
+
+ ///
+ /// Gets or sets the drop-down menu for this toolbar button.
+ ///
+ [Category(Categories.Behavior)]
+ [Description("The drop down menu for this toolbar button.")]
+ [DefaultValue(null)]
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public virtual BetterToolbarDropDownMenu DropDownMenu => _dropDownMenu;
+
+ ///
+ /// Gets or sets the collection of sub-items for the drop-down button.
+ ///
+ [Category(Categories.Behavior)]
+ [Description("The collection of sub-items for the drop-down button.")]
+ [DefaultValue(null)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
+ public virtual BetterMenuItemCollection SubItems => DropDownMenu.Items;
+
+ ///
+ ///
+ ///
+ ///
+ protected override int ComputeAutoSizeWidth()
+ {
+ int width = base.ComputeAutoSizeWidth();
+
+ width += 12; // Accounts for the drop-down arrow
+
+ return width;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ private protected override BetterToolbarItem CreateClone() => new BetterToolbarDropDownButton();
+ }
+}
\ No newline at end of file
diff --git a/src/BetterControls/BetterToolbar/Items/BetterToolbarItem.cs b/src/BetterControls/BetterToolbar/Items/BetterToolbarItem.cs
index 342cd42..b2a08ae 100644
--- a/src/BetterControls/BetterToolbar/Items/BetterToolbarItem.cs
+++ b/src/BetterControls/BetterToolbar/Items/BetterToolbarItem.cs
@@ -25,6 +25,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
*/
using BetterControls.ComponentModel;
+using System;
using System.ComponentModel;
using System.Drawing;
@@ -36,7 +37,7 @@ namespace BetterControls
[ToolboxItem(false)]
[DesignTimeVisible(false)]
[Designer("BetterToolbarItemDesigner")]
- public abstract partial class BetterToolbarItem : BetterToolbarItemBase
+ public abstract partial class BetterToolbarItem : BetterToolbarItemBase, ICloneable
{
///
/// Initialize a new instance of .
@@ -46,7 +47,7 @@ private protected BetterToolbarItem() { }
///
/// Initialize a new instance of .
///
- /// The parent toolbar as an instance of .
+ /// The owner toolbar as an instance of .
private protected BetterToolbarItem(BetterToolbar ownerToolbar)
: base(ownerToolbar)
{ }
@@ -78,6 +79,21 @@ public virtual bool AutoSize
}
}
+ ///
+ /// Gets the displayed index of this item in the toolbar.
+ ///
+ [Browsable(false)]
+ public virtual int DisplayedItemIndex
+ {
+ get
+ {
+ if (IsOwnerHandleCreated)
+ return ItemIndex;
+
+ return -1;
+ }
+ }
+
///
/// Gets or sets a value indicating whether or not the toolbar item is visible.
///
@@ -192,7 +208,7 @@ protected override void PerformItemChanged(CollectionElementItemChangedFlags fla
{
NativeMethods.TBBUTTONINFO structure = ComputeTbButtonInfo();
- UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.TB_SETBUTTONINFO, ItemIndex, ref structure);
+ UnsafeNativeMethods.SendMessage(GetHandleRef(), NativeMethods.TB_SETBUTTONINFO, DisplayedItemIndex, ref structure);
// Reflect this change to the parent control.
OwnerToolbar.PerformItemsChanged(flags, new BetterToolbarItem[]
@@ -202,6 +218,41 @@ protected override void PerformItemChanged(CollectionElementItemChangedFlags fla
}
}
+ ///
+ ///
+ ///
+ ///
+ public object Clone()
+ {
+ BetterToolbarItem item = CreateClone();
+
+ ConfigureClone(item);
+
+ return item;
+ }
+
+ ///
+ /// Creates a new concrete type that should be cloned.
+ ///
+ ///
+ private protected abstract BetterToolbarItem CreateClone();
+
+ ///
+ /// Configures the created concrete type that should be cloned.
+ ///
+ /// The concrete type that should be cloned.
+ private protected virtual void ConfigureClone(BetterToolbarItem item)
+ {
+ if (item is null)
+ {
+ throw new ArgumentNullException(nameof(item));
+ }
+
+ item.AutoSize = AutoSize;
+ item.Visible = Visible;
+ item.Width = Width;
+ }
+
///
///
///
diff --git a/src/BetterControls/BetterToolbar/Items/BetterToolbarItemBase.cs b/src/BetterControls/BetterToolbar/Items/BetterToolbarItemBase.cs
index 5269c5d..4b58836 100644
--- a/src/BetterControls/BetterToolbar/Items/BetterToolbarItemBase.cs
+++ b/src/BetterControls/BetterToolbar/Items/BetterToolbarItemBase.cs
@@ -84,7 +84,7 @@ public bool IsOwnerHandleCreated {
/// Gets a for the current component.
///
/// An instance of .
- protected virtual HandleRef GetHandleRef()
+ protected internal virtual HandleRef GetHandleRef()
{
if (!IsOwnerHandleCreated)
return default;
diff --git a/src/BetterControls/BetterToolbar/Items/BetterToolbarPushButton.cs b/src/BetterControls/BetterToolbar/Items/BetterToolbarPushButton.cs
index f4aa61f..bd73c44 100644
--- a/src/BetterControls/BetterToolbar/Items/BetterToolbarPushButton.cs
+++ b/src/BetterControls/BetterToolbar/Items/BetterToolbarPushButton.cs
@@ -53,5 +53,11 @@ public BetterToolbarPushButton(string text, string description, int imageIndex)
private protected BetterToolbarPushButton(BetterToolbar ownerToolbar)
: base(ownerToolbar)
{ }
+
+ ///
+ ///
+ ///
+ ///
+ private protected override BetterToolbarItem CreateClone() => new BetterToolbarPushButton();
}
}
\ No newline at end of file
diff --git a/src/BetterControls/BetterToolbar/Items/BetterToolbarSeparator.cs b/src/BetterControls/BetterToolbar/Items/BetterToolbarSeparator.cs
index 57c3361..b72ab9e 100644
--- a/src/BetterControls/BetterToolbar/Items/BetterToolbarSeparator.cs
+++ b/src/BetterControls/BetterToolbar/Items/BetterToolbarSeparator.cs
@@ -52,5 +52,11 @@ protected override int ComputeAutoSizeWidth()
{
return 8;
}
+
+ ///
+ ///
+ ///
+ ///
+ private protected override BetterToolbarItem CreateClone() => new BetterToolbarSeparator();
}
}
\ No newline at end of file
diff --git a/src/BetterControls/BetterToolbar/Items/BetterToolbarToggleButton.cs b/src/BetterControls/BetterToolbar/Items/BetterToolbarToggleButton.cs
index 4553428..a86ae29 100644
--- a/src/BetterControls/BetterToolbar/Items/BetterToolbarToggleButton.cs
+++ b/src/BetterControls/BetterToolbar/Items/BetterToolbarToggleButton.cs
@@ -52,6 +52,12 @@ public BetterToolbarToggleButton(string text, string description, int imageIndex
/// The owner toolbar as an instance of .
private protected BetterToolbarToggleButton(BetterToolbar ownerToolbar)
: base(ownerToolbar)
- { }
+ { }
+
+ ///
+ ///
+ ///
+ ///
+ private protected override BetterToolbarItem CreateClone() => new BetterToolbarToggleButton();
}
}
\ No newline at end of file