【Winform】-實踐"讀取中(Now Loading)"機制

就算是Winform,果然使用者還是會要求"Now Loading"這種東西的。

前言

關於這個視窗,有幾個要點

1.創建時必須是新的執行續,這樣才會避免全部非同步跑完才會畫面Render。

2.由於"讀取中"視窗是在新執行續,因此直接叫用是不OK的

3.由於第二點,對於"讀取中"視窗的操作就必須使用到存在於Windows系統中的元件,萬惡的 User32.dll (基本不須特別安裝,windows本身就會有的東西)

 

實作

首先先弄一個"讀取中"的Form,不需要寫邏輯。

接著實作呼叫方式

    public static partial class WinformLoading
    {
        public static IntPtr WinPointerLoadingForm { get; private set; }
            = IntPtr.Zero;
        public static void CreateLoadingForm()
        {
            //Make sure it is only launched once.
            lock (_Locker)
            {
                if (WinPointerLoadingForm == IntPtr.Zero)
                {
                    var frmActive = Form.ActiveForm;
                    if (false == frmActive is frmLoading)
                    {
                        var loadingForm = new frmLoading();
                        if (frmActive != null)
                        {
                            loadingForm.Location = new Point()
                            {
                                X = frmActive.Location.X + (frmActive.Size.Width - loadingForm.Size.Width) / 2,
                                Y = frmActive.Location.Y + (frmActive.Size.Height - loadingForm.Size.Height) / 2,
                            };
                            loadingForm.StartPosition = FormStartPosition.Manual;
                        }
                        else
                        {
                            loadingForm.StartPosition = FormStartPosition.CenterScreen;
                        }
                        loadingForm.HandleCreated += LoadingFormHandleCreated;
                        loadingForm.HandleDestroyed += LoadingFormHandleDestroyed;
                        RunInNewThread(loadingForm, false);
                    }
                }
                else if (false == IsWindow(WinPointerLoadingForm))
                {
                    WinPointerLoadingForm = IntPtr.Zero;
                    CreateLoadingForm();
                }
            }

            //----
            void LoadingFormHandleCreated(object sender, EventArgs e)
            {
                Control second = sender as Control;
                lock (_Locker)
                {
                    WinPointerLoadingForm = second.Handle;
                }
                PostMessage(WinPointerLoadingForm, WM_ACTIVATEAPP, IntPtr.Zero, IntPtr.Zero);
            }

            void LoadingFormHandleDestroyed(object sender, EventArgs e)
            {
                lock (_Locker)
                {
                    WinPointerLoadingForm = IntPtr.Zero;
                }
            }
        }

        public static void CloseLoadingForm()
        {
            lock (_Locker)
            {
                if (WinPointerLoadingForm != IntPtr.Zero)
                {
                    try
                    {
                        PostMessage(WinPointerLoadingForm, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
                    }
                    finally
                    {
                        WinPointerLoadingForm = IntPtr.Zero;
                    }
                }
            }

        }

    }
    partial class WinformLoading
    {
        static object _Locker { get; } = new object();
        [DllImport("User32.dll")]
        extern static IntPtr PostMessage(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        extern static bool IsWindow(IntPtr hWnd);


        const int WM_CLOSE = 0x0010;
        const int WM_ACTIVATEAPP = 0x001C;

        static void RunInNewThread(Form form, bool isBackground)
        {
            if (form == null)
                throw new ArgumentNullException("form");
            if (form.IsHandleCreated)
                throw new InvalidOperationException("Form is already running.");
            Thread thread = new Thread((object state) =>
            {
                Application.Run(state as Form);
            });
            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = isBackground;
            thread.Start(form);
        }
    }

實際測試(使用先前於 C# - System.Net.Http.HttpClient擴充 最後整理出來的元件)

        private void btnCallApi_Click(object sender, EventArgs e)
        {
            var loadingProcess = new LoadingProcess(WinformLoading.CreateLoadingForm, WinformLoading.CloseLoadingForm);
            tbApiResult.Text = $"loading Process....{DateTime.Now.ToString("HH: mm: ss")}";
            var apiResult = Task.Run(async () =>
            {
                await Task.Delay(5000);
            });
            loadingProcess.AddTask(apiResult);
            apiResult.Wait();
            tbApiResult.Text = $"loading End....{DateTime.Now.ToString("HH: mm: ss")}";
        }

 

延伸閱讀

1. Window Notifications( https://docs.microsoft.com/en-us/windows/desktop/winmsg/window-notifications )

 

備註

畫面鎖定的部分就交給Winform原生的機制即可,換言之就是避免使用 async void,以及讓等候所有非同步即可。