src/programFrames/constexpr.cs

// 由 Ingo Karstein 创建的简单 PowerShell 主机(http://blog.karstein-consulting.com),由 Markus Scholtes 重构并支持 GUI。
 
using System;
using System.Collections.Generic;
using System.Text;
using System.Globalization;
using System.Reflection;
#if noConsole
    using System.Windows.Forms;
    using System.Drawing;
    using System.Runtime.InteropServices;
#endif
using System.Runtime.Versioning;
 
/*__ASSEMBLY_ATTRIBUTES__*/
namespace PSRunnerNS {
    #if noConsole
    // 常量工程的窗口化输出使用同一套 WinForms 消息框,仅根据 App.DarkMode 与系统主题切换调色板。
    internal static class ConstMessageBox {
        [DllImport("dwmapi.dll")]
        static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);
        [DllImport("user32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
        static extern IntPtr MB_GetString(uint strId);
        #if !darkModeOff && !Pwsh20
        [DllImport("uxtheme.dll", CharSet = CharSet.Unicode)]
        static extern int SetWindowTheme(IntPtr hWnd, string pszSubAppName, string pszSubIdList);
        [DllImport("kernel32.dll", CharSet = CharSet.Ansi)]
        static extern IntPtr GetModuleHandle(string name);
        [DllImport("kernel32.dll", CharSet = CharSet.Ansi)]
        static extern IntPtr GetProcAddress(IntPtr hModule, IntPtr procName);
        delegate int SetPreferredAppModeDel(int mode);
        delegate void FlushMenuThemesDel();
 
        static readonly Color DarkWindowColor = Color.FromArgb(32, 32, 32);
        static readonly Color DarkFieldColor = Color.FromArgb(45, 45, 45);
        static readonly Color DarkBorderColor = Color.FromArgb(64, 64, 64);
        static readonly Color DarkTextColor = Color.FromArgb(245, 245, 245);
        static bool resolved;
        static bool isDark;
        const int WM_SETTINGCHANGE = 0x001A;
        public static bool IsDark {
            get {
                if (!resolved) {
                    resolved = true;
                    #if darkModeOn
                    isDark = true; // 编译期强制暗色,不做探测
                    #elif darkModeOff || Pwsh20
                    isDark = false;
                    #else
                    try {
                        object value = Microsoft.Win32.Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme", 1);
                        isDark = value != null && Convert.ToInt32(value) == 0;
                    } catch { isDark = false; }
                    #endif
                    Enable();
                }
                return isDark;
            }
        }
        #if !darkModeOn
        static void RefreshSystemTheme() {
            bool wasDark = isDark;
            resolved = true;
            try {
                object value = Microsoft.Win32.Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme", 1);
                isDark = value != null && Convert.ToInt32(value) == 0;
            } catch { isDark = false; }
            if (wasDark != isDark) { Enable(); }
        }
        #endif
        public static Color WindowColor { get { return IsDark ? DarkWindowColor : Color.White; } }
        public static Color FieldColor { get { return IsDark ? DarkFieldColor : Color.FromArgb(240, 240, 240); } }
        public static Color BorderColor { get { return IsDark ? DarkBorderColor : Color.FromArgb(173, 173, 173); } }
        public static Color TextColor { get { return IsDark ? DarkTextColor : SystemColors.ControlText; } }
 
        static void Enable() {
            try {
                // uxtheme 未公开序号:135=SetPreferredAppMode,136=FlushMenuThemes(见 ysc3839/win32-darkmode)。
                IntPtr uxtheme = GetModuleHandle("uxtheme.dll");
                if (uxtheme == IntPtr.Zero) { return; }
                IntPtr proc = GetProcAddress(uxtheme, (IntPtr)135); // SetPreferredAppMode
                if (proc != IntPtr.Zero) { ((SetPreferredAppModeDel)Marshal.GetDelegateForFunctionPointer(proc, typeof(SetPreferredAppModeDel)))(isDark ? 1 : 3); }
                proc = GetProcAddress(uxtheme, (IntPtr)136); // FlushMenuThemes
                if (proc != IntPtr.Zero) { ((FlushMenuThemesDel)Marshal.GetDelegateForFunctionPointer(proc, typeof(FlushMenuThemesDel)))(); }
            } catch { }
        }
 
        public static void ApplyTitleBar(IntPtr hwnd) {
            try {
                SetWindowTheme(hwnd, isDark ? "DarkMode_Explorer" : null, null);
                int on = isDark ? 1 : 0;
                DwmSetWindowAttribute(hwnd, 20, ref on, sizeof(int)); // DWMWA_USE_IMMERSIVE_DARK_MODE
                DwmSetWindowAttribute(hwnd, 19, ref on, sizeof(int)); // 旧 build
            } catch { }
        }
 
        sealed class MessageBoxForm : Form {
            internal Control ContentControl;
 
            protected override void OnHandleCreated(EventArgs e) {
                base.OnHandleCreated(e);
                ApplyDarkMode();
            }
 
            #if !darkModeOn
            protected override void WndProc(ref Message m) {
                if (m.Msg == WM_SETTINGCHANGE && IsImmersiveColorSet(m.LParam)) {
                    RefreshSystemTheme();
                    ApplyDarkMode();
                }
                base.WndProc(ref m);
            }
 
            static bool IsImmersiveColorSet(IntPtr lParam) {
                if (lParam == IntPtr.Zero) { return true; }
                try {
                    string value = Marshal.PtrToStringUni(lParam);
                    return string.IsNullOrEmpty(value) || value.IndexOf("ImmersiveColorSet", StringComparison.OrdinalIgnoreCase) >= 0;
                } catch { return true; }
            }
            #endif
 
            internal void ApplyDarkMode() {
                BackColor = WindowColor;
                ForeColor = TextColor;
                foreach (Control control in Controls) {
                    control.BackColor = control is Button ? FieldColor : WindowColor;
                    control.ForeColor = TextColor;
                    Button button = control as Button;
                    if (button != null) {
                        button.FlatAppearance.BorderColor = BorderColor;
                        button.UseVisualStyleBackColor = false;
                    }
                }
                ApplyTitleBar(Handle);
                TextBox box = ContentControl as TextBox;
                #if !darkModeOff && !Pwsh20
                if (box != null) { SetWindowTheme(box.Handle, IsDark ? "DarkMode_CFD" : null, null); }
                #endif
            }
        }
        #else
        public static bool IsDark { get { return false; } }
        public static Color WindowColor { get { return Color.White; } }
        public static Color FieldColor { get { return Color.FromArgb(240, 240, 240); } }
        public static Color BorderColor { get { return Color.FromArgb(173, 173, 173); } }
        public static Color TextColor { get { return SystemColors.ControlText; } }
 
        public static void ApplyTitleBar(IntPtr hwnd) {
            int light = 0;
            DwmSetWindowAttribute(hwnd, 20, ref light, sizeof(int));
            DwmSetWindowAttribute(hwnd, 19, ref light, sizeof(int));
        }
        #endif
 
        // 本地化 "确定" 按钮文案;取不到时回退英文。
        static string LocalizedOk() {
            try {
                string value = Marshal.PtrToStringUni(MB_GetString(0));
                if (!string.IsNullOrEmpty(value)) { return value; }
            } catch { }
            return "OK";
        }
 
        // 对齐原生 MessageBox(Information) 的观感:左侧 32@96dpi 信息图标、文本区自动换行、
        // 底部 80x28 本地化确认按钮,整套几何按 DPI 缩放。
        public static void Show(string text, string title) {
            #if darkModeOff
            MessageBox.Show(text, title, MessageBoxButtons.OK);
            #else
            if (text == null) { text = ""; }
            using (MessageBoxForm form = new MessageBoxForm()) {
                form.Text = title;
                form.FormBorderStyle = FormBorderStyle.FixedDialog;
                form.StartPosition = FormStartPosition.CenterScreen;
                form.MinimizeBox = false;
                form.MaximizeBox = false;
                form.ShowIcon = true;
                form.AutoScaleMode = AutoScaleMode.None;
                form.BackColor = WindowColor;
                form.ForeColor = TextColor;
                form.Font = SystemFonts.MessageBoxFont;
 
                int dpi = (int)form.CreateGraphics().DpiX;
                int marginLeft = dpi * 22 / 96, marginTop = dpi * 28 / 96, gapIconText = dpi * 10 / 96;
                int marginRight = dpi * 18 / 96, marginBottom = dpi * 14 / 96, gapTextButton = dpi * 36 / 96;
                int buttonWidth = dpi * 80 / 96, buttonHeight = dpi * 28 / 96;
                Size iconSize = SystemInformation.IconSize;
 
                int workingWidth = Screen.FromControl(form).WorkingArea.Width;
                int cap = workingWidth * 5 / 8 - dpi * 18 / 96;
                cap = System.Math.Min(cap, workingWidth / 4);
 
                Label content = new Label();
                content.AutoSize = true;
                content.UseMnemonic = false;
                content.MaximumSize = new Size(cap, 0);
                content.Font = form.Font;
                content.ForeColor = TextColor;
                content.BackColor = WindowColor;
                content.Text = text;
 
                // 超长文本兜底:改用只读多行 TextBox 限高可滚动(原生也会尽量长高,这里保证不超出屏幕)。
                int maxHeight = Screen.FromControl(form).WorkingArea.Height - dpi * 220 / 96;
 
                Control contentControl;
                int contentWidth, contentHeight;
                Size wrapped = TextRenderer.MeasureText(text, form.Font, new Size(cap, int.MaxValue), TextFormatFlags.WordBreak | TextFormatFlags.NoPadding | TextFormatFlags.NoPrefix);
                if (wrapped.Height > maxHeight && maxHeight > dpi * 40 / 96) {
                    TextBox box = new TextBox();
                    box.Multiline = true;
                    box.ReadOnly = true;
                    box.WordWrap = true;
                    box.ScrollBars = ScrollBars.Vertical;
                    box.BorderStyle = BorderStyle.FixedSingle;
                    box.TabStop = false;
                    box.Cursor = Cursors.Arrow;
                    box.BackColor = WindowColor;
                    box.ForeColor = TextColor;
                    box.Font = form.Font;
                    box.Text = text;
                    contentControl = box;
                    contentWidth = cap;
                    contentHeight = maxHeight;
                } else {
                    contentControl = content;
                    contentWidth = wrapped.Width;
                    contentHeight = wrapped.Height;
                }
 
                int textLeft = marginLeft + iconSize.Width + gapIconText;
                int bodyHeight = System.Math.Max(iconSize.Height, contentHeight);
                int clientWidth = System.Math.Max(textLeft + contentWidth + marginRight, marginLeft + buttonWidth + marginRight);
                int buttonTop = marginTop + bodyHeight + gapTextButton;
                int clientHeight = buttonTop + buttonHeight + marginBottom;
                form.ClientSize = new Size(clientWidth, clientHeight);
 
                PictureBox icon = new PictureBox();
                icon.Size = iconSize;
                icon.BackColor = WindowColor;
                icon.SizeMode = PictureBoxSizeMode.StretchImage;
                icon.Image = new Icon(SystemIcons.Information, iconSize).ToBitmap();
                icon.Location = new Point(marginLeft, marginTop);
                form.Controls.Add(icon);
 
                // 短文本相对图标垂直居中,长文本与图标顶对齐。
                int textTop = contentHeight < iconSize.Height ? marginTop + (iconSize.Height - contentHeight) / 2 : marginTop;
                contentControl.Location = new Point(textLeft, textTop);
                contentControl.Size = new Size(contentWidth, contentHeight);
                form.Controls.Add(contentControl);
                form.ContentControl = contentControl;
 
                Button ok = new Button();
                ok.Text = LocalizedOk();
                ok.DialogResult = DialogResult.OK;
                ok.FlatStyle = FlatStyle.Flat;
                ok.FlatAppearance.BorderSize = 1;
                ok.FlatAppearance.BorderColor = BorderColor;
                ok.BackColor = FieldColor;
                ok.ForeColor = TextColor;
                ok.UseVisualStyleBackColor = false;
                ok.Size = new Size(buttonWidth, buttonHeight);
                ok.Location = new Point(clientWidth - marginRight - buttonWidth, buttonTop);
                form.Controls.Add(ok);
 
                form.AcceptButton = ok;
                form.ActiveControl = ok;
                if (form.IsHandleCreated) { form.ApplyDarkMode(); }
                form.ShowDialog();
            }
            #endif
        }
    }
    #endif
 
    internal static class PSRunnerEntry {
        private static int Main() {
            #if !noOutput
                #if UNICODEEncoding && !noConsole
                System.Console.OutputEncoding = new System.Text.UnicodeEncoding();
                #endif
                #if UTF8Encoding && !noConsole
                System.Console.OutputEncoding = new System.Text.UTF8Encoding();
                #endif
 
                #if !noVisualStyles && noConsole
                Application.EnableVisualStyles();
                #endif
 
                #if noConsole
                    // 加载 assembly:AssemblyTitle
                    AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute) Attribute.GetCustomAttribute(Assembly.GetExecutingAssembly(), typeof(AssemblyTitleAttribute));
                    string title;
                    if (titleAttribute != null)
                        title = titleAttribute.Title;
                    else
                        title = System.AppDomain.CurrentDomain.FriendlyName;
                    // 弹窗输出 \ConstResult
                    string[] ConstResult = { "$ConstResult" };
                    foreach (string item in ConstResult) {
                        ConstMessageBox.Show(item, title);
                    }
                #else
                    // 控制台输出 \ConstResult
                    System.Console.WriteLine("$ConstResult");
                #endif
            #endif
            return $ConstExitCodeResult;
        }
    }
}