就算是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,以及讓等候所有非同步即可。