196 lines
5.6 KiB
C#
196 lines
5.6 KiB
C#
using System.Diagnostics;
|
||
using System.IO.Packaging;
|
||
using System.Windows.Threading;
|
||
|
||
namespace NeoUI.Controls;
|
||
|
||
/// <summary>
|
||
/// 提供显示和关闭启动画面的方法。
|
||
/// </summary>
|
||
public static class Splash
|
||
{
|
||
private static STAThread<SplashWindow>? Current { get; set; }
|
||
|
||
static Splash()
|
||
{
|
||
if (!UriParser.IsKnownScheme("pack"))
|
||
{
|
||
// 确保 PackUriHelper 的 UriSchemePack 已经注册
|
||
_ = PackUriHelper.UriSchemePack;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步显示启动画面。
|
||
/// </summary>
|
||
/// <param name="imageUriString">启动画面图像的URI字符串。</param>
|
||
/// <param name="opacity">启动画面的不透明度,默认为1.0(完全不透明)。</param>
|
||
/// <param name="completed">可选的完成回调,当启动画面关闭后调用。</param>
|
||
public static void ShowAsync(string imageUriString, double opacity = 1d, Action? completed = null)
|
||
{
|
||
var imageUri = new Uri(imageUriString);
|
||
Current = new STAThread<SplashWindow>(sta =>
|
||
{
|
||
if (string.IsNullOrWhiteSpace(sta.Value.Name))
|
||
{
|
||
sta.Value.Name = "Splash Thread";
|
||
}
|
||
sta.Result = new SplashWindow(imageUri)
|
||
{
|
||
Opacity = opacity,
|
||
};
|
||
sta.Result.Show();
|
||
});
|
||
Current.Start();
|
||
|
||
if (completed != null)
|
||
{
|
||
Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
Current.Value.Join();
|
||
completed?.Invoke();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.WriteLine(ex);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步关闭启动画面。
|
||
/// </summary>
|
||
/// <param name="owner">拥有启动画面的窗口,默认为null。</param>
|
||
/// <param name="forced">是否强制关闭启动画面,默认为false。</param>
|
||
public static void CloseAsync(Window? owner = null, bool forced = false)
|
||
{
|
||
try
|
||
{
|
||
var current = Current?.Result;
|
||
|
||
if (current == null)
|
||
{
|
||
return;
|
||
}
|
||
current.Closing += (_, _) =>
|
||
{
|
||
owner?.Dispatcher.Invoke(() =>
|
||
{
|
||
nint hwnd = new WindowInteropHelper(owner).Handle;
|
||
|
||
//User32.SetForegroundWindow(hwnd);
|
||
//User32.BringWindowToTop(hwnd);
|
||
});
|
||
};
|
||
if (forced)
|
||
{
|
||
current.Shutdown();
|
||
}
|
||
else
|
||
{
|
||
if (!current.AutoEnd)
|
||
{
|
||
current.StartEnd();
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.WriteLine(ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在指定窗口加载完成后关闭启动画面。
|
||
/// </summary>
|
||
/// <param name="owner">拥有启动画面的窗口。</param>
|
||
/// <param name="minimumMilliseconds">启动画面显示的最小毫秒数,如果未达到该时间则延迟关闭,默认为null。</param>
|
||
/// <param name="forced">是否强制关闭启动画面,默认为false。</param>
|
||
/// <exception cref="ArgumentNullException">当<paramref name="owner"/>为null时抛出。</exception>
|
||
public static void CloseOnLoaded(Window? owner = null, int? minimumMilliseconds = null, bool forced = false)
|
||
{
|
||
if (owner == null)
|
||
{
|
||
throw new ArgumentNullException(nameof(owner));
|
||
}
|
||
|
||
owner.Loaded += OnLoaded;
|
||
|
||
async void OnLoaded(object? sender, RoutedEventArgs e)
|
||
{
|
||
owner.Loaded -= OnLoaded;
|
||
|
||
if (minimumMilliseconds != null)
|
||
{
|
||
var current = Current?.Result;
|
||
|
||
if (current != null)
|
||
{
|
||
var survivalMilliseconds = (DateTime.Now - current.TimeOfCtor).TotalMilliseconds;
|
||
if (survivalMilliseconds < minimumMilliseconds)
|
||
{
|
||
await Task.Delay((int)(minimumMilliseconds.Value - survivalMilliseconds));
|
||
}
|
||
}
|
||
}
|
||
|
||
CloseAsync(owner, forced);
|
||
}
|
||
}
|
||
|
||
private class STAThread<T> : STADispatcherObject, IDisposable where T : class
|
||
{
|
||
public Thread Value { get; set; }
|
||
public T Result { get; set; } = null!;
|
||
|
||
public STAThread(Action<STAThread<T>> start)
|
||
{
|
||
Value = new Thread(() =>
|
||
{
|
||
Dispatcher = Dispatcher.CurrentDispatcher;
|
||
start?.Invoke(this);
|
||
Dispatcher.Run();
|
||
})
|
||
{
|
||
IsBackground = true,
|
||
Name = $"STAThread<{typeof(T)}>",
|
||
};
|
||
Value.SetApartmentState(ApartmentState.STA);
|
||
}
|
||
|
||
public void Start()
|
||
{
|
||
Value.Start();
|
||
}
|
||
|
||
public void Forget()
|
||
{
|
||
Dispose();
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
if (typeof(IDisposable).IsAssignableFrom(typeof(T)))
|
||
{
|
||
try
|
||
{
|
||
((IDisposable?)Result)?.Dispose();
|
||
}
|
||
catch
|
||
{
|
||
// ignored
|
||
}
|
||
}
|
||
Dispatcher?.InvokeShutdown();
|
||
}
|
||
}
|
||
|
||
private class STADispatcherObject(Dispatcher dispatcher = null!)
|
||
{
|
||
public Dispatcher Dispatcher { get; set; } = dispatcher;
|
||
}
|
||
}
|