制作背景
之前做Win8 Metro动态加载内容框架的时候,由于采用了XAML+JavaScript的方法,程序复杂的执行逻辑是由JavaScript控制的,而页面一多,流程一复杂,制作起来就非常麻烦,还要考虑不同XAML页面返回事件的名称。所以就写了个状态机模型的程序制作工具。
技术支持
说到制作状态机,无疑微软的VS是最强大的,微软本身就有Workflow。使用微软的工作流就可以很方便的制作出一个状态机,然后在用System.Workflow下的类库提取,整理结构,然后生成JavaScript就完事了。
使用VS来制作工作流XAML是因为VS非常直观方便,所以只使用微软工作流里的StateMachine、State、Transition、WriteLine(占位识别),支持嵌套状态机。
技术详解
-
解析微软工作流XAML
首先需要引用微软工作流相关的类库System.Activities、System.Workflow.ComponentModel、System.Workflow.Activities、System.Workflow.Runtime、System.WorkflowServices。
读取活动树,path为VS生成的工作流XAML文件路径:
Activity act = ActivityXamlServices.Load(path);
然后对活动树进行解析,活动树的类型主要处理类型:StateMachine、State(需要处理状态的Enter、Exit和Transition)、Literal`1(状态转移条件填写为True时)、CSharpValue`1(状态转移条件需要后续编程时)、WriteLine(普通预埋点)
状态转移条件
这一步的目的是把微软工作流的复杂对象进行整理,方便序列化给JavaScript使用,并对预埋的需要扩展JavaScript编程的地方创建JavaScript函数名(创建方法需要根据活动树生成,要保证修改工作流后原有的未更改部分生成名一致),并记录到函数表上。
整理后的活动树
-
JavaScript代码填写程序补完
这一部分没什么太多说的,把之前解析出来的JavaScript函数列出来,填写上即可。当时我还是使用DataSet比较多,所以函数表使用DataSet做的,请各位想象成喜欢的形式:
void initCodeTable()
{
codeTable = new DataTable("Code");
codeTable.Columns.Add("Guid");
codeTable.Columns.Add("Event");
codeTable.Columns.Add("Interface");
codeTable.Columns.Add("Codes");
CM.Tables.Add(codeTable);
}这里事件的概念主要是配合Win8Metro那头的框架设计的。事件都是XAML+JS Api中的。
事件列表
-
JavaScript组装
上一步已经把JavaScript函数全都做完了,接下来把这些函数生成到最终的html文件中就好了。这里预先写一个模版文件,执行主体JavaScript状态机就在这个文件里面。
JavaScript的功底比较菜,没做封装,各位凑合看看吧:
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
<script type="text/javascript" src="jquery-1.8.2.min.js"></script>
<script type="text/javascript" src="core-min.js"></script>
<script type="text/javascript" src="enc-base64-min.js"></script>
<script type="text/javascript" src="json.js"></script>
<script type="text/javascript" src="UppApp.js"></script><!--UppBox通讯类库-->
<script type="text/javascript" src="Network.js"></script>
</head>
<body>
<script type="text/javascript">
//省略UppBox应用层初始化 var DEBUG = false;
var NETDEBUG = false;
var SMEngine;
var currSM;
var SUSPEND = false; //状态机数据注入点
//<!--JSONDATA--> //状态用临时变量
var tempVar = (function () {
function tempVar() { }
return tempVar;
})(); function MessageBus(para);//接收Win8Metro发来的信息,具体内容略function SendMsg(cmd, msg);//向Win8Metro发送信息,具体内容略
function DebugInfo(para);//调试信息传递,可通过控制台/UppBox/body的DOM节点 function InitApp() {
//网络状态,配置项等的初始化注入点
//<!--INITDATA-->
AppDemo.initFW();//UppBox初始化
SMEngine = eval(json);//json为上方注入的内容,是个Json结构对象
currSM = SMEngine;
if (DEBUG) DebugInfo("Init()" + currSM.DisplayName);
EntryState(currSM.States[0]);
try {
AppLoaded();
} catch (e) {
}
} function runFunction(func, para) {
if (func == null) return false;
switch (func.Type) {
case "Action":
var funName = func.Code;
try {
if (DEBUG) DebugInfo("runFunction():" + funName + " para:" + para);
var result = eval(funName + "(para);");
return result;
} catch (e) {
return false;
}
break; default:
break;
}
} function TAction(action, entryState, para) {
if (DEBUG) DebugInfo("TAction:" + action.DisplayName + " " + entryState.DisplayName + " " + para);
try {
if (para != "[SUSPEND]") {
var result = runFunction(action, para);
if (result == false) {
SUSPEND = true;
setTimeout(function (action, entryState) {
TAction(action, entryState, "[SUSPEND]");
}, 500, action, entryState);
} else {
EntryState(entryState);
}
}
else {
if (SUSPEND == false) {
EntryState(entryState);
} else {
setTimeout(function (action, entryState) {
TAction(action, entryState, "[SUSPEND]");
}, 500, action, entryState);
}
}
} catch (e) {
}
} function Trigger(para) {
if (SUSPEND) {
if (DEBUG) DebugInfo("SUSPEND():" + para);
otherFun(para);
return;
}
for (var i = 0; i < currSM.currState.Transitions.length; i++) {
var result = runFunction(currSM.currState.Transitions[i].Trigger, para);
if (result == true) {
var result = runFunction(currSM.currState.Transitions[i].Condition, para);
if (result == true) {
var stateName = currSM.currState.Transitions[i].To;
for (var j = 0; j < currSM.States.length; j++) {
var sns = currSM.States[j].DisplayName.split("_");
var CurrName = sns[sns.length - 1];
if (CurrName == stateName) {
ExitState();
TAction(currSM.currState.Transitions[i].Action, currSM.States[j], para);
return;
}
}
return;
}
}
}
var result = runFunction(currSM.currState.Main, para);
if (result != true) {
if (DEBUG) DebugInfo("otherFun(): para:" + para);
otherFun(para);
}
} function otherFun(para) {
try {
var str = "";
var msg = "";
try {
str = para.split(" ");
if (str.length > 1) msg = str[1];
for (var i = 2; i < str.length; i++) {
msg += " " + str[i];
}
eval(str[0] + "(msg)");
} catch (e) {
str = para;
eval(str + "()");
}
} catch (e) {
}
} function EntryState(state) {
if (DEBUG) {
var debug = "EntryState():" + state.DisplayName;
if (currSM.currState != null) debug += " CurrState:" + currSM.currState.DisplayName;
DebugInfo(debug);
}
switch (state.Entry.Type) {
case "Action":
if (!state.IsFinal) {
currSM.currState = state;
} else {
while (true) {
if (currSM.preSM == null) break;
currSM = currSM.preSM;
if (!currSM.currState.IsFinal) break;
}
}
runFunction(state.Entry, null);
Trigger("");
break; case "StateMachine":
currSM.currState = state;
state.Entry.preSM = currSM;
currSM = state.Entry;
EntryState(currSM.States[0]);
break; default:
break;
}
} function ExitState() {
if (DEBUG) DebugInfo("ExitState():" + currSM.currState.DisplayName);
if (currSM.currState != null) {
runFunction(currSM.currState.Exit, null);
}
} //函数注入点,编辑器中写的各种函数在此注入
//<!--FUNCDATA--> //举个例子
//Entry:SM_23717574ae2bff0c6e0984140518db_欢迎界面初始化_Entry_初始化_Entry:
function A_87846c21b09faea4a622f8331aa9a4(para){
var str;
try {
str = para.split(" ");
switch (str[0]) {
default:
break;
}
}catch(e){} SendMsg("Storyboard", "[NULL] Start 霓虹灯");
SendMsg("Storyboard", "[NULL] Start 进入");
SendMsg("Storyboard", "[NULL] Start 眨眼睛");
}
</script>
</body>
</html>
其他
程序逻辑编辑器界面
程序逻辑编辑器界面
HTML5支持WebSocket可以连接UppBox,自写的JS状态机引擎在调试模式下可以把与C#代码交互过程全部传输出来,还可以传输指令,方便调试。