mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-04-07 00:15:52 +02:00
Re-add ContextMenu that was removed in .NET Core 3.1
This commit is contained in:
parent
2927097e8e
commit
ea95e5cbac
@ -142,6 +142,10 @@ By default, [CefSharp](https://github.com/cefsharp/CefSharp/) is not built with
|
||||
|
||||
Windows library that implements `TweetLib.Browser.CEF` using the [CefSharp](https://github.com/cefsharp/CefSharp/) library and Windows Forms.
|
||||
|
||||
#### TweetLib.WinForms.Legacy
|
||||
|
||||
Windows library that re-adds some legacy Windows Forms components that were removed in .NET Core 3.1. The sources were taken from the [.NET Core 3.0 sources of Windows Forms](https://github.com/dotnet/winforms/tree/v3.0.2), and edited to remove unnecessary features.
|
||||
|
||||
### Linux Projects
|
||||
|
||||
#### TweetDuck
|
||||
|
@ -10,6 +10,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck.Video", "windows\
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetImpl.CefSharp", "windows\TweetImpl.CefSharp\TweetImpl.CefSharp.csproj", "{44DF3E2E-F465-4A31-8B43-F40FFFB018BA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.WinForms.Legacy", "windows\TweetLib.WinForms.Legacy\TweetLib.WinForms.Legacy.csproj", "{B54E732A-4090-4DAA-9ABD-311368C17B68}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Communication", "lib\TweetLib.Communication\TweetLib.Communication.csproj", "{72473763-4B9D-4FB6-A923-9364B2680F06}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Core", "lib\TweetLib.Core\TweetLib.Core.csproj", "{93BA3CB4-A812-4949-B07D-8D393FB38937}"
|
||||
@ -48,6 +50,10 @@ Global
|
||||
{44DF3E2E-F465-4A31-8B43-F40FFFB018BA}.Debug|x86.Build.0 = Debug|x86
|
||||
{44DF3E2E-F465-4A31-8B43-F40FFFB018BA}.Release|x86.ActiveCfg = Release|x86
|
||||
{44DF3E2E-F465-4A31-8B43-F40FFFB018BA}.Release|x86.Build.0 = Release|x86
|
||||
{B54E732A-4090-4DAA-9ABD-311368C17B68}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{B54E732A-4090-4DAA-9ABD-311368C17B68}.Debug|x86.Build.0 = Debug|x86
|
||||
{B54E732A-4090-4DAA-9ABD-311368C17B68}.Release|x86.ActiveCfg = Release|x86
|
||||
{B54E732A-4090-4DAA-9ABD-311368C17B68}.Release|x86.Build.0 = Release|x86
|
||||
{72473763-4B9D-4FB6-A923-9364B2680F06}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{72473763-4B9D-4FB6-A923-9364B2680F06}.Debug|x86.Build.0 = Debug|x86
|
||||
{72473763-4B9D-4FB6-A923-9364B2680F06}.Release|x86.ActiveCfg = Release|x86
|
||||
|
@ -99,18 +99,18 @@ public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser
|
||||
extraContext.Reset();
|
||||
}
|
||||
|
||||
public static ContextMenuStrip CreateMenu(FormBrowser form) {
|
||||
ContextMenuStrip menu = new ContextMenuStrip();
|
||||
public static ContextMenu CreateMenu(FormBrowser form) {
|
||||
ContextMenu menu = new ContextMenu();
|
||||
|
||||
menu.Items.Add(TitleReloadBrowser, null, (_, _) => form.ReloadToTweetDeck());
|
||||
menu.Items.Add(TitleMuteNotifications, null, static (_, _) => ToggleMuteNotifications());
|
||||
menu.Items.Add("-");
|
||||
menu.Items.Add(TitleSettings, null, (_, _) => form.OpenSettings());
|
||||
menu.Items.Add(TitlePlugins, null, (_, _) => form.OpenPlugins());
|
||||
menu.Items.Add(TitleAboutProgram, null, (_, _) => form.OpenAbout());
|
||||
menu.MenuItems.Add(TitleReloadBrowser, (_, _) => form.ReloadToTweetDeck());
|
||||
menu.MenuItems.Add(TitleMuteNotifications, (_, _) => ToggleMuteNotifications());
|
||||
menu.MenuItems.Add("-");
|
||||
menu.MenuItems.Add(TitleSettings, (_, _) => form.OpenSettings());
|
||||
menu.MenuItems.Add(TitlePlugins, (_, _) => form.OpenPlugins());
|
||||
menu.MenuItems.Add(TitleAboutProgram, (_, _) => form.OpenAbout());
|
||||
|
||||
menu.Opening += (_, _) => {
|
||||
((ToolStripMenuItem) menu.Items[1]).Checked = Config.MuteNotifications;
|
||||
menu.Popup += (_, _) => {
|
||||
menu.MenuItems[1].Checked = Config.MuteNotifications;
|
||||
};
|
||||
|
||||
return menu;
|
||||
|
@ -57,7 +57,7 @@ public bool IsWaiting {
|
||||
private readonly FormNotificationTweet notification;
|
||||
private readonly PluginManager plugins;
|
||||
private readonly UpdateChecker updates;
|
||||
private readonly ContextMenuStrip contextMenu;
|
||||
private readonly ContextMenu contextMenu;
|
||||
private readonly uint windowRestoreMessage;
|
||||
|
||||
private bool isLoaded;
|
||||
|
@ -45,19 +45,23 @@ public bool HasNotifications {
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ContextMenuStrip contextMenu;
|
||||
private readonly ContextMenu contextMenu;
|
||||
private readonly ContextMenuStrip fakeContextMenu;
|
||||
private bool hasNotifications;
|
||||
|
||||
private TrayIcon() {
|
||||
InitializeComponent();
|
||||
|
||||
this.contextMenu = new ContextMenuStrip();
|
||||
this.contextMenu.Items.Add("Restore", null, menuItemRestore_Click);
|
||||
this.contextMenu.Items.Add("Mute notifications", null, menuItemMuteNotifications_Click);
|
||||
this.contextMenu.Items.Add("Close", null, menuItemClose_Click);
|
||||
this.contextMenu.Opening += contextMenu_Popup;
|
||||
this.contextMenu = new ContextMenu();
|
||||
this.contextMenu.MenuItems.Add("Restore", menuItemRestore_Click);
|
||||
this.contextMenu.MenuItems.Add("Mute notifications", menuItemMuteNotifications_Click);
|
||||
this.contextMenu.MenuItems.Add("Close", menuItemClose_Click);
|
||||
this.contextMenu.Popup += contextMenu_Popup;
|
||||
|
||||
this.notifyIcon.ContextMenuStrip = contextMenu;
|
||||
this.fakeContextMenu = new ContextMenuStrip();
|
||||
this.fakeContextMenu.Opening += fakeContextMenu_Opening;
|
||||
|
||||
this.notifyIcon.ContextMenuStrip = this.fakeContextMenu;
|
||||
this.notifyIcon.Text = Program.BrandName;
|
||||
|
||||
Config.MuteToggled += Config_MuteToggled;
|
||||
@ -72,6 +76,7 @@ protected override void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
components?.Dispose();
|
||||
contextMenu.Dispose();
|
||||
fakeContextMenu.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
@ -95,8 +100,13 @@ private void trayIcon_MouseClick(object? sender, MouseEventArgs e) {
|
||||
}
|
||||
}
|
||||
|
||||
private void fakeContextMenu_Opening(object? sender, CancelEventArgs args) {
|
||||
args.Cancel = true;
|
||||
contextMenu.Show(notifyIcon, Cursor.Position);
|
||||
}
|
||||
|
||||
private void contextMenu_Popup(object? sender, EventArgs e) {
|
||||
((ToolStripMenuItem) contextMenu.Items[1]).Checked = Config.MuteNotifications;
|
||||
contextMenu.MenuItems[1].Checked = Config.MuteNotifications;
|
||||
}
|
||||
|
||||
private void menuItemRestore_Click(object? sender, EventArgs e) {
|
||||
@ -104,7 +114,7 @@ private void menuItemRestore_Click(object? sender, EventArgs e) {
|
||||
}
|
||||
|
||||
private void menuItemMuteNotifications_Click(object? sender, EventArgs e) {
|
||||
Config.MuteNotifications = !((ToolStripMenuItem) contextMenu.Items[1]).Checked;
|
||||
Config.MuteNotifications = !contextMenu.MenuItems[1].Checked;
|
||||
Config.Save();
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,7 @@
|
||||
<ProjectReference Include="..\TweetDuck.Browser\TweetDuck.Browser.csproj" />
|
||||
<ProjectReference Include="..\TweetDuck.Video\TweetDuck.Video.csproj" />
|
||||
<ProjectReference Include="..\TweetImpl.CefSharp\TweetImpl.CefSharp.csproj" />
|
||||
<ProjectReference Include="..\TweetLib.WinForms.Legacy\TweetLib.WinForms.Legacy.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Platforms>x86</Platforms>
|
||||
<RuntimeIdentifier>win7-x86</RuntimeIdentifier>
|
||||
<LangVersion>10</LangVersion>
|
||||
<Nullable>disable</Nullable>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<RootNamespace>System</RootNamespace>
|
||||
<AssemblyName>TweetLib.WinForms.Legacy</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\Version.cs" Link="Version.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
29
windows/TweetLib.WinForms.Legacy/Windows/Forms/Command2.cs
Normal file
29
windows/TweetLib.WinForms.Legacy/Windows/Forms/Command2.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
|
||||
namespace System.Windows.Forms {
|
||||
internal sealed class Command2 {
|
||||
private static readonly Type Type = typeof(Form).Assembly.GetType("System.Windows.Forms.Command");
|
||||
private static readonly ConstructorInfo Constructor = Type.GetConstructor(new Type[] { typeof(ICommandExecutor) }) ?? throw new NullReferenceException();
|
||||
private static readonly MethodInfo DisposeMethod = Type.GetMethod("Dispose", BindingFlags.Instance | BindingFlags.Public) ?? throw new NullReferenceException();
|
||||
private static readonly PropertyInfo IDProperty = Type.GetProperty("ID") ?? throw new NullReferenceException();
|
||||
|
||||
public int ID { get; }
|
||||
|
||||
private readonly object cmd;
|
||||
|
||||
public Command2(ICommandExecutor executor) {
|
||||
this.cmd = Constructor.Invoke(new object[] { executor });
|
||||
this.ID = (int) IDProperty.GetValue(cmd)!;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
try {
|
||||
DisposeMethod.Invoke(cmd, null);
|
||||
} catch (Exception e) {
|
||||
Debug.WriteLine(e);
|
||||
Debugger.Break();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Drawing;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace System.Windows.Forms {
|
||||
public sealed class ContextMenu : Menu {
|
||||
private static readonly FieldInfo NotifyIconWindowField = typeof(NotifyIcon).GetField("window", BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new InvalidOperationException();
|
||||
|
||||
public event EventHandler Popup;
|
||||
|
||||
public void Show(Control control, Point pos) {
|
||||
if (control == null) {
|
||||
throw new ArgumentNullException(nameof(control));
|
||||
}
|
||||
|
||||
if (!control.IsHandleCreated || !control.Visible) {
|
||||
throw new ArgumentException(null, nameof(control));
|
||||
}
|
||||
|
||||
Popup?.Invoke(this, EventArgs.Empty);
|
||||
pos = control.PointToScreen(pos);
|
||||
NativeMethods.TrackPopupMenuEx(new HandleRef(this, Handle), NativeMethods.TPM_VERTICAL | NativeMethods.TPM_RIGHTBUTTON, pos.X, pos.Y, new HandleRef(control, control.Handle), IntPtr.Zero);
|
||||
}
|
||||
|
||||
public void Show(NotifyIcon icon, Point pos) {
|
||||
Popup?.Invoke(this, EventArgs.Empty);
|
||||
NativeWindow window = (NativeWindow) NotifyIconWindowField.GetValue(icon);
|
||||
NativeMethods.TrackPopupMenuEx(new HandleRef(this, Handle), NativeMethods.TPM_VERTICAL | NativeMethods.TPM_RIGHTALIGN, pos.X, pos.Y, new HandleRef(window, window.Handle), IntPtr.Zero);
|
||||
NativeMethods.PostMessage(new HandleRef(window, window.Handle), 0, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
}
|
261
windows/TweetLib.WinForms.Legacy/Windows/Forms/Menu.cs
Normal file
261
windows/TweetLib.WinForms.Legacy/Windows/Forms/Menu.cs
Normal file
@ -0,0 +1,261 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace System.Windows.Forms {
|
||||
public abstract class Menu : Component {
|
||||
internal const int CHANGE_ITEMS = 0;
|
||||
internal const int CHANGE_VISIBLE = 1;
|
||||
internal const int CHANGE_ITEMADDED = 4;
|
||||
|
||||
private MenuItemCollection itemsCollection;
|
||||
internal MenuItem[] items;
|
||||
internal IntPtr handle;
|
||||
internal bool created;
|
||||
|
||||
internal IntPtr Handle {
|
||||
get {
|
||||
if (handle == IntPtr.Zero) {
|
||||
handle = CreateMenuHandle();
|
||||
}
|
||||
|
||||
CreateMenuItems();
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool IsParent => items != null && ItemCount > 0;
|
||||
|
||||
internal int ItemCount { get; private set; }
|
||||
|
||||
public MenuItemCollection MenuItems => itemsCollection ??= new MenuItemCollection(this);
|
||||
|
||||
internal void ClearHandles() {
|
||||
if (handle != IntPtr.Zero) {
|
||||
NativeMethods.DestroyMenu(new HandleRef(this, handle));
|
||||
}
|
||||
|
||||
handle = IntPtr.Zero;
|
||||
|
||||
if (created) {
|
||||
for (int i = 0; i < ItemCount; i++) {
|
||||
items[i].ClearHandles();
|
||||
}
|
||||
|
||||
created = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void CloneMenu(Menu menuSrc) {
|
||||
if (menuSrc == null) {
|
||||
throw new ArgumentNullException(nameof(menuSrc));
|
||||
}
|
||||
|
||||
MenuItem[] newItems = null;
|
||||
if (menuSrc.items != null) {
|
||||
int count = menuSrc.MenuItems.Count;
|
||||
newItems = new MenuItem[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
newItems[i] = menuSrc.MenuItems[i].CloneMenu();
|
||||
}
|
||||
}
|
||||
|
||||
MenuItems.Clear();
|
||||
if (newItems != null) {
|
||||
MenuItems.AddRange(newItems);
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr CreateMenuHandle() {
|
||||
return NativeMethods.CreatePopupMenu();
|
||||
}
|
||||
|
||||
internal void CreateMenuItems() {
|
||||
if (!created) {
|
||||
for (int i = 0; i < ItemCount; i++) {
|
||||
items[i].CreateMenuItem();
|
||||
}
|
||||
|
||||
created = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void DestroyMenuItems() {
|
||||
if (created) {
|
||||
for (int i = 0; i < ItemCount; i++) {
|
||||
items[i].ClearHandles();
|
||||
}
|
||||
|
||||
while (NativeMethods.GetMenuItemCount(new HandleRef(this, handle)) > 0) {
|
||||
NativeMethods.RemoveMenu(new HandleRef(this, handle), 0, NativeMethods.MF_BYPOSITION);
|
||||
}
|
||||
|
||||
created = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
while (ItemCount > 0) {
|
||||
MenuItem item = items[--ItemCount];
|
||||
|
||||
if (item.Site is { Container: {} }) {
|
||||
item.Site.Container.Remove(item);
|
||||
}
|
||||
|
||||
item.Parent = null;
|
||||
item.Dispose();
|
||||
}
|
||||
|
||||
items = null;
|
||||
}
|
||||
|
||||
if (handle != IntPtr.Zero) {
|
||||
NativeMethods.DestroyMenu(new HandleRef(this, handle));
|
||||
handle = IntPtr.Zero;
|
||||
if (disposing) {
|
||||
ClearHandles();
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
internal virtual void ItemsChanged(int change) {
|
||||
switch (change) {
|
||||
case CHANGE_ITEMS:
|
||||
case CHANGE_VISIBLE:
|
||||
DestroyMenuItems();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class MenuItemCollection {
|
||||
private readonly Menu owner;
|
||||
|
||||
internal MenuItemCollection(Menu owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public MenuItem this[int index] {
|
||||
get {
|
||||
if (index < 0 || index >= owner.ItemCount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
return owner.items[index];
|
||||
}
|
||||
}
|
||||
|
||||
internal int Count => owner.ItemCount;
|
||||
|
||||
public void Add(string caption) {
|
||||
Add(new MenuItem(caption));
|
||||
}
|
||||
|
||||
public void Add(string caption, EventHandler onClick) {
|
||||
Add(new MenuItem(caption, onClick));
|
||||
}
|
||||
|
||||
internal void Add(MenuItem item) {
|
||||
Add(owner.ItemCount, item);
|
||||
}
|
||||
|
||||
private void Add(int index, MenuItem item) {
|
||||
if (item == null) {
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
// MenuItems can only belong to one menu at a time
|
||||
if (item.Parent != null) {
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
// Validate our index
|
||||
if (index < 0 || index > owner.ItemCount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
if (owner.items == null || owner.items.Length == owner.ItemCount) {
|
||||
MenuItem[] newItems = new MenuItem[owner.ItemCount < 2 ? 4 : owner.ItemCount * 2];
|
||||
if (owner.ItemCount > 0) {
|
||||
Array.Copy(owner.items!, 0, newItems, 0, owner.ItemCount);
|
||||
}
|
||||
|
||||
owner.items = newItems;
|
||||
}
|
||||
|
||||
Array.Copy(owner.items, index, owner.items, index + 1, owner.ItemCount - index);
|
||||
owner.items[index] = item;
|
||||
owner.ItemCount++;
|
||||
item.Parent = owner;
|
||||
owner.ItemsChanged(CHANGE_ITEMS);
|
||||
if (owner is MenuItem menuItem) {
|
||||
menuItem.ItemsChanged(CHANGE_ITEMADDED, item);
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddRange(MenuItem[] items) {
|
||||
if (items == null) {
|
||||
throw new ArgumentNullException(nameof(items));
|
||||
}
|
||||
|
||||
foreach (MenuItem item in items) {
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool Contains(MenuItem value) {
|
||||
for (int index = 0; index < Count; ++index) {
|
||||
if (this[index] == value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal void Clear() {
|
||||
if (owner.ItemCount > 0) {
|
||||
for (int i = 0; i < owner.ItemCount; i++) {
|
||||
owner.items[i].Parent = null;
|
||||
}
|
||||
|
||||
owner.ItemCount = 0;
|
||||
owner.items = null;
|
||||
|
||||
owner.ItemsChanged(CHANGE_ITEMS);
|
||||
|
||||
if (owner is MenuItem item) {
|
||||
item.UpdateMenuItem(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void Remove(MenuItem item) {
|
||||
if (item.Parent == owner) {
|
||||
RemoveAt(item.Index);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveAt(int index) {
|
||||
if (index < 0 || index >= owner.ItemCount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
MenuItem item = owner.items[index];
|
||||
item.Parent = null;
|
||||
owner.ItemCount--;
|
||||
Array.Copy(owner.items, index + 1, owner.items, index, owner.ItemCount - index);
|
||||
owner.items[owner.ItemCount] = null;
|
||||
owner.ItemsChanged(CHANGE_ITEMS);
|
||||
|
||||
if (owner.ItemCount == 0) {
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
454
windows/TweetLib.WinForms.Legacy/Windows/Forms/MenuItem.cs
Normal file
454
windows/TweetLib.WinForms.Legacy/Windows/Forms/MenuItem.cs
Normal file
@ -0,0 +1,454 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace System.Windows.Forms {
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
public sealed class MenuItem : Menu {
|
||||
private const int StateBarBreak = 0x00000020;
|
||||
private const int StateBreak = 0x00000040;
|
||||
private const int StateChecked = 0x00000008;
|
||||
private const int StateDefault = 0x00001000;
|
||||
private const int StateDisabled = 0x00000003;
|
||||
private const int StateRadioCheck = 0x00000200;
|
||||
private const int StateHidden = 0x00010000;
|
||||
private const int StateCloneMask = 0x0003136B;
|
||||
private const int StateOwnerDraw = 0x00000100;
|
||||
|
||||
private bool _hasHandle;
|
||||
private MenuItemData _data;
|
||||
private MenuItem _nextLinkedItem; // Next item linked to the same MenuItemData.
|
||||
|
||||
private const uint FirstUniqueID = 0xC0000000;
|
||||
private uint _uniqueID = 0;
|
||||
private IntPtr _msaaMenuInfoPtr = IntPtr.Zero;
|
||||
|
||||
private MenuItem() : this(0, null, null, null, null) {}
|
||||
|
||||
internal MenuItem(string text) : this(0, text, null, null, null) {}
|
||||
|
||||
internal MenuItem(string text, EventHandler onClick) : this(0, text, onClick, null, null) {}
|
||||
|
||||
private MenuItem(Shortcut shortcut, string text, EventHandler onClick, EventHandler onPopup, EventHandler onSelect) {
|
||||
var _ = new MenuItemData(this, shortcut, true, text, onClick, onPopup, onSelect, null, null);
|
||||
}
|
||||
|
||||
internal Menu Parent { get; set; }
|
||||
|
||||
internal int Index {
|
||||
get {
|
||||
if (Parent != null) {
|
||||
for (int i = 0; i < Parent.ItemCount; i++) {
|
||||
if (Parent.items[i] == this) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private int MenuID {
|
||||
get {
|
||||
CheckIfDisposed();
|
||||
return _data.GetMenuID();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Enabled {
|
||||
get {
|
||||
CheckIfDisposed();
|
||||
return (_data.State & StateDisabled) == 0;
|
||||
}
|
||||
set {
|
||||
CheckIfDisposed();
|
||||
_data.SetState(StateDisabled, !value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Checked {
|
||||
get {
|
||||
CheckIfDisposed();
|
||||
return (_data.State & StateChecked) != 0;
|
||||
}
|
||||
set {
|
||||
CheckIfDisposed();
|
||||
|
||||
if (value && ItemCount != 0) {
|
||||
throw new ArgumentException(null, nameof(value));
|
||||
}
|
||||
|
||||
_data.SetState(StateChecked, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool RadioCheck {
|
||||
get {
|
||||
CheckIfDisposed();
|
||||
return (_data.State & StateRadioCheck) != 0;
|
||||
}
|
||||
set {
|
||||
CheckIfDisposed();
|
||||
_data.SetState(StateRadioCheck, value);
|
||||
}
|
||||
}
|
||||
|
||||
private string Text {
|
||||
get {
|
||||
CheckIfDisposed();
|
||||
return _data.caption;
|
||||
}
|
||||
}
|
||||
|
||||
private Shortcut Shortcut {
|
||||
get {
|
||||
CheckIfDisposed();
|
||||
return _data.shortcut;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShowShortcut {
|
||||
get {
|
||||
CheckIfDisposed();
|
||||
return _data.showShortcut;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Visible {
|
||||
get {
|
||||
CheckIfDisposed();
|
||||
return _data.Visible;
|
||||
}
|
||||
set {
|
||||
CheckIfDisposed();
|
||||
_data.Visible = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal MenuItem CloneMenu() {
|
||||
var newItem = new MenuItem();
|
||||
newItem.CloneMenu(this);
|
||||
return newItem;
|
||||
}
|
||||
|
||||
private void CloneMenu(MenuItem itemSrc) {
|
||||
base.CloneMenu(itemSrc);
|
||||
int state = itemSrc._data.State;
|
||||
var _ = new MenuItemData(this, itemSrc.Shortcut, itemSrc.ShowShortcut, itemSrc.Text, itemSrc._data.onClick, itemSrc._data.onPopup, itemSrc._data.onSelect, itemSrc._data.onDrawItem, itemSrc._data.onMeasureItem);
|
||||
_data.SetState(state & StateCloneMask, true);
|
||||
}
|
||||
|
||||
internal void CreateMenuItem() {
|
||||
if ((_data.State & StateHidden) == 0) {
|
||||
NativeMethods.MENUITEMINFO_T info = CreateMenuItemInfo();
|
||||
NativeMethods.InsertMenuItem(new HandleRef(Parent, Parent.handle), -1, true, info);
|
||||
_hasHandle = info.hSubMenu != IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
private NativeMethods.MENUITEMINFO_T CreateMenuItemInfo() {
|
||||
var info = new NativeMethods.MENUITEMINFO_T {
|
||||
fMask = NativeMethods.MIIM_ID | NativeMethods.MIIM_STATE | NativeMethods.MIIM_SUBMENU | NativeMethods.MIIM_TYPE | NativeMethods.MIIM_DATA,
|
||||
fType = _data.State & (StateBarBreak | StateBreak | StateRadioCheck | StateOwnerDraw)
|
||||
};
|
||||
|
||||
if (_data.caption.Equals("-")) {
|
||||
info.fType |= NativeMethods.MFT_SEPARATOR;
|
||||
}
|
||||
|
||||
info.fState = _data.State & (StateChecked | StateDefault | StateDisabled);
|
||||
|
||||
info.wID = MenuID;
|
||||
if (IsParent) {
|
||||
info.hSubMenu = Handle;
|
||||
}
|
||||
|
||||
info.hbmpChecked = IntPtr.Zero;
|
||||
info.hbmpUnchecked = IntPtr.Zero;
|
||||
|
||||
if (IntPtr.Size == 4) {
|
||||
// Store the unique ID in the dwItemData..
|
||||
// For simple menu items, we can just put the unique ID in the dwItemData.
|
||||
// But for owner-draw items, we need to point the dwItemData at an MSAAMENUINFO
|
||||
// structure so that MSAA can get the item text.
|
||||
// To allow us to reliably distinguish between IDs and structure pointers later
|
||||
// on, we keep IDs in the 0xC0000000-0xFFFFFFFF range. This is the top 1Gb of
|
||||
// unmananged process memory, where an app's heap allocations should never come
|
||||
// from. So that we can still get the ID from the dwItemData for an owner-draw
|
||||
// item later on, a copy of the ID is tacked onto the end of the MSAAMENUINFO
|
||||
// structure.
|
||||
if (_data.OwnerDraw) {
|
||||
info.dwItemData = AllocMsaaMenuInfo();
|
||||
}
|
||||
else {
|
||||
info.dwItemData = (IntPtr) unchecked((int) _uniqueID);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// On Win64, there are no reserved address ranges we can use for menu item IDs. So instead we will
|
||||
// have to allocate an MSAMENUINFO heap structure for all menu items, not just owner-drawn ones.
|
||||
info.dwItemData = AllocMsaaMenuInfo();
|
||||
}
|
||||
|
||||
// We won't render the shortcut if: 1) it's not set, 2) we're a parent, 3) we're toplevel
|
||||
if (_data.showShortcut && _data.shortcut != 0 && !IsParent) {
|
||||
info.dwTypeData = _data.caption + "\t" + TypeDescriptor.GetConverter(typeof(Keys)).ConvertToString((Keys) (int) _data.shortcut);
|
||||
}
|
||||
else {
|
||||
// Windows issue: Items with empty captions sometimes block keyboard
|
||||
// access to other items in same menu.
|
||||
info.dwTypeData = (_data.caption.Length == 0 ? " " : _data.caption);
|
||||
}
|
||||
|
||||
info.cch = 0;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
Parent?.MenuItems.Remove(this);
|
||||
_data?.RemoveItem(this);
|
||||
_uniqueID = 0;
|
||||
}
|
||||
|
||||
FreeMsaaMenuInfo();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
|
||||
private struct MsaaMenuInfoWithId {
|
||||
public readonly NativeMethods.MSAAMENUINFO _msaaMenuInfo;
|
||||
public readonly uint _uniqueID;
|
||||
|
||||
public MsaaMenuInfoWithId(string text, uint uniqueID) {
|
||||
_msaaMenuInfo = new NativeMethods.MSAAMENUINFO(text);
|
||||
_uniqueID = uniqueID;
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr AllocMsaaMenuInfo() {
|
||||
FreeMsaaMenuInfo();
|
||||
_msaaMenuInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf<MsaaMenuInfoWithId>());
|
||||
|
||||
if (IntPtr.Size == 4) {
|
||||
// We only check this on Win32, irrelevant on Win64 (see CreateMenuItemInfo)
|
||||
// Check for incursion into menu item ID range (unlikely!)
|
||||
Debug.Assert(((uint) (ulong) _msaaMenuInfoPtr) < FirstUniqueID);
|
||||
}
|
||||
|
||||
MsaaMenuInfoWithId msaaMenuInfoStruct = new MsaaMenuInfoWithId(_data.caption, _uniqueID);
|
||||
Marshal.StructureToPtr(msaaMenuInfoStruct, _msaaMenuInfoPtr, false);
|
||||
Debug.Assert(_msaaMenuInfoPtr != IntPtr.Zero);
|
||||
return _msaaMenuInfoPtr;
|
||||
}
|
||||
|
||||
private void FreeMsaaMenuInfo() {
|
||||
if (_msaaMenuInfoPtr != IntPtr.Zero) {
|
||||
Marshal.DestroyStructure(_msaaMenuInfoPtr, typeof(MsaaMenuInfoWithId));
|
||||
Marshal.FreeHGlobal(_msaaMenuInfoPtr);
|
||||
_msaaMenuInfoPtr = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
internal override void ItemsChanged(int change) {
|
||||
base.ItemsChanged(change);
|
||||
|
||||
if (change == CHANGE_ITEMS) {
|
||||
// when the menu collection changes deal with it locally
|
||||
Debug.Assert(!created, "base.ItemsChanged should have wiped out our handles");
|
||||
if (Parent is { created: true }) {
|
||||
UpdateMenuItem(force: true);
|
||||
CreateMenuItems();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!_hasHandle && IsParent) {
|
||||
UpdateMenuItem(force: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void ItemsChanged(int change, MenuItem item) {
|
||||
if (change == CHANGE_ITEMADDED && _data is { baseItem: {} } && _data.baseItem.MenuItems.Contains(item)) {
|
||||
if (Parent is { created: true }) {
|
||||
UpdateMenuItem(force: true);
|
||||
CreateMenuItems();
|
||||
}
|
||||
else if (_data != null) {
|
||||
MenuItem currentMenuItem = _data.firstItem;
|
||||
while (currentMenuItem != null) {
|
||||
if (currentMenuItem.created) {
|
||||
MenuItem newItem = item.CloneMenu();
|
||||
item._data.AddItem(newItem);
|
||||
currentMenuItem.MenuItems.Add(newItem);
|
||||
break;
|
||||
}
|
||||
|
||||
currentMenuItem = currentMenuItem._nextLinkedItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClick(EventArgs e) {
|
||||
CheckIfDisposed();
|
||||
|
||||
if (_data.baseItem != this) {
|
||||
_data.baseItem.OnClick(e);
|
||||
}
|
||||
else {
|
||||
_data.onClick?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateMenuItem(bool force) {
|
||||
if (Parent is not { created: true }) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (force || Parent is ContextMenu) {
|
||||
NativeMethods.MENUITEMINFO_T info = CreateMenuItemInfo();
|
||||
NativeMethods.SetMenuItemInfo(new HandleRef(Parent, Parent.handle), MenuID, false, info);
|
||||
|
||||
if (_hasHandle && info.hSubMenu == IntPtr.Zero) {
|
||||
ClearHandles();
|
||||
}
|
||||
|
||||
_hasHandle = info.hSubMenu != IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckIfDisposed() {
|
||||
if (_data == null) {
|
||||
throw new ObjectDisposedException(GetType().FullName);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class MenuItemData : ICommandExecutor {
|
||||
internal MenuItem baseItem;
|
||||
internal MenuItem firstItem;
|
||||
|
||||
internal readonly string caption;
|
||||
internal readonly Shortcut shortcut;
|
||||
internal readonly bool showShortcut;
|
||||
internal EventHandler onClick;
|
||||
internal EventHandler onPopup;
|
||||
internal EventHandler onSelect;
|
||||
internal DrawItemEventHandler onDrawItem;
|
||||
internal MeasureItemEventHandler onMeasureItem;
|
||||
|
||||
private Command2 cmd;
|
||||
|
||||
internal MenuItemData(MenuItem baseItem, Shortcut shortcut, bool showShortcut, string caption, EventHandler onClick, EventHandler onPopup, EventHandler onSelect, DrawItemEventHandler onDrawItem, MeasureItemEventHandler onMeasureItem) {
|
||||
AddItem(baseItem);
|
||||
this.shortcut = shortcut;
|
||||
this.showShortcut = showShortcut;
|
||||
this.caption = caption ?? string.Empty;
|
||||
this.onClick = onClick;
|
||||
this.onPopup = onPopup;
|
||||
this.onSelect = onSelect;
|
||||
this.onDrawItem = onDrawItem;
|
||||
this.onMeasureItem = onMeasureItem;
|
||||
}
|
||||
|
||||
internal int State { get; private set; }
|
||||
|
||||
internal bool OwnerDraw => (State & StateOwnerDraw) != 0;
|
||||
|
||||
internal bool Visible {
|
||||
get => (State & StateHidden) == 0;
|
||||
set {
|
||||
if (((State & StateHidden) == 0) != value) {
|
||||
State = value ? State & ~StateHidden : State | StateHidden;
|
||||
ItemsChanged(CHANGE_VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddItem(MenuItem item) {
|
||||
if (item._data != this) {
|
||||
item._data?.RemoveItem(item);
|
||||
|
||||
item._nextLinkedItem = firstItem;
|
||||
firstItem = item;
|
||||
baseItem ??= item;
|
||||
|
||||
item._data = this;
|
||||
item.UpdateMenuItem(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Execute() {
|
||||
baseItem?.OnClick(EventArgs.Empty);
|
||||
}
|
||||
|
||||
internal int GetMenuID() {
|
||||
cmd ??= new Command2(this);
|
||||
return cmd.ID;
|
||||
}
|
||||
|
||||
private void ItemsChanged(int change) {
|
||||
for (MenuItem item = firstItem; item != null; item = item._nextLinkedItem) {
|
||||
item.Parent?.ItemsChanged(change);
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveItem(MenuItem item) {
|
||||
Debug.Assert(item._data == this, "bad item passed to MenuItemData.removeItem");
|
||||
|
||||
if (item == firstItem) {
|
||||
firstItem = item._nextLinkedItem;
|
||||
}
|
||||
else {
|
||||
MenuItem itemT;
|
||||
for (itemT = firstItem; item != itemT._nextLinkedItem;) {
|
||||
itemT = itemT._nextLinkedItem;
|
||||
}
|
||||
|
||||
itemT._nextLinkedItem = item._nextLinkedItem;
|
||||
}
|
||||
|
||||
item._nextLinkedItem = null;
|
||||
item._data = null;
|
||||
|
||||
if (item == baseItem) {
|
||||
baseItem = firstItem;
|
||||
}
|
||||
|
||||
if (firstItem == null) {
|
||||
// No longer needed. Toss all references and the Command object.
|
||||
Debug.Assert(baseItem == null, "why isn't baseItem null?");
|
||||
onClick = null;
|
||||
onPopup = null;
|
||||
onSelect = null;
|
||||
onDrawItem = null;
|
||||
onMeasureItem = null;
|
||||
if (cmd != null) {
|
||||
cmd.Dispose();
|
||||
cmd = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetState(int flag, bool value) {
|
||||
if (((State & flag) != 0) != value) {
|
||||
State = value ? State | flag : State & ~flag;
|
||||
UpdateMenuItems();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMenuItems() {
|
||||
for (MenuItem item = firstItem; item != null; item = item._nextLinkedItem) {
|
||||
item.UpdateMenuItem(force: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace System.Windows.Forms {
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
||||
[SuppressMessage("ReSharper", "NotAccessedField.Global")]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
internal static class NativeMethods {
|
||||
public const int MIIM_STATE = 0x00000001;
|
||||
public const int MIIM_ID = 0x00000002;
|
||||
public const int MIIM_SUBMENU = 0x00000004;
|
||||
public const int MIIM_TYPE = 0x00000010;
|
||||
public const int MIIM_DATA = 0x00000020;
|
||||
public const int MF_BYPOSITION = 0x00000400;
|
||||
public const int MFT_SEPARATOR = 0x00000800;
|
||||
public const int TPM_RIGHTBUTTON = 0x0002;
|
||||
public const int TPM_RIGHTALIGN = 0x0008;
|
||||
public const int TPM_VERTICAL = 0x0040;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public sealed class MENUITEMINFO_T {
|
||||
public int cbSize = Marshal.SizeOf<MENUITEMINFO_T>();
|
||||
public int fMask;
|
||||
public int fType;
|
||||
public int fState;
|
||||
public int wID;
|
||||
public IntPtr hSubMenu;
|
||||
public IntPtr hbmpChecked;
|
||||
public IntPtr hbmpUnchecked;
|
||||
public IntPtr dwItemData;
|
||||
public string dwTypeData;
|
||||
public int cch;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct MSAAMENUINFO {
|
||||
private const int MSAA_MENU_SIG = (unchecked((int) 0xAA0DF00D));
|
||||
|
||||
public readonly int dwMSAASignature;
|
||||
public readonly int cchWText;
|
||||
public readonly string pszWText;
|
||||
|
||||
public MSAAMENUINFO(string text) {
|
||||
dwMSAASignature = MSAA_MENU_SIG;
|
||||
cchWText = text.Length;
|
||||
pszWText = text;
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
|
||||
public static extern bool TrackPopupMenuEx(HandleRef hmenu, int fuFlags, int x, int y, HandleRef hwnd, IntPtr tpm);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern bool InsertMenuItem(HandleRef hMenu, int uItem, bool fByPosition, MENUITEMINFO_T lpmii);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern bool SetMenuItemInfo(HandleRef hMenu, int uItem, bool fByPosition, MENUITEMINFO_T lpmii);
|
||||
|
||||
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
|
||||
public static extern int GetMenuItemCount(HandleRef hMenu);
|
||||
|
||||
[DllImport("user32.dll", ExactSpelling = true)]
|
||||
public static extern IntPtr CreatePopupMenu();
|
||||
|
||||
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
|
||||
public static extern bool RemoveMenu(HandleRef hMenu, int uPosition, int uFlags);
|
||||
|
||||
[DllImport("user32.dll", ExactSpelling = true)]
|
||||
public static extern bool DestroyMenu(HandleRef hMenu);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern bool PostMessage(HandleRef hwnd, int msg, IntPtr wparam, IntPtr lparam);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user