我想在所有窗口的系统菜单中添加“Always-On-Top”-menuentry(当您右键单击标题栏或单击图标时会打开的菜单)。我更喜欢C#或C ++,但如果最坏的情况发生,我也会使用VB ......
我知道像Dexpot这样的应用程序可以做到这一点,但我无法找到有用的源代码或免费应用程序,它们可以为所有窗口而不仅仅是自己的窗口执行此操作。 我也知道还有其他方法可以实现这个功能(AutoHotkeys或小程序,它们存在于系统托盘中,让你选择应该保持在顶部的窗口),但我正在寻找一种更流畅和直观的方式。理想情况下,我会在标题栏上添加一个小针脚按钮,但我的猜测是更多涉及,所以我现在会坚持使用菜单变体。
想法?谢谢!
答案 0 :(得分:1)
使用AddMenuItems()
方法并使用MS-Paint进行测试。我注意到的一件事是程序关闭后,修改后的系统菜单变得不稳定。可能这是因为事件不是来自过程' UI线程。可以在ApplicationExit
事件中调用GetMenu(hMainWindowHandle, true)
,其中true表示还原菜单。
public static class AlwaysOnTop {
static AlwaysOnTop() {
Application.ApplicationExit += delegate {
try {
foreach (DictionaryEntry de in htThreads) {
Hook h = (Hook) de.Value;
RemoveMenu(h.hMenu, h.uniqueId, 0);
//DeleteMenu(h.hMenu, h.uniqueId, 0);
UnhookWinEvent(h.hWinEventHook);
}
} catch {
}
};
}
private const int EVENT_OBJECT_INVOKED = 0x8013;
private const int OBJID_SYSMENU = -1;
private const int WINEVENT_OUTOFCONTEXT = 0;
private const int MF_STRING = 0x00000000;
private const int HWND_TOPMOST = -1;
private const int HWND_NOTOPMOST = -2;
private const int SWP_NOMOVE = 0x0002;
private const int SWP_NOSIZE = 0x0001;
private const uint MF_UNCHECKED = 0x00000000;
private const uint MF_CHECKED = 0x00000008;
[DllImport("user32.dll")]
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll")]
private static extern bool AppendMenu(IntPtr hMenu, uint uFlags, uint uIDNewItem, String lpNewItem);
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("user32.dll",SetLastError=true)]
private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventProc lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
[DllImport("user32.dll", SetLastError = true)]
internal static extern int UnhookWinEvent(IntPtr hWinEventHook);
[DllImport("user32.dll", SetLastError=true)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
private static extern bool CheckMenuItem(IntPtr hMenu, uint uIDCheckItem, uint uCheck);
[DllImport("user32.dll")]
private static extern bool RemoveMenu(IntPtr hMenu, uint uPosition, uint uFlags);
//[DllImport("user32.dll")]
//private static extern bool DeleteMenu(IntPtr hMenu, uint uPosition, uint uFlags);
private static Hashtable htThreads = new Hashtable();
private static WinEventProc CallWinEventProc = new WinEventProc(EventCallback);
private delegate void WinEventProc(IntPtr hWinEventHook, int iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime);
private static void EventCallback(IntPtr hWinEventHook, int iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime) {
//callback function, called when message is intercepted
if (iEvent == EVENT_OBJECT_INVOKED) {
if (idObject == OBJID_SYSMENU) {
Hook h = (Hook) htThreads[(uint) dwEventThread];
if (h != null && h.uniqueId == idChild) {
bool b = !h.Checked;
if (b)
SetWindowPos(h.hMainWindowHandle, (IntPtr) HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
else
SetWindowPos(h.hMainWindowHandle, (IntPtr) HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
CheckMenuItem(h.hMenu, h.uniqueId, (b ? MF_CHECKED : MF_UNCHECKED));
h.Checked = b;
}
}
}
}
private class Hook {
public uint uniqueId = 1001;
public IntPtr hWinEventHook;
public IntPtr hMenu;
public IntPtr hMainWindowHandle;
public bool Checked;
}
public static void AddMenuItems() {
Process[] arr = Process.GetProcesses();
foreach (Process p in arr) {
if (p.MainWindowHandle == IntPtr.Zero)
continue;
if (p.ProcessName != "mspaint") // <-- remove or change this line
continue;
IntPtr hMenu = GetSystemMenu(p.MainWindowHandle, false);
if (hMenu == IntPtr.Zero)
continue;
bool b = AppendMenu(hMenu, MF_STRING, 1001, "Always On Top");
uint pid = 0;
uint tid = GetWindowThreadProcessId(p.MainWindowHandle, out pid);
Hook h = (Hook) htThreads[tid];
if (h == null) {
h = new Hook();
h.hMenu = hMenu;
h.hWinEventHook = SetWinEventHook(EVENT_OBJECT_INVOKED, EVENT_OBJECT_INVOKED, IntPtr.Zero, CallWinEventProc, pid, tid, WINEVENT_OUTOFCONTEXT);
h.hMainWindowHandle = p.MainWindowHandle;
htThreads[tid] = h;
}
}
}
}
答案 1 :(得分:0)
以下是使用ToolStripDropDown
代替默认窗口菜单的示例。通过设置背景颜色,字体并在需要时添加一些图标,可以使其看起来更像默认窗口菜单。
隐藏默认窗口菜单显然更加困难。也许有更好的方法。隐藏菜单的失败尝试留在下面的代码中。右键单击标题栏时仍会显示默认菜单。
public class FormCustomMenu : Form {
WindowMenu WindowMenu = new WindowMenu();
public FormCustomMenu() {
//this.ShowIcon = false;
}
private const int WM_INITMENU = 0x116;
private const int WM_INITMENUPOPUP = 0x117;
private const int WM_SYSCOMMAND = 0x112;
protected override void WndProc(ref Message m) {
if (m.Msg == WM_SYSCOMMAND) { //WM_INITMENU || m.Msg == WM_INITMENUPOPUP) {}
Point pt = Cursor.Position;
int h = SystemInformation.CaptionHeight;
Rectangle r = new Rectangle(this.Location, new Size(h, h));
if (!r.Contains(pt) || Cursor.Current != Cursors.Default)
base.WndProc(ref m);
else {
Rectangle r2 = RectangleToScreen(this.ClientRectangle);
WindowMenu.Show(r2.Location);
}
}
else {
base.WndProc(ref m);
}
}
/*
Failed attempts at hiding the default window menu.
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
//IntPtr hMenu = GetSystemMenu(Handle, false);
//SendMessage(hMenu, WM_SETREDRAW, (IntPtr) 0, (IntPtr) 0);
//int count = GetMenuItemCount(hMenu);
//for (int i = count - 1; i >= 3; i--)
// RemoveMenu(hMenu, (uint) i, MF_BYPOSITION);
}
[DllImport("user32.dll")]
public static extern int GetMenuItemCount(IntPtr hMenu);
[DllImport("user32.dll")]
private static extern bool RemoveMenu(IntPtr hMenu, uint uPosition, uint uFlags);
//[DllImport("user32.dll")]
//public static extern IntPtr DestroyMenu(IntPtr hMenu);
[DllImport("user32.dll")]
public static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll")]
private static extern bool DeleteMenu(IntPtr hMenu, uint uPosition, uint uFlags);
private const int MF_BYPOSITION = 0x00000400;
private const int WM_SETREDRAW = 11;
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, IntPtr wParam, IntPtr lParam);
// this also removes the min/max/close buttons:
//private const int WS_SYSMENU = 0x80000;
//protected override CreateParams CreateParams {
// get {
// var p = base.CreateParams;
// p.Style = p.Style & ~WS_SYSMENU;
// return p;
// }
//}
*/
}
public class WindowMenu : ToolStripDropDown {
public WindowMenu() {
Items.Add(new ToolStripMenuItem("Custom1"));
}
}