/*
* 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 .
*/
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(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;
}
}