Files
Shanxue-Education-Points-Co…/MainForm.cs
T
2026-01-26 16:49:48 +01:00

995 lines
35 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* MyCSharpProject - Shanxue-Education-Points-Coin-Converter
* Copyright (C) 2026 ZerkyLiu
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
using System;
using System.Drawing;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace
{
public class MainForm : Form
{
private static readonly decimal[] RatePoints = { 500m, 1000m, 1500m, 2000m, 2500m, 3000m, 3500m, 4000m };
private static readonly decimal[] RateRmb = { 10m, 20m, 35m, 45m, 60m, 75m, 85m, 100m };
private static readonly string[] Zodiac = { "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪" };
private static readonly string[] NtpServers = { "ntp.aliyun.com", "ntp.ntsc.ac.cn", "pool.ntp.org" };
private static readonly string RateTextHorizontal = BuildRateText(true);
private static readonly string RateTextVertical = BuildRateText(false);
private readonly Label _currencyLabel;
private readonly Label _timeLabel;
private readonly Label _timeSourceLabel;
private readonly CheckBox _topMostToggle;
private readonly Button _aboutButton;
private readonly Label _inputLabel;
private readonly Label _rmbLabel;
private readonly TextBox _pointsInput;
private readonly Label _rmbOutput;
private readonly Label _rateLabel;
private readonly Timer _clockTimer;
private readonly Timer _syncTimer;
private readonly Timer _fallbackTopMostTimer;
private DateTime _currentTime;
private string _timeSource;
private IntPtr _uiAccessDll;
private string? _uiAccessDllPath;
private IsUIAccessDelegate? _isUiAccess;
private bool _uiAccessEnabled;
private bool _usingFallbackTopMost;
private bool _usingUiAccessTopMost;
private bool _suppressPointsTextChange;
private readonly bool _forceFallbackTopMost;
private const string UiAccessRelaunchArg = "--uiaccess-relaunch";
private const string UiAccessResourceSuffix = ".dll.uiaccess.dll";
private const string IconResourceSuffix = ".ico.logo_256x256.ico";
private float _rmbOutputBaseFontSize;
public MainForm()
{
Text = "善学教育积分卡汇率计算器";
StartPosition = FormStartPosition.CenterScreen;
ClientSize = new Size(640, 730);
MinimumSize = SizeFromClientSize(ClientSize);
Font = new Font("Microsoft YaHei UI", 10F);
BackColor = Color.WhiteSmoke;
AutoScaleMode = AutoScaleMode.Dpi;
DoubleBuffered = true;
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
var layout = new TableLayoutPanel
{
Dock = DockStyle.Fill,
ColumnCount = 1,
RowCount = 4,
Padding = new Padding(28),
};
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
layout.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
_currencyLabel = new Label
{
AutoSize = true,
Font = new Font("Microsoft YaHei UI", 18F, FontStyle.Bold),
ForeColor = Color.FromArgb(38, 50, 56),
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft
};
layout.Controls.Add(_currencyLabel, 0, 0);
_timeLabel = new Label
{
AutoSize = true,
ForeColor = Color.FromArgb(55, 71, 79),
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft
};
_topMostToggle = new CheckBox
{
Text = "超级置顶",
AutoSize = true,
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleRight
};
_topMostToggle.CheckedChanged += (_, __) => ApplyTopMost(_topMostToggle.Checked);
_aboutButton = new Button
{
Text = "关于",
AutoSize = true,
Dock = DockStyle.Fill
};
_aboutButton.Click += (_, __) => ShowAboutDialog();
_timeSourceLabel = new Label
{
AutoSize = true,
ForeColor = Color.FromArgb(96, 125, 139),
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft
};
var rightPanel = new TableLayoutPanel
{
Dock = DockStyle.Fill,
ColumnCount = 1,
RowCount = 2,
AutoSize = true
};
rightPanel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
rightPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
rightPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
rightPanel.Controls.Add(_aboutButton, 0, 0);
rightPanel.Controls.Add(_topMostToggle, 0, 1);
var timeRow = new TableLayoutPanel
{
Dock = DockStyle.Fill,
ColumnCount = 2,
RowCount = 1,
AutoSize = true
};
timeRow.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
timeRow.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
timeRow.RowStyles.Add(new RowStyle(SizeType.AutoSize));
timeRow.Controls.Add(_timeLabel, 0, 0);
timeRow.Controls.Add(rightPanel, 1, 0);
var timePanel = new TableLayoutPanel
{
Dock = DockStyle.Fill,
ColumnCount = 1,
RowCount = 2,
AutoSize = true
};
timePanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
timePanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
timePanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
timePanel.Controls.Add(timeRow, 0, 0);
timePanel.Controls.Add(_timeSourceLabel, 0, 1);
layout.Controls.Add(timePanel, 0, 1);
_inputLabel = new Label
{
Text = "输入积分币数量",
AutoSize = true,
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft
};
_pointsInput = new TextBox
{
Dock = DockStyle.Fill,
TextAlign = HorizontalAlignment.Left,
ImeMode = ImeMode.Disable
};
_pointsInput.TextChanged += PointsInputOnTextChanged;
_pointsInput.KeyPress += PointsInputOnKeyPress;
_rmbLabel = new Label
{
Text = "换算人民币(元)",
AutoSize = true,
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft
};
_rmbOutput = new Label
{
Text = "0.00",
AutoSize = true,
Font = new Font("Microsoft YaHei UI", 22F, FontStyle.Bold),
ForeColor = Color.FromArgb(0, 150, 136),
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleCenter
};
var convLayout = new TableLayoutPanel
{
Dock = DockStyle.Fill,
ColumnCount = 2,
RowCount = 2,
AutoSize = false
};
convLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 40F));
convLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 60F));
convLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
convLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
convLayout.Controls.Add(_inputLabel, 0, 0);
convLayout.Controls.Add(_pointsInput, 1, 0);
convLayout.Controls.Add(_rmbLabel, 0, 1);
convLayout.Controls.Add(_rmbOutput, 1, 1);
layout.Controls.Add(convLayout, 0, 2);
_rateLabel = new Label
{
Text = GetRateText(true),
AutoSize = true,
ForeColor = Color.FromArgb(96, 125, 139),
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft
};
layout.Controls.Add(_rateLabel, 0, 3);
Controls.Add(layout);
_timeSource = "本地";
_currentTime = DateTime.Now;
_clockTimer = new Timer { Interval = 1000 };
_clockTimer.Tick += ClockTimerOnTick;
_syncTimer = new Timer { Interval = 10 * 60 * 1000 };
_syncTimer.Tick += async (_, __) => await SyncNetworkTimeAsync();
_fallbackTopMostTimer = new Timer { Interval = 1 };
_fallbackTopMostTimer.Tick += (_, __) => ApplyFallbackTopMostTick();
_forceFallbackTopMost = IsWin7OrLower();
ClientSizeChanged += (_, __) => { UpdateRateLayout(); UpdateResponsiveFonts(); UpdateConversion(); };
Shown += (_, __) => _ = InitializeAsync();
}
private Task InitializeAsync()
{
TryInitializeIcon();
TryInitializeUiAccess();
if (TryRelaunchWithUiAccess())
{
return Task.CompletedTask;
}
UpdateTimeLabels();
UpdateRateLayout();
UpdateResponsiveFonts();
UpdateConversion();
StartInputFocus();
_ = SyncNetworkTimeAsync();
_clockTimer.Start();
_syncTimer.Start();
return Task.CompletedTask;
}
private void UpdateRateLayout()
{
var horizontal = ClientSize.Width >= 720;
_rateLabel.MaximumSize = new Size(Math.Max(0, ClientSize.Width - 120), 0);
_rateLabel.Text = horizontal ? RateTextHorizontal : RateTextVertical;
}
private void UpdateResponsiveFonts()
{
var w = ClientSize.Width;
var h = ClientSize.Height;
var resultSize = (float)Math.Max(24, Math.Min(w * 0.06, h * 0.08));
var inputSize = (float)Math.Max(12, Math.Min(w * 0.03, h * 0.04));
_rmbOutputBaseFontSize = resultSize;
_rmbOutput.Font = new Font("Microsoft YaHei UI", resultSize, FontStyle.Bold);
_pointsInput.Font = new Font("Microsoft YaHei UI", inputSize, FontStyle.Regular);
}
private static string GetRateText(bool horizontal)
{
return horizontal ? RateTextHorizontal : RateTextVertical;
}
private static string BuildRateText(bool horizontal)
{
var header = "汇率:";
if (horizontal)
{
var text = header;
for (var i = 0; i < RatePoints.Length; i++)
{
text += $"{RatePoints[i]}币={RateRmb[i]}";
if (i < RatePoints.Length - 1)
{
text += "";
}
}
return text;
}
var newline = Environment.NewLine;
var multi = header + newline;
for (var i = 0; i < RatePoints.Length; i++)
{
multi += $"{RatePoints[i]}币={RateRmb[i]}";
if (i < RatePoints.Length - 1)
{
multi += newline;
}
}
return multi;
}
private void UpdateTimeLabels()
{
var timeText = _currentTime.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
_timeLabel.Text = $"当前时间:{timeText}";
_timeSourceLabel.Text = $"时间来源:{_timeSource}";
_currencyLabel.Text = $"当前积分币:{GetZodiacCurrency(_currentTime)}";
}
private void ClockTimerOnTick(object? sender, EventArgs e)
{
_currentTime = _currentTime.AddSeconds(1);
UpdateTimeLabels();
}
private async Task SyncNetworkTimeAsync()
{
var networkTime = await Task.Run(TryGetNetworkTime);
if (networkTime.HasValue)
{
_currentTime = networkTime.Value.ToLocalTime();
_timeSource = "NTP时间服务器";
}
else
{
_currentTime = DateTime.Now;
_timeSource = "本地";
}
UpdateTimeLabels();
}
private static DateTime? TryGetNetworkTime()
{
foreach (var server in NtpServers)
{
try
{
var ntpData = new byte[48];
ntpData[0] = 0x1B;
var addresses = Dns.GetHostEntry(server).AddressList;
foreach (var address in addresses)
{
if (address.AddressFamily != AddressFamily.InterNetwork)
{
continue;
}
var ipEndPoint = new IPEndPoint(address, 123);
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
{
socket.ReceiveTimeout = 1500;
socket.SendTimeout = 1500;
socket.Connect(ipEndPoint);
socket.Send(ntpData);
socket.Receive(ntpData);
}
const byte serverReplyTime = 40;
var intPart = BitConverter.ToUInt32(ntpData, serverReplyTime);
var fractPart = BitConverter.ToUInt32(ntpData, serverReplyTime + 4);
intPart = SwapEndianness(intPart);
fractPart = SwapEndianness(fractPart);
var milliseconds = (intPart * 1000L) + ((fractPart * 1000L) / 0x100000000L);
var networkDateTime = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(milliseconds);
return networkDateTime;
}
}
catch
{
continue;
}
}
return null;
}
private static uint SwapEndianness(uint x)
{
return (x & 0x000000FFU) << 24 | (x & 0x0000FF00U) << 8 | (x & 0x00FF0000U) >> 8 | (x & 0xFF000000U) >> 24;
}
private static string GetZodiacCurrency(DateTime time)
{
var index = Mod(time.Year - 2020, 12);
return $"{Zodiac[index]}币";
}
private static int Mod(int x, int m)
{
var r = x % m;
return r < 0 ? r + m : r;
}
private void StartInputFocus()
{
if (!IsHandleCreated)
{
return;
}
BeginInvoke(new Action(() =>
{
ActiveControl = _pointsInput;
_pointsInput.Focus();
_pointsInput.SelectionStart = _pointsInput.TextLength;
}));
}
private static bool IsWin7OrLower()
{
var version = Environment.OSVersion.Version;
return version.Major < 6 || (version.Major == 6 && version.Minor <= 1);
}
private void PointsInputOnTextChanged(object? sender, EventArgs e)
{
if (_suppressPointsTextChange)
{
return;
}
var rawText = _pointsInput.Text;
var filteredText = new string(rawText.Where(char.IsDigit).ToArray());
if (!string.Equals(rawText, filteredText, StringComparison.Ordinal))
{
var selectionStart = _pointsInput.SelectionStart;
var removedCount = rawText.Length - filteredText.Length;
_suppressPointsTextChange = true;
_pointsInput.Text = filteredText;
_pointsInput.SelectionStart = Math.Max(0, Math.Min(filteredText.Length, selectionStart - removedCount));
_suppressPointsTextChange = false;
}
UpdateConversion();
}
private void PointsInputOnKeyPress(object? sender, KeyPressEventArgs e)
{
if (char.IsControl(e.KeyChar) || char.IsDigit(e.KeyChar))
{
return;
}
e.Handled = true;
}
private void UpdateConversion()
{
var rawText = _pointsInput.Text.Trim();
if (string.IsNullOrEmpty(rawText))
{
SetRmbOutputText("0.00", GetRmbOutputBaseSize());
return;
}
if (!decimal.TryParse(rawText, out var points))
{
SetRmbOutputText("TL", GetRmbOutputBaseSize());
return;
}
if (points <= 0)
{
SetRmbOutputText("0.00", GetRmbOutputBaseSize());
return;
}
try
{
var rmb = ConvertPointsToRmb(points);
if (rmb < 0.01m)
{
SetRmbOutputText("0.00", GetRmbOutputBaseSize());
return;
}
UpdateRmbDisplay(rmb);
}
catch
{
SetRmbOutputText("TL", GetRmbOutputBaseSize());
}
}
private static decimal ConvertPointsToRmb(decimal points)
{
var pointSteps = new[] { 0m, RatePoints[0], RatePoints[1], RatePoints[2], RatePoints[3], RatePoints[4], RatePoints[5], RatePoints[6], RatePoints[7] };
var rmbSteps = new[] { 0m, RateRmb[0], RateRmb[1], RateRmb[2], RateRmb[3], RateRmb[4], RateRmb[5], RateRmb[6], RateRmb[7] };
if (points <= pointSteps[0])
{
return 0m;
}
if (points >= pointSteps[pointSteps.Length - 1])
{
return Interpolate(points, pointSteps[pointSteps.Length - 2], pointSteps[pointSteps.Length - 1], rmbSteps[rmbSteps.Length - 2], rmbSteps[rmbSteps.Length - 1]);
}
for (var i = 1; i < pointSteps.Length; i++)
{
if (points <= pointSteps[i])
{
return Interpolate(points, pointSteps[i - 1], pointSteps[i], rmbSteps[i - 1], rmbSteps[i]);
}
}
return 0m;
}
private static decimal Interpolate(decimal x, decimal x1, decimal x2, decimal y1, decimal y2)
{
if (x2 == x1)
{
return y1;
}
var ratio = (x - x1) / (x2 - x1);
return y1 + (y2 - y1) * ratio;
}
private void UpdateRmbDisplay(decimal rmb)
{
var baseSize = GetRmbOutputBaseSize();
var minSize = _rmbLabel.Font.Size;
var normalText = rmb.ToString("F2", CultureInfo.InvariantCulture);
var normalResult = FitTextToRmbOutput(normalText, baseSize, minSize);
if (normalResult.Fits)
{
SetRmbOutputText(normalResult.Text, normalResult.FontSize);
return;
}
var sciText = rmb.ToString("0.###E+0", CultureInfo.InvariantCulture);
var sciResult = FitTextToRmbOutput(sciText, baseSize, minSize);
if (sciResult.Fits)
{
SetRmbOutputText(sciResult.Text, sciResult.FontSize);
return;
}
var ellipsisText = "…请放大窗口";
var ellipsisResult = FitTextToRmbOutput(ellipsisText, baseSize, minSize);
SetRmbOutputText(ellipsisResult.Text, ellipsisResult.FontSize);
}
private float GetRmbOutputBaseSize()
{
var baseSize = _rmbOutputBaseFontSize > 0 ? _rmbOutputBaseFontSize : _rmbOutput.Font.Size;
return Math.Max(baseSize, _rmbLabel.Font.Size);
}
private (string Text, float FontSize, bool Fits) FitTextToRmbOutput(string text, float baseSize, float minSize)
{
var targetSize = Math.Max(baseSize, minSize);
if (TryFitText(text, targetSize))
{
return (text, targetSize, true);
}
for (var size = targetSize; size >= minSize; size -= 0.5f)
{
if (TryFitText(text, size))
{
return (text, size, true);
}
}
return (text, minSize, false);
}
private bool TryFitText(string text, float fontSize)
{
var availableWidth = _rmbOutput.ClientSize.Width;
var availableHeight = _rmbOutput.ClientSize.Height;
if (availableWidth <= 0 || availableHeight <= 0)
{
return true;
}
using (var font = new Font(_rmbOutput.Font.FontFamily, fontSize, _rmbOutput.Font.Style))
{
var size = TextRenderer.MeasureText(text, font, new Size(int.MaxValue, int.MaxValue), TextFormatFlags.NoPadding);
return size.Width <= availableWidth && size.Height <= availableHeight;
}
}
private void SetRmbOutputText(string text, float fontSize)
{
if (Math.Abs(_rmbOutput.Font.Size - fontSize) > 0.1f)
{
_rmbOutput.Font = new Font(_rmbOutput.Font.FontFamily, fontSize, _rmbOutput.Font.Style);
}
_rmbOutput.Text = text;
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
if (_fallbackTopMostTimer != null)
{
_fallbackTopMostTimer.Stop();
}
if (_uiAccessDll != IntPtr.Zero)
{
FreeLibrary(_uiAccessDll);
_uiAccessDll = IntPtr.Zero;
}
base.OnFormClosed(e);
}
private void TryInitializeUiAccess()
{
if (_uiAccessDll != IntPtr.Zero)
{
return;
}
var dllPath = EnsureUiAccessExtracted();
if (dllPath == null)
{
return;
}
_uiAccessDll = LoadLibraryW(dllPath);
if (_uiAccessDll == IntPtr.Zero)
{
return;
}
var proc = GetProcAddress(_uiAccessDll, "IsUIAccess");
if (proc == IntPtr.Zero)
{
FreeLibrary(_uiAccessDll);
_uiAccessDll = IntPtr.Zero;
return;
}
_isUiAccess = Marshal.GetDelegateForFunctionPointer<IsUIAccessDelegate>(proc);
_uiAccessEnabled = _isUiAccess();
}
private void TryInitializeIcon()
{
try
{
using (var stream = GetEmbeddedResourceStream(IconResourceSuffix))
{
if (stream == null)
{
return;
}
using (var icon = new Icon(stream))
{
Icon = (Icon)icon.Clone();
}
}
}
catch
{
}
}
private void ShowAboutDialog()
{
var dialog = new Form
{
Text = "关于",
StartPosition = FormStartPosition.CenterParent,
FormBorderStyle = FormBorderStyle.FixedDialog,
MaximizeBox = false,
MinimizeBox = false,
ShowInTaskbar = false,
ClientSize = new Size(520, 420)
};
var textBox = new TextBox
{
Multiline = true,
ReadOnly = true,
ScrollBars = ScrollBars.Vertical,
Dock = DockStyle.Fill,
Font = new Font("Microsoft YaHei UI", 10F),
BackColor = SystemColors.Window,
BorderStyle = BorderStyle.None
};
var buildTime = File.Exists(Application.ExecutablePath)
? File.GetLastWriteTime(Application.ExecutablePath).ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)
: "未知";
textBox.Text = string.Join(Environment.NewLine, new[]
{
"善学教育积分卡汇率计算器",
"版本号: 1.0.0",
"作者: ZerkyLiu",
$"构建时间:{buildTime}",
"运行环境:.NET 4.7",
$"系统信息:{Environment.OSVersion}",
$"UIAccess状态: {(_uiAccessEnabled ? "" : "")}",
"",
"功能亮点:",
"• 动态自适应结果展示",
"• 网络时间同步",
"• 高精度阶梯换算",
"• UIAccess 置顶支持",
"",
"未来计划:",
"• 联网自动更新",
"• 修复已知问题"
});
dialog.Controls.Add(textBox);
dialog.ShowDialog(this);
}
private bool TryRelaunchWithUiAccess()
{
if (_uiAccessEnabled)
{
return false;
}
var args = Environment.GetCommandLineArgs();
foreach (var arg in args)
{
if (string.Equals(arg, UiAccessRelaunchArg, StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
var dllPath = EnsureUiAccessExtracted();
if (string.IsNullOrEmpty(dllPath))
{
return false;
}
var exePath = Application.ExecutablePath;
var forwardedArgs = BuildForwardedArgs(args);
var startInfo = new ProcessStartInfo
{
FileName = "rundll32.exe",
Arguments = $"\"{dllPath}\",run \"{exePath}\" {forwardedArgs} {UiAccessRelaunchArg}",
UseShellExecute = false,
CreateNoWindow = true
};
try
{
Process.Start(startInfo);
BeginInvoke(new Action(Close));
return true;
}
catch
{
return false;
}
}
private string? EnsureUiAccessExtracted()
{
if (!string.IsNullOrEmpty(_uiAccessDllPath) && File.Exists(_uiAccessDllPath))
{
return _uiAccessDllPath;
}
var tempDir = Path.Combine(Path.GetTempPath(), "善学教育积分卡汇率计算器");
var dllPath = Path.Combine(tempDir, $"uiaccess_{Process.GetCurrentProcess().Id}.dll");
try
{
Directory.CreateDirectory(tempDir);
if (File.Exists(dllPath))
{
_uiAccessDllPath = dllPath;
return dllPath;
}
using (var stream = GetEmbeddedResourceStream(UiAccessResourceSuffix))
{
if (stream == null)
{
return null;
}
using (var file = new FileStream(dllPath, FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite))
{
stream.CopyTo(file);
}
}
_uiAccessDllPath = dllPath;
return dllPath;
}
catch
{
if (File.Exists(dllPath))
{
_uiAccessDllPath = dllPath;
return dllPath;
}
return null;
}
}
private static Stream? GetEmbeddedResourceStream(string suffix)
{
var assembly = typeof(MainForm).Assembly;
var resourceName = assembly.GetManifestResourceNames()
.FirstOrDefault(name => name.EndsWith(suffix, StringComparison.OrdinalIgnoreCase));
if (resourceName == null)
{
return null;
}
return assembly.GetManifestResourceStream(resourceName);
}
private static string BuildForwardedArgs(string[] args)
{
if (args.Length <= 1)
{
return string.Empty;
}
var builder = new System.Text.StringBuilder();
for (var i = 1; i < args.Length; i++)
{
if (string.Equals(args[i], UiAccessRelaunchArg, StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (builder.Length > 0)
{
builder.Append(' ');
}
builder.Append(QuoteArg(args[i]));
}
return builder.ToString();
}
private static string QuoteArg(string value)
{
if (string.IsNullOrEmpty(value))
{
return "\"\"";
}
if (value.IndexOfAny(new[] { ' ', '\t', '"', '\\' }) == -1)
{
return value;
}
var escaped = value.Replace("\\", "\\\\").Replace("\"", "\\\"");
return $"\"{escaped}\"";
}
private void ApplyTopMost(bool enabled)
{
TopMost = enabled;
if (!enabled)
{
SetFallbackTopMost(false);
SetUiAccessTopMost(false);
if (_uiAccessEnabled && IsHandleCreated)
{
SetWindowPos(Handle, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
return;
}
if (_forceFallbackTopMost)
{
SetFallbackTopMost(true);
ApplyFallbackTopMostTick();
return;
}
if (_uiAccessEnabled && IsHandleCreated)
{
SetFallbackTopMost(false);
SetUiAccessTopMost(true);
SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
ApplyFallbackTopMostTick();
return;
}
SetUiAccessTopMost(false);
SetFallbackTopMost(true);
ApplyFallbackTopMostTick();
}
private void SetUiAccessTopMost(bool enabled)
{
if (_usingUiAccessTopMost == enabled)
{
return;
}
_usingUiAccessTopMost = enabled;
if (enabled)
{
_fallbackTopMostTimer.Start();
}
else if (!_usingFallbackTopMost)
{
_fallbackTopMostTimer.Stop();
}
}
private void SetFallbackTopMost(bool enabled)
{
if (_usingFallbackTopMost == enabled)
{
return;
}
_usingFallbackTopMost = enabled;
_topMostToggle.Text = enabled ? "超级置顶(备)" : "超级置顶";
if (enabled)
{
_fallbackTopMostTimer.Start();
}
else if (!_usingUiAccessTopMost)
{
_fallbackTopMostTimer.Stop();
}
}
private void ApplyFallbackTopMostTick()
{
if ((!_usingFallbackTopMost && !_usingUiAccessTopMost) || !IsHandleCreated)
{
return;
}
TopMost = true;
SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate bool IsUIAccessDelegate();
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr LoadLibraryW(string lpFileName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FreeLibrary(IntPtr hModule);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
private const uint SWP_NOMOVE = 0x0002;
private const uint SWP_NOSIZE = 0x0001;
private const uint SWP_NOACTIVATE = 0x0010;
}
}