制作背景
之前做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文件路径:
1Activity act = ActivityXamlServices.Load(path);
然后对活动树进行解析,活动树的类型主要处理类型:StateMachine、State(需要处理状态的Enter、Exit和Transition)、Literal`1(状态转移条件填写为True时)、CSharpValue`1(状态转移条件需要后续编程时)、WriteLine(普通预埋点)
状态转移条件 这一步的目的是把微软工作流的复杂对象进行整理,方便序列化给JavaScript使用,并对预埋的需要扩展JavaScript编程的地方创建JavaScript函数名(创建方法需要根据活动树生成,要保证修改工作流后原有的未更改部分生成名一致),并记录到函数表上。
整理后的活动树 -
JavaScript代码填写程序补完
这一部分没什么太多说的,把之前解析出来的JavaScript函数列出来,填写上即可。当时我还是使用DataSet比较多,所以函数表使用DataSet做的,请各位想象成喜欢的形式:
123456789void
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);
}
事件列表 -
JavaScript组装
上一步已经把JavaScript函数全都做完了,接下来把这些函数生成到最终的html文件中就好了。这里预先写一个模版文件,执行主体JavaScript状态机就在这个文件里面。
JavaScript的功底比较菜,没做封装,各位凑合看看吧:
1 <!DOCTYPE html> 2 3 <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> 4 <head> 5 <meta charset="utf-8" /> 6 <title></title> 7 <script type="text/javascript" src="jquery-1.8.2.min.js"></script> 8 <script type="text/javascript" src="core-min.js"></script> 9 <script type="text/javascript" src="enc-base64-min.js"></script> 10 <script type="text/javascript" src="json.js"></script> 11 <script type="text/javascript" src="UppApp.js"></script><!--UppBox通讯类库--> 12 <script type="text/javascript" src="Network.js"></script> 13 </head> 14 <body> 15 <script type="text/javascript"> 16 //省略UppBox应用层初始化 17 18 var DEBUG = false; 19 var NETDEBUG = false; 20 var SMEngine; 21 var currSM; 22 var SUSPEND = false; 23 24 //状态机数据注入点 25 //<!--JSONDATA--> 26 27 //状态用临时变量 28 var tempVar = (function () { 29 function tempVar() { } 30 return tempVar; 31 })(); 32 33 function MessageBus(para);//接收Win8Metro发来的信息,具体内容略function SendMsg(cmd, msg);//向Win8Metro发送信息,具体内容略 34 function DebugInfo(para);//调试信息传递,可通过控制台/UppBox/body的DOM节点 35 36 function InitApp() { 37 //网络状态,配置项等的初始化注入点 38 //<!--INITDATA--> 39 AppDemo.initFW();//UppBox初始化 40 SMEngine = eval(json);//json为上方注入的内容,是个Json结构对象 41 currSM = SMEngine; 42 if (DEBUG) DebugInfo("Init()" + currSM.DisplayName); 43 EntryState(currSM.States[0]); 44 try { 45 AppLoaded(); 46 } catch (e) { 47 } 48 } 49 50 function runFunction(func, para) { 51 if (func == null) return false; 52 switch (func.Type) { 53 case "Action": 54 var funName = func.Code; 55 try { 56 if (DEBUG) DebugInfo("runFunction():" + funName + " para:" + para); 57 var result = eval(funName + "(para);"); 58 return result; 59 } catch (e) { 60 return false; 61 } 62 break; 63 64 default: 65 break; 66 } 67 } 68 69 function TAction(action, entryState, para) { 70 if (DEBUG) DebugInfo("TAction:" + action.DisplayName + " " + entryState.DisplayName + " " + para); 71 try { 72 if (para != "[SUSPEND]") { 73 var result = runFunction(action, para); 74 if (result == false) { 75 SUSPEND = true; 76 setTimeout(function (action, entryState) { 77 TAction(action, entryState, "[SUSPEND]"); 78 }, 500, action, entryState); 79 } else { 80 EntryState(entryState); 81 } 82 } 83 else { 84 if (SUSPEND == false) { 85 EntryState(entryState); 86 } else { 87 setTimeout(function (action, entryState) { 88 TAction(action, entryState, "[SUSPEND]"); 89 }, 500, action, entryState); 90 } 91 } 92 } catch (e) { 93 } 94 } 95 96 function Trigger(para) { 97 if (SUSPEND) { 98 if (DEBUG) DebugInfo("SUSPEND():" + para); 99 otherFun(para); 100 return; 101 } 102 for (var i = 0; i < currSM.currState.Transitions.length; i++) { 103 var result = runFunction(currSM.currState.Transitions[i].Trigger, para); 104 if (result == true) { 105 var result = runFunction(currSM.currState.Transitions[i].Condition, para); 106 if (result == true) { 107 var stateName = currSM.currState.Transitions[i].To; 108 for (var j = 0; j < currSM.States.length; j++) { 109 var sns = currSM.States[j].DisplayName.split("_"); 110 var CurrName = sns[sns.length - 1]; 111 if (CurrName == stateName) { 112 ExitState(); 113 TAction(currSM.currState.Transitions[i].Action, currSM.States[j], para); 114 return; 115 } 116 } 117 return; 118 } 119 } 120 } 121 var result = runFunction(currSM.currState.Main, para); 122 if (result != true) { 123 if (DEBUG) DebugInfo("otherFun(): para:" + para); 124 otherFun(para); 125 } 126 } 127 128 function otherFun(para) { 129 try { 130 var str = ""; 131 var msg = ""; 132 try { 133 str = para.split(" "); 134 if (str.length > 1) msg = str[1]; 135 for (var i = 2; i < str.length; i++) { 136 msg += " " + str[i]; 137 } 138 eval(str[0] + "(msg)"); 139 } catch (e) { 140 str = para; 141 eval(str + "()"); 142 } 143 } catch (e) { 144 } 145 } 146 147 function EntryState(state) { 148 if (DEBUG) { 149 var debug = "EntryState():" + state.DisplayName; 150 if (currSM.currState != null) debug += " CurrState:" + currSM.currState.DisplayName; 151 DebugInfo(debug); 152 } 153 switch (state.Entry.Type) { 154 case "Action": 155 if (!state.IsFinal) { 156 currSM.currState = state; 157 } else { 158 while (true) { 159 if (currSM.preSM == null) break; 160 currSM = currSM.preSM; 161 if (!currSM.currState.IsFinal) break; 162 } 163 } 164 runFunction(state.Entry, null); 165 Trigger(""); 166 break; 167 168 case "StateMachine": 169 currSM.currState = state; 170 state.Entry.preSM = currSM; 171 currSM = state.Entry; 172 EntryState(currSM.States[0]); 173 break; 174 175 default: 176 break; 177 } 178 } 179 180 function ExitState() { 181 if (DEBUG) DebugInfo("ExitState():" + currSM.currState.DisplayName); 182 if (currSM.currState != null) { 183 runFunction(currSM.currState.Exit, null); 184 } 185 } 186 187 //函数注入点,编辑器中写的各种函数在此注入 188 //<!--FUNCDATA--> 189 190 //举个例子 191 //Entry:SM_23717574ae2bff0c6e0984140518db_欢迎界面初始化_Entry_初始化_Entry: 192 function A_87846c21b09faea4a622f8331aa9a4(para){ 193 var str; 194 try { 195 str = para.split(" "); 196 switch (str[0]) { 197 default: 198 break; 199 } 200 }catch(e){} 201 202 SendMsg("Storyboard", "[NULL] Start 霓虹灯"); 203 SendMsg("Storyboard", "[NULL] Start 进入"); 204 SendMsg("Storyboard", "[NULL] Start 眨眼睛"); 205 } 206 </script> 207 </body> 208 </html>
其他
HTML5支持WebSocket可以连接UppBox,自写的JS状态机引擎在调试模式下可以把与C#代码交互过程全部传输出来,还可以传输指令,方便调试。