# aardio 范例: Windows 11 · 右键菜单风格



```aardio
//RUNAS//Windows 11 右键菜单风格
//新版 Feature Flags 写入需要管理员权限

import win.ui;
import win.versionEx;
import win.reg;
import process;

if(!win.versionEx.isWin11Later){
	win.msgboxErr("此范例仅用于 Windows 11。");
	return;
}

// 按完整版本号判断，不能只比较 buildNumber，修订号也很重要。
var useFeatureFlags = win.versionEx.isGreaterOrEqual("10.0.26200.9807");
var featureFlags;
if(useFeatureFlags){
	import sys.featureFlags;
	featureFlags = sys.featureFlags;
}

// 这些 ID 控制 Windows 11 新版可自定义菜单，并非直接启用 Win10 经典菜单。
// 新系统切换经典菜单：先禁用新版路径，再设置原来的 CLSID 覆盖。
// Feature ID 属于 Windows 内部实现，后续系统更新可能改变其含义或效果。
var menuFeatures = [60813048,49453572,61934903];
var menuClsid = "{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}";
var classesPath = "HKCU\Software\Classes\CLSID";
var classicPath = classesPath ++ "\" ++ menuClsid;

// 只读查询，不创建注册表项。空字符串与“未设置默认值”不能混为一谈。
var readClassic = function(){
	var reg,err,code = win.regReaderWow64(classicPath ++ "\InprocServer32");
	if(!reg){
		if(code == 2 || code == 3) return false;
		return null,err;
	}
	var value = reg.queryValue("");
	reg.close();
	return value === "";
}

var writeClassic = function(enabled){
	if(enabled){
		var reg,err = win.regWow64(classicPath ++ "\InprocServer32");
		if(!reg) return null,err;
		var ok,err = reg.setSzValue("","");
		reg.close();
		return ok,err;
	}

	// 已不存在即为成功，避免恢复默认时无意创建空键。
	var existing,err,code = win.regWow64(classicPath,true);
	if(!existing){
		if(code == 2 || code == 3) return true;
		return null,err;
	}
	existing.close();
	var reg,err = win.regWow64(classesPath,true);
	if(!reg) return null,err;
	var ok,code = reg.delKeyTree(menuClsid);
	reg.close();
	if(!ok) return null,"删除经典菜单注册表项失败，错误码：" ++ tostring(code);
	return true;
}

// 这里只判断已保存的配置，不能据此断言正在运行的 Explorer 已经生效。
var classifyStyle = function(classic,features){
	if(!useFeatureFlags) return classic ? "classic" : "modern";
	var allDisabled,allEnabled,hasUserOverride = true,true,false;
	for(i=1;#menuFeatures){
		var info = features[i];
		if(info.error) return null,"Feature " ++ tostring(info.featureId) ++ "：" ++ info.error;
		if(info.enabledState != featureFlags.state.disabled) allDisabled = false;
		if(info.enabledState != featureFlags.state.enabled) allEnabled = false;
		if(info.priority >= featureFlags.priority.user) hasUserOverride = true;
	}
	if(classic && allDisabled) return "classic";
	if(!classic && (allEnabled || allDisabled)) return "modern";
	if(!classic && !hasUserOverride) return "system";
	return "mixed";
}

var readStyle = function(){
	var classic,err = readClassic();
	if(classic === null) return null,err;
	var features;
	if(useFeatureFlags) features = featureFlags.queryMany(menuFeatures);
	return classifyStyle(classic,features);
}

var applyStyle = function(mode){
	if(mode != "classic" && mode != "modern" && mode != "system")
		return null,"未知的菜单风格。";
	if(useFeatureFlags){
		// 查询失败不写入，避免在无法识别目标特性的系统上盲目修改。
		var features = featureFlags.queryMany(menuFeatures);
		for(i=1;#features){
			if(features[i].error) return null,features[i].error;
		}
		var ok,err;
		if(mode == "classic") ok,err = featureFlags.disable(menuFeatures);
		elseif(mode == "modern") ok,err = featureFlags.enable(menuFeatures);
		else ok,err = featureFlags.reset(menuFeatures); // 删除 User 覆盖，不等于强制启用
		if(!ok) return null,"Feature Flags 写入失败，请以管理员权限运行。" ++ '\r\n' ++ err;
	}

	var ok,err = writeClassic(mode == "classic");
	if(!ok){
		// Feature API 与注册表不是同一个事务：明确报告部分成功，不伪装成全部成功。
		if(useFeatureFlags) return null,"Feature Flags 已更新，但注册表操作失败。请重试或恢复系统默认。" ++ '\r\n' ++ err,true;
		return null,err;
	}
	::Shell32.SHChangeNotify(0x8000000/*_SHCNE_ASSOCCHANGED*/,0,0,0);
	return true;
}
/*DSG{{*/
var winform = win.form(text="Windows 11 · 右键菜单风格";right=659;bottom=587;bgcolor=0xFAF7F4;border="dialog frame";font=LOGFONT(h=-13;name='Microsoft YaHei UI');max=false)
winform.add(
apply={cls="plus";text="应用所选风格";left=504;top=534;right=632;bottom=574;border={radius=8};notify=1;z=13};
classicCard={cls="plus";text="经典菜单";left=28;top=176;right=322;bottom=410;align="left";border={color=0xEBE2DA;radius=14;width=1};color=0x30251B;font=LOGFONT(h=-18;name='Microsoft YaHei UI';weight=600);notify=1;textPadding={left=20;top=18;right=48};valign="top";z=6};
current={cls="plus";text="正在读取配置…";left=28;top=427;right=632;bottom=453;align="left";color=0x30251B;font=LOGFONT(h=-14;name='Microsoft YaHei UI';weight=600);z=8};
intro={cls="plus";text="熟悉的经典菜单，或简洁的 Windows 11，由你选择。";left=28;top=97;right=632;bottom=123;align="left";color=0x857466;z=3};
modernCard={cls="plus";text="Windows 11";left=338;top=176;right=632;bottom=410;align="left";border={color=0xEBE2DA;radius=14;width=1};color=0x30251B;font=LOGFONT(h=-18;name='Microsoft YaHei UI';weight=600);notify=1;textPadding={left=20;top=18;right=48};valign="top";z=7};
refresh={cls="plus";text="刷新状态";left=550;top=130;right=632;bottom=160;border={radius=7};color=0xC06125;z=5};
reset={cls="plus";text="恢复系统默认";left=28;top=534;right=170;bottom=574;border={color=0xEBE2DA;radius=8;width=1};z=11};
restart={cls="plus";text="重启资源管理器";left=326;top=534;right=490;bottom=574;border={color=0xEBE2DA;radius=8;width=1};z=12};
section={cls="plus";text="WINDOWS 11  /  个性化";left=28;top=24;right=440;bottom=44;align="left";color=0xC06125;font=LOGFONT(name='Microsoft YaHei UI';weight=600);z=1};
separator={cls="bkplus";left=28;top=515;right=632;bottom=516;bgcolor=0xEBE2DA;z=10};
status={cls="plus";left=28;top=461;right=632;bottom=501;align="left";color=0x7B6959;font=LOGFONT(h=-13;name='Microsoft YaHei UI');valign="top";z=9};
systemInfo={cls="plus";left=28;top=132;right=542;bottom=157;align="left";color=0x7B6959;font=LOGFONT(h=-13;name='Microsoft YaHei UI');z=4};
title={cls="plus";text="右键菜单风格";left=26;top=51;right=632;bottom=91;align="left";color=0x30251B;font=LOGFONT(h=-28;name='Microsoft YaHei UI';weight=700);z=2}
)
/*}}*/

// 使用 plus 的状态皮肤；卡片预览是示意图，不读取或模拟实际系统菜单。
for i,card in [winform.classicCard,winform.modernCard]{
	card.skin({
		background={default=0xFFFFFFFF;hover=0xFFF8FAFD;active=0xFFF0F5FC};
		border={default={radius=14;width=1;color=0xFFDAE2EB};hover={radius=14;width=1;color=0xFF8AAED6}};
		checked={background={default=0xFFF2F7FF;hover=0xFFEDF4FF};border={default={radius=14;width=2;color=0xFF2563C0}}};
		cursor="hand"
	});
}

for i,button in [winform.reset,winform.restart,winform.refresh]{
	button.skin({background={default=0xFFFFFFFF;hover=0xFFEBF1FA;active=0xFFE1EAF6;disabled=0xFFF1F3F6};color={default=0xFF46566A;disabled=0xFF9BA7B6};cursor="hand"});
}

winform.apply.skin({background={default=0xFF2563C0;hover=0xFF3475D3;active=0xFF1D50A0;disabled=0xFFDFE6EF};color={default=0xFFFFFFFF;disabled=0xFF8C9CAF};cursor="hand"});

// 缓存绘图对象。自绘统一用逻辑坐标，避免高 DPI 下文字和菜单预览错位。
var drawing = {
	font=gdip.font("Microsoft YaHei UI",11.5);
	muted=gdip.solidBrush(0xFF59697B);
	ink=gdip.solidBrush(0xFF334155);
	stage=gdip.solidBrush(0xFFEAF0F7);
	paper=gdip.solidBrush(0xFFFFFFFF);
	highlight=gdip.solidBrush(0xFFE5EEFC);
	accent=gdip.solidBrush(0xFF2563C0);
	line=gdip.pen(0xFFD6DFEA,1);
	ring=gdip.pen(0xFF8A9BAF,1.3)
};

var drawPreview = function(graphics,rc){
	var modern = owner == winform.modernCard;
	var dpiX,dpiY = winform.dpiScale(1,1);
	var saved = graphics.save();
	
	graphics.scale(dpiX,dpiY);
	graphics.compositingQuality = 0;
	graphics.drawString(modern ? "简洁布局 · 新系统支持自定义" : "Windows 10 风格 · 完整命令直达",drawing.font,20,52,drawing.muted);
	graphics.drawCircle(drawing.ring,267,30,8);
	
	if(owner.checked) graphics.fillCircle(drawing.accent,267,30,5);
	graphics.fillRoundRect(drawing.stage,20,82,254,132,10);
	graphics.fillRoundRect(drawing.paper,43,92,208,112,modern ? 8 : 2);
	graphics.drawRoundRect(drawing.line,43,92,208,112,modern ? 8 : 2);
	
	if(modern){
		graphics.drawString("剪切      复制      删除",drawing.font,58,99,drawing.ink);
		graphics.drawLine(drawing.line,53,124,241,124);
		graphics.drawString("打开",drawing.font,59,131,drawing.ink);
		graphics.drawString("应用扩展                         ›",drawing.font,59,154,drawing.ink);
		graphics.drawString("自定义菜单",drawing.font,59,177,drawing.muted);
	}
	else{
		graphics.fillRect(drawing.highlight,47,95,200,20);
		for i,text in ["打开","剪切","复制","删除","属性"]{
			graphics.drawString(text,drawing.font,59,96+(i-1)*21,drawing.ink);
		}
	}
	
	graphics.restore(saved);
	return true;
}

winform.classicCard.onDrawForeground = drawPreview;
winform.modernCard.onDrawForeground = drawPreview;
winform.onDestroy = function(){
	for name,obj in drawing { obj.delete(); }
}

var selectedMode;
var names = {classic="经典菜单";modern="Windows 11";system="跟随 Windows 默认";mixed="混合配置，请重新选择"};
var selectStyle = function(mode){
	selectedMode = null;
	if(mode == "classic" || mode == "modern") selectedMode = mode;
	winform.classicCard.checked = mode == "classic";
	winform.modernCard.checked = mode == "modern";
	winform.apply.disabled = !selectedMode;
}

var setStatus = function(text,isError){
	winform.status.text = text;
	winform.status.color = isError ? 0xFFB34436 : 0xFF59697B;
}

var refreshState = function(){
	var mode,err = readStyle();
	selectStyle(mode);
	winform.current.text = "当前配置：" ++ (names[mode] : "读取失败");
	if(!mode) setStatus(err,true);
	else setStatus("选择卡片不会修改系统。新版开关影响整台电脑，注册表项仅影响当前账户。");
}

winform.systemInfo.text = win.versionEx.full() ++ (useFeatureFlags ? "  ·  Feature Flags + 注册表" : "  ·  注册表兼容模式");
winform.refresh.oncommand = refreshState;
winform.classicCard.oncommand = function(){
	selectStyle("classic");
	setStatus("已选择经典菜单。点击“应用所选风格”后保存，不会自动重启资源管理器。");
}
winform.modernCard.oncommand = function(){
	selectStyle("modern");
	setStatus("已选择 Windows 11。点击“应用所选风格”后保存，不会自动重启资源管理器。");
}

import win.ui.tooltip;
var balloonTipCtrl = win.ui.tooltip.tracking(winform,false);
var saveMode = function(mode){
	var message = "即将设置为：" ++ names[mode] ++ "。";
	if(useFeatureFlags) message ++= '\r\n新版 Feature 开关影响整台电脑；经典菜单注册表项仅影响当前账户。';
	message ++= '\r\n保存后需重启资源管理器；如果仍未生效，请注销或重启系统。继续？';
	if(!winform.msgboxTest(message,"保存菜单配置")) return;
	var ok,err,partial = applyStyle(mode);
	refreshState();
	if(ok) {
		setStatus("已保存。请先完成文件复制等操作，再点击“重启资源管理器”使配置生效。")
		 
    	var x,y,cx,cy = winform.restart.getPos(true); 
    	balloonTipCtrl.setText('请先完成文件复制等操作。\n然后继续点击「重启资源管理器」。').trackPopup(true,x+120,y+cy );
	};
	else{
		setStatus(partial ? "部分设置已更新，请重试或恢复系统默认。" : "保存失败，请查看错误提示；当前配置已重新读取。",true);
		winform.msgboxErr(err,"未能完成设置");
	}
}

winform.apply.oncommand = function(){
	if(selectedMode) saveMode(selectedMode);
}
winform.reset.oncommand = function(){ saveMode("system"); }

// 只结束当前桌面的 Shell 进程，不按进程名杀掉其他登录会话的 Explorer。
// 桌面由 Windows 的 AutoRestartShell 以原用户身份重新启动，避免提权启动 Explorer。
var restartExplorer = function(){
	var shell = ::User32.GetShellWindow();
	if(!shell) return null,"未找到桌面进程，请在任务管理器中手动重启 Windows 资源管理器。";
	
	var pid = {INT value};
	::User32.GetWindowThreadProcessId(shell,pid);
	
	var prcs,err = process(pid.value,0x100001/*_SYNCHRONIZE | _PROCESS_TERMINATE*/);
	if(!prcs) return null,err;
	
	var ok = ::Kernel32.TerminateProcess(prcs.handle,0);
	prcs.free();
	
	if(!ok) return null,"无法结束桌面进程，请在任务管理器中手动重启。";
	return pid.value;
}

winform.restart.oncommand = function(){
	if(!winform.msgboxTest("此操作会关闭当前桌面进程中的文件夹窗口，并短暂隐藏任务栏。" ++ '\r\n请先完成文件复制、移动等操作。现在重启？',"重启资源管理器")) return;
	winform.restart.disabledText = "正在重启…";
	var previousPid,err = restartExplorer();
	
	if(previousPid){
		var ready = winform.waitUntil(function(){
			var shell = ::User32.GetShellWindow();
			if(!shell) return false;
			var pid = {INT value};
			::User32.GetWindowThreadProcessId(shell,pid);
			return pid.value != previousPid;
		},8000);
		
		if(ready) setStatus("资源管理器已重启。可在桌面或文件上单击右键查看实际效果。");
		else setStatus("桌面未自动恢复。请按 Ctrl+Shift+Esc，在任务管理器中运行 explorer.exe。",true);
	}
	else setStatus(err,true);
	winform.restart.disabledText = null;
}

refreshState();
winform.show();
win.loopMessage();

```

