Вот текущее решение для формы единственного экземпляра, которая минимизирует область значков. Пожалуйста, оставьте комментарий, если вы видите какие-либо потенциальные проблемы.
- Форма восстанавливается в своем предыдущем состоянии окна. Таким образом, если это было
Normal
до минимизации, то это будет Normal
после, и если это было Maximized
до, то это будет Maximized
после. - Все дочерние элементы windows и дочерние элементы ребенок windows также минимизирован. Поведение по умолчанию в Windows состоит в том, чтобы минимизировать непосредственный дочерний элемент windows только тогда, когда основной
Form
по какой-то причине свернут. - Если программа уже запущена, а
Form
равен Normal
или Maximized
тогда окно выводится на передний план. SW_RESTORE
не отправляется. - Если программа уже запущена, но
Form
равен Minimized
, то SW_RESTORE
отправляется.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Demo {
public class IconTrayForm : Form {
private const String TITLE = "IconTrayForm";
private const int SW_RESTORE = 9;
private const int SW_HIDE = 0;
private const int SW_SHOW = 5;
Button btnChildWindow = new Button { Text = "Child Window", AutoSizeMode = AutoSizeMode.GrowAndShrink, AutoSize = true };
CheckBox cbMinimizeToIconTray = new CheckBox { Text = "Minimize to Icon Tray", Checked = true, AutoSize = true };
char ChildTitle = 'A';
//---
List<IntPtr> hiddenWindows = new List<IntPtr>();
NotifyIcon notifyIcon = new NotifyIcon();
bool isRestoring = false; // prevents double-calls
public IconTrayForm() {
this.Text = TITLE;
FlowLayoutPanel p = new FlowLayoutPanel { Dock = DockStyle.Fill, FlowDirection = FlowDirection.TopDown };
p.Controls.Add(cbMinimizeToIconTray);
p.Controls.Add(btnChildWindow);
Controls.Add(p);
Size = new Size(600,600);
StartPosition = FormStartPosition.CenterScreen;
btnChildWindow.Click += btnChildWindow_Click;
notifyIcon.Icon = this.Icon;
notifyIcon.MouseClick += notifyIcon_MouseClick;
}
private void notifyIcon_MouseClick(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Left) {
if (WindowState == FormWindowState.Minimized) {
RestoreWindows();
}
}
}
protected override void OnSizeChanged(EventArgs e) {
if (isRestoring)
return;
base.OnSizeChanged(e);
FormWindowState ws = this.WindowState;
if (ws == FormWindowState.Minimized) {
if (cbMinimizeToIconTray.Checked) {
notifyIcon.Visible = true;
HandleUtils.ShowWindow(this.Handle, SW_HIDE);
// windows that are not owned by this parent form will still appear visible and must be hidden:
hiddenWindows = HandleUtils.GetProcessWindowHandles().Where(h => HandleUtils.IsWindowVisible(h)).ToList();
foreach (IntPtr h in hiddenWindows) {
HandleUtils.ShowWindow(h, SW_HIDE);
}
}
}
else {
Form f = this;
if (f.IsHandleCreated && !f.Visible) {
RestoreWindows();
}
}
}
private void RestoreWindows() {
isRestoring = true;
try {
Form f = this;
// SW_RESTORE seems like the best option. It works to restore minimized back to normal/maximized
if (f.WindowState == FormWindowState.Minimized) {
// the exe calls ShowWindow(hwnd, SW_RESTORE) which changes the window state from Minimized to Maximized
// but calling ShowWindow(hwnd, SW_RESTORE) again then changes the window state from Maximized to Normal
HandleUtils.ShowWindow(f.Handle, SW_RESTORE);
}
foreach (IntPtr h in hiddenWindows)
HandleUtils.ShowWindow(h, SW_SHOW);
this.hiddenWindows = new List<IntPtr>();
HandleUtils.SetForegroundWindow(f.Handle); // must do this otherwise the window stays behind
// Make sure all visible windows are not offscreen. Any offscreen window moved will become the active window.
//List<IntPtr> visibleWindows = HandleUtils.GetProcessWindowHandles().Where(h => HandleUtils.IsWindowVisible(h)).ToList();
//foreach (IntPtr h in visibleWindows) {
// FormEx.EnsureVisible(h);
//}
this.Visible = true; // for some reason this.Visible stays false even though ShowWindow is called
// this causes problems when a control is added to a TabPage.Controls.Add(...) that the content is never displayed
if (notifyIcon != null)
notifyIcon.Visible = false;
} finally {
isRestoring = false;
}
}
void btnChildWindow_Click(object sender, EventArgs e) {
ChildForm f = new ChildForm(ChildTitle.ToString());
f.StartPosition = FormStartPosition.Manual;
ChildTitle++;
f.Owner = this;
Point p = this.Location;
p.Offset(0, 100);
f.Location = p;
f.Show();
}
private class ChildForm : Form {
Button btnChildWindow = new Button { Text = "Child Window", AutoSizeMode = AutoSizeMode.GrowAndShrink, AutoSize = true };
char ChildTitle = 'A';
public ChildForm(String title) {
this.Text = title;
Controls.Add(btnChildWindow);
btnChildWindow.Click += btnChildWindow_Click;
}
void btnChildWindow_Click(object sender, EventArgs e) {
ChildForm f = new ChildForm(this.Text + "." + ChildTitle);
f.StartPosition = FormStartPosition.Manual;
ChildTitle++;
f.Owner = this;
Point p = this.Location;
p.Offset(0, 70);
f.Location = p;
f.Show();
}
}
protected override void Dispose(bool disposing) {
base.Dispose(disposing);
if (disposing) {
notifyIcon.Dispose();
}
}
[STAThread]
static void Main() {
// first detect if an instance is already running
bool isMainWindowRunning = false;
Process currentProcess = Process.GetCurrentProcess();
List<Process> processes = new List<Process>();
try {
int processId = currentProcess.Id;
String processName = System.IO.Path.GetFileNameWithoutExtension(Application.ExecutablePath);
//String processName = currentProcess.ProcessName;
processes.AddRange(Process.GetProcessesByName(processName));
processes.AddRange(Process.GetProcessesByName(processName + ".vshost")); // for debugging
//var ata = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false)[0] as AssemblyTitleAttribute;
String title2 = TITLE; //Application.ProductName;
IntPtr mainWindowHandle = IntPtr.Zero;
foreach (Process p in processes) {
if (p.Id == processId)
continue;
IntPtr ptr = p.MainWindowHandle;
if (ptr != IntPtr.Zero) {
String title = p.MainWindowTitle ?? "";
// Possibly there are multiple exe with the same name, so safe guard by comparing the title
if (!title.StartsWith(title2, StringComparison.InvariantCultureIgnoreCase))
continue;
mainWindowHandle = ptr;
isMainWindowRunning = true;
goto FOUND_MAIN_WINDOW_HANDLE;
}
else {
// p.MainWindowHandle == IntPtr.Zero when the form is minimized to the icon tray.
IList<IntPtr> list = HandleUtils.GetProcessWindowHandles(p.Id);
foreach (IntPtr h in list) {
String title = HandleUtils.GetWindowText(h) ?? "";
// potentially the class name could be used to identify a Form if the title was blank
//Application.WindowsFormsVersion == "WindowsForms10"
//String className = User32.GetClassName(h) ?? "";
if (!title.StartsWith(title2, StringComparison.InvariantCultureIgnoreCase))
continue;
mainWindowHandle = h;
isMainWindowRunning = true;
goto FOUND_MAIN_WINDOW_HANDLE;
}
}
}
FOUND_MAIN_WINDOW_HANDLE:
if (isMainWindowRunning) {
WINDOWPLACEMENT wp = HandleUtils.GetPlacement(mainWindowHandle);
if (wp.showCmd == ShowWindowCommands.Normal || wp.showCmd == ShowWindowCommands.Maximized)
HandleUtils.SetForegroundWindow(mainWindowHandle);
else
HandleUtils.ShowWindow(mainWindowHandle, SW_RESTORE);
return;
}
} finally {
currentProcess.Dispose();
foreach (Process p in processes)
p.Dispose();
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Demo.IconTrayForm());
}
}
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPLACEMENT {
public int length;
public int flags;
public ShowWindowCommands showCmd;
public System.Drawing.Point ptMinPosition;
public System.Drawing.Point ptMaxPosition;
public System.Drawing.Rectangle rcNormalPosition;
}
public enum ShowWindowCommands : int {
Hide = 0,
Normal = 1,
Minimized = 2,
Maximized = 3,
}
public static class HandleUtils {
public static IList<IntPtr> GetProcessWindowHandles() {
using (System.Diagnostics.Process p = System.Diagnostics.Process.GetCurrentProcess()) {
return GetProcessWindowHandles(p);
}
}
public static IList<IntPtr> GetProcessWindowHandles(int processId) {
using (System.Diagnostics.Process p = System.Diagnostics.Process.GetProcessById(processId)) {
return GetProcessWindowHandles(p);
}
}
public static IList<IntPtr> GetProcessWindowHandles(System.Diagnostics.Process p) {
var handles = new List<IntPtr>();
foreach (System.Diagnostics.ProcessThread thread in p.Threads) {
EnumThreadWindows(thread.Id,
(hWnd, lParam) => {
handles.Add(hWnd);
return true;
},
IntPtr.Zero);
}
return handles;
}
private delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(int dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam);
public static WINDOWPLACEMENT GetPlacement(IntPtr hwnd) {
WINDOWPLACEMENT placement = new WINDOWPLACEMENT();
placement.length = Marshal.SizeOf(placement);
GetWindowPlacement(hwnd, ref placement);
return placement;
}
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern int ShowWindow(IntPtr hWnd, int cmdShow);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
public static String GetWindowText(IntPtr hWnd) {
StringBuilder sb = new StringBuilder(256);
GetWindowText(hWnd, sb, sb.Capacity);
return sb.ToString();
}
}
}