Form.Visible имеет значение false после использования ShowWindow (hWnd, SW_RESTORE) - PullRequest
0 голосов
/ 25 марта 2020

Работа над одной программой-экземпляром, которая сворачивается в панель значков. Я заметил, что свойство Form Visible остается ложным, даже если форма видна на экране. Кажется, что внутренняя работа Form должна определять, когда он снова становится видимым из внешнего источника, но это не так. Наличие Visible false приводит к тому, что другие элементы управления не работают должным образом.

Обходной путь - прослушивать WM_SETFOCUS сообщения в методе WndProc и затем принудительно form.Visible = true;, но мне интересно, есть ли лучший подход к одному экземпляру? Использование pipe-слушателя может быть другим подходом, но это кажется излишним.

Вот некоторый код, который иллюстрирует проблему. Поток предназначен для того, чтобы сделать вид, что другая программа восстанавливает программу из свернутого состояния в области значков:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Demo {

public class FormVisibleFalse : Form {

    private const int WM_SETFOCUS = 0x7;
    private const int SW_RESTORE = 9;
    private bool setVisible = true;

    NotifyIcon notifyIcon = new NotifyIcon();
    FormWindowState windowState = FormWindowState.Normal;
    Button btnIsVisible = new Button { Text = "IsVisible", AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink };
    IntPtr mainWindowHandle = IntPtr.Zero;

    public FormVisibleFalse() {
        Text = "Form.Visible = False";
        notifyIcon.MouseClick += notifyIcon_MouseClick;
        notifyIcon.Icon = this.Icon;
        btnIsVisible.Click += btnIsVisible_Click;
        FlowLayoutPanel p = new FlowLayoutPanel { Dock = DockStyle.Fill, WrapContents = false, FlowDirection = FlowDirection.TopDown };
        p.Controls.Add(btnIsVisible);
        p.Controls.Add(new Label { Text = "After minimizing the form, click the IsVisible button.", AutoSize = true });
        Controls.Add(p);
        this.StartPosition = FormStartPosition.CenterScreen;
    }

    protected override void OnHandleCreated(EventArgs e) {
        base.OnHandleCreated(e);
        mainWindowHandle = this.Handle;
    }

    void btnIsVisible_Click(object sender, EventArgs e) {
        MessageBox.Show("Form.Visible = " + this.Visible);
    }

    void notifyIcon_MouseClick(object sender, MouseEventArgs e) {
        setVisible = false;
        this.Visible = true;
        this.WindowState = windowState;
        notifyIcon.Visible = false;
    }

    protected override void OnSizeChanged(EventArgs e) {
        base.OnSizeChanged(e);
        if (this.WindowState == FormWindowState.Minimized) {
            this.Visible = false;
            notifyIcon.Visible = true;
            setVisible = true;

            // two seconds later, restore the form.
            // Note: This is just a simple way to illustrate the problem. The actual pinvoke call
            // is made from a second instance of the process that detects if an instance is
            // already running, and restores the main window. A pipe listener could also be used to
            // pass a message telling the running instance to restore, but that's a more complicated
            // solution.
            Thread t = new Thread(() => {
                Thread.Sleep(2000);
                ShowWindow(mainWindowHandle, SW_RESTORE); // also tried SW_NORMAL and SW_SHOW
                SetForegroundWindow(mainWindowHandle);
            });
            t.IsBackground = true;
            t.Start(); // after this runs, then click the IsVisible button
        }
        else {
            windowState = this.WindowState;
        }
    }

    protected override void WndProc(ref Message m) {
        base.WndProc(ref m);

        if (m.Msg == WM_SETFOCUS && setVisible) {
            setVisible = false;
            // this.Visible = true; // <-- this fixes the problem but is there a more correct way?
            notifyIcon.Visible = false;
        }
    }

    protected override void Dispose(bool disposing) {
        base.Dispose(disposing);
        if (disposing) {
            notifyIcon.Dispose();
        }
    }

    [DllImport("user32.dll")]
    public static extern int ShowWindow(IntPtr hWnd, int cmdShow);
    [DllImport("user32.dll")]
    public static extern bool SetForegroundWindow(IntPtr hWnd);

}

}

1 Ответ

0 голосов
/ 26 марта 2020

Вот текущее решение для формы единственного экземпляра, которая минимизирует область значков. Пожалуйста, оставьте комментарий, если вы видите какие-либо потенциальные проблемы.

  • Форма восстанавливается в своем предыдущем состоянии окна. Таким образом, если это было 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();
    }
}
}

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...