2025-08-20 12:10:13 +08:00
|
|
|
|
using System.Diagnostics;
|
2025-08-12 23:08:54 +08:00
|
|
|
|
using System.IO.Packaging;
|
|
|
|
|
|
using System.Windows.Threading;
|
|
|
|
|
|
|
2026-01-02 17:30:30 +08:00
|
|
|
|
namespace VariaStudio.Controls;
|
2025-08-12 23:08:54 +08:00
|
|
|
|
|
2025-08-20 12:10:13 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 提供显示和关闭启动画面的方法。
|
|
|
|
|
|
/// </summary>
|
2025-08-12 23:08:54 +08:00
|
|
|
|
public static class Splash
|
|
|
|
|
|
{
|
|
|
|
|
|
private static STAThread<SplashWindow>? Current { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
static Splash()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!UriParser.IsKnownScheme("pack"))
|
|
|
|
|
|
{
|
2025-08-20 12:10:13 +08:00
|
|
|
|
// 确保 PackUriHelper 的 UriSchemePack 已经注册
|
2025-08-12 23:08:54 +08:00
|
|
|
|
_ = PackUriHelper.UriSchemePack;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-20 12:10:13 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 异步显示启动画面。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="imageUriString">启动画面图像的URI字符串。</param>
|
|
|
|
|
|
/// <param name="opacity">启动画面的不透明度,默认为1.0(完全不透明)。</param>
|
|
|
|
|
|
/// <param name="completed">可选的完成回调,当启动画面关闭后调用。</param>
|
2025-08-12 23:08:54 +08:00
|
|
|
|
public static void ShowAsync(string imageUriString, double opacity = 1d, Action? completed = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var imageUri = new Uri(imageUriString);
|
2025-08-20 12:10:13 +08:00
|
|
|
|
Current = new STAThread<SplashWindow>(sta =>
|
2025-08-12 23:08:54 +08:00
|
|
|
|
{
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-20 12:10:13 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 异步关闭启动画面。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="owner">拥有启动画面的窗口,默认为null。</param>
|
|
|
|
|
|
/// <param name="forced">是否强制关闭启动画面,默认为false。</param>
|
2025-08-12 23:08:54 +08:00
|
|
|
|
public static void CloseAsync(Window? owner = null, bool forced = false)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-08-20 12:10:13 +08:00
|
|
|
|
var current = Current?.Result;
|
2025-08-12 23:08:54 +08:00
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-20 12:10:13 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 在指定窗口加载完成后关闭启动画面。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="owner">拥有启动画面的窗口。</param>
|
|
|
|
|
|
/// <param name="minimumMilliseconds">启动画面显示的最小毫秒数,如果未达到该时间则延迟关闭,默认为null。</param>
|
|
|
|
|
|
/// <param name="forced">是否强制关闭启动画面,默认为false。</param>
|
|
|
|
|
|
/// <exception cref="ArgumentNullException">当<paramref name="owner"/>为null时抛出。</exception>
|
2025-08-12 23:08:54 +08:00
|
|
|
|
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)
|
|
|
|
|
|
{
|
2025-08-20 12:10:13 +08:00
|
|
|
|
var current = Current?.Result;
|
2025-08-12 23:08:54 +08:00
|
|
|
|
|
|
|
|
|
|
if (current != null)
|
|
|
|
|
|
{
|
2025-08-20 12:10:13 +08:00
|
|
|
|
var survivalMilliseconds = (DateTime.Now - current.TimeOfCtor).TotalMilliseconds;
|
2025-08-12 23:08:54 +08:00
|
|
|
|
if (survivalMilliseconds < minimumMilliseconds)
|
|
|
|
|
|
{
|
|
|
|
|
|
await Task.Delay((int)(minimumMilliseconds.Value - survivalMilliseconds));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CloseAsync(owner, forced);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private class STAThread<T> : STADispatcherObject, IDisposable where T : class
|
|
|
|
|
|
{
|
2025-08-20 12:10:13 +08:00
|
|
|
|
public Thread Value { get; set; }
|
2025-08-12 23:08:54 +08:00
|
|
|
|
public T Result { get; set; } = null!;
|
|
|
|
|
|
|
|
|
|
|
|
public STAThread(Action<STAThread<T>> start)
|
|
|
|
|
|
{
|
2025-08-20 12:10:13 +08:00
|
|
|
|
Value = new Thread(() =>
|
2025-08-12 23:08:54 +08:00
|
|
|
|
{
|
|
|
|
|
|
Dispatcher = Dispatcher.CurrentDispatcher;
|
|
|
|
|
|
start?.Invoke(this);
|
|
|
|
|
|
Dispatcher.Run();
|
|
|
|
|
|
})
|
|
|
|
|
|
{
|
|
|
|
|
|
IsBackground = true,
|
|
|
|
|
|
Name = $"STAThread<{typeof(T)}>",
|
|
|
|
|
|
};
|
|
|
|
|
|
Value.SetApartmentState(ApartmentState.STA);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Start()
|
|
|
|
|
|
{
|
2025-08-20 12:10:13 +08:00
|
|
|
|
Value.Start();
|
2025-08-12 23:08:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Forget()
|
|
|
|
|
|
{
|
|
|
|
|
|
Dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (typeof(IDisposable).IsAssignableFrom(typeof(T)))
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
((IDisposable?)Result)?.Dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
2025-08-20 12:10:13 +08:00
|
|
|
|
// ignored
|
2025-08-12 23:08:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
Dispatcher?.InvokeShutdown();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private class STADispatcherObject(Dispatcher dispatcher = null!)
|
|
|
|
|
|
{
|
|
|
|
|
|
public Dispatcher Dispatcher { get; set; } = dispatcher;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|