一个优秀的交互设计往往会影响一个产品的命运。在设计这款调试器时,我一直在构思这款调试器该长什么样子。简单、好用是我设计的原则,于是在《跨平台PHP调试器设计及使用方法——立项》一文中,我给出了一个Demo。之后实现的效果也与之变化并不大。(转载请指明出于breaksoftware的csdn博客)

在《跨平台PHP调试器设计及使用方法——立项》一文中,我阐述了该款调试器将采用网页的形式提供交互操作。这样我们需要一个Python实现的Web框架。在这个大的开源的环境下,我们可以轻易找到一些优秀的库来辅助我们快速开发一些功能。比如之前我选择pydbgp库去和Xdebug进行通信,这样就规避了很多协议的底层实现工作。同样在Web框架这块,我决定使用比较轻量级bottle库。

bottle库的官方网址是http://www.bottlepy.org/。其文档可见http://www.bottlepy.org/docs/dev/。bottle库使用起来非常方便,在我们工程的phpdebugserver.py中,我们先引入bottle库

from bottle import route, run
from bottle import static_file
from bottle import template
from bottle import request, response, get, post

route是路由功能。当我们请求一个URL时,Web框架需要将URL中路径信息或者参数信息对应到一个处理逻辑中。比如我们对http://xxxx/cmd进行发送Post请求,则可以通过下面这种路由方式指定处理逻辑

@route('/cmd', method='POST')
def request_cmd_post():ParamValue = request.forms.get("ParamKey")

我们可以从request.forms里获取Post请求的参数,上例中就是获取请求中ParamKey对应的值。

static_file用于返回一个文件。作为一个提供Web服务的服务器,它上面可能保存了一些用户需要直接拉取的文件。最典型的一个例子就是网页中往往包含了一些JavaScript文件。这些文件就可以通过这个接口返回。比如

@route('/files/<filepath:path>')
def highlight_file(filepath):return static_file(filepath, root='views')

当浏览器中请求http://xxxx/files/window.js时,就会被路由到该函数。static_file方法传入的是第一个参数是相对路径,即“files/windows.js”,第二个参数传递的是相对目录对应的根目录。上例中我们传递的是views,则返回的文件是在当前路径下的views/files/windows.js。

template是bottle提供的模板功能。当我们提供一些网页时,其大体的框架是相同的,改变的是其内容实体。比如我们展现编辑已保存的请求的界面时

@route("/request", method='get')
def request_action():
……elif "edit_data" == action:data = rdb.get_request(param_de)return template('component/edit_request', data = json.dumps(data), name = param_en)

其窗体格式样式一样,而内容我们可以通过上述template方法传入

我基本也就使用了上述几个bottle的功能。在发布工程时,把bottle.py这个文件放在工程中即可。

有了Web服务器,我们还需要一个界面框架。这么大一个工具,我们可以想象界面上需要类似桌面系统中软件界面的相关控件,比如ViewList等。当然这些东西也不会是我们从头开始实现,我们还是采用拿来主义。这次我选择的是Jquery EasyUI库。

选择Jquery EasyUI前,我先翻看了下它的支持控件和相关文档。我可以预见的控件基本都在这个框架中被找到,而且其样例和文档也非常丰富。最终我决定选择它作为我们UI框架。Jquery EasyUI的官网主页是http://www.jeasyui.com/;Demo的地址是http://www.jeasyui.com/demo/main/index.php;相关文档的地址是http://www.jeasyui.com/documentation/index.php。
        我们先看下网页文件的组织结构

frame.tpl是我们主界面的描述文件,实际它的内容比较空,它只是包含了若干JavaScript文件、样式文件以及界面的组成模板的路径。

<html><head><link rel="stylesheet" type="text/css" href="/files/third/jquery-easyui-1.4.5/themes/default/easyui.css"><link rel="stylesheet" type="text/css" href="/files/third/jquery-easyui-1.4.5/themes/icon.css"><link rel="stylesheet" href="/files/third/highlight/styles/tomorrow-night-eighties.css"></link><link rel="stylesheet" type="text/css" href="/files/themes/debugger.css"><script src="/files/third/jquery3_1/jquery-3.1.0.js" type="text/javascript"></script><script type="text/javascript" src="/files/js/edit_request.js"></script><script type="text/javascript" src="/files/js/debug_setting.js"></script><script type="text/javascript" src="/files/js/tools.js"></script><script type="text/javascript" src="/files/js/modify_variable.js"></script><script type="text/javascript" src="/files/js/request.js"></script><script type="text/javascript" src="/files/js/status.js"></script><script type="text/javascript" src="/files/js/console.js"></script><script type="text/javascript" src="/files/js/variables.js"></script><script type="text/javascript" src="/files/js/call_stack.js"></script><script type="text/javascript" src="/files/js/breakpoint.js"></script><script type="text/javascript" src="/files/js/files_watch.js"></script><script type="text/javascript" src="/files/js/variables_watch.js"></script><script type="text/javascript" src="/files/js/debug.js"></script><script type="text/javascript" src="/files/js/files_tree.js"></script><script type="text/javascript" src="/files/js/view.js"></script><script type="text/javascript" src="/files/third/jquery-easyui-1.4.5/jquery.min.js"></script><script type="text/javascript" src="/files/third/jquery-easyui-1.4.5/jquery.easyui.min.js"></script><script src="/files/third/jquery_timer/jquery.timer.js"></script><script src="/files/third/highlight/highlight.pack.js"></script><script src="/files/third/json-viewer/jquery.json-viewer.js"></script><script>hljs.initHighlightingOnLoad();</script><script src="/files/third/jquery_base64_js/jquery.base64.js"></script><script type="text/javascript">//$.ajaxSetup({//    async: false,//    cache:false//});$.ajaxSetup({timeout : 10000});function base64_decode(data) {return $.base64.atob(data, true);}function base64_encode(data) {return $.base64.btoa(data);}function highlight_code(type, source) {return hljs.highlight(type, source);}</script><title>Cmd Shell</title></head><body><div id="php_debugger" class="easyui-window" title="Php Debugger" data-options="iconCls:'icon-sum',footer:'#ft'" style="width:100%;height:900px;min-width:900px;min-height:800px;padding:0px;top:0;"><div class="easyui-layout" style="width:100%;height:100%;min-width:800px;padding:0px;">%include('top_menu_layout.tpl')%include('botton_tab_layout.tpl')             %include('folder_layout.tpl')%include('source_layout.tpl')%include('request_layout.tpl')        </div></div><div id="ft" style="padding:5px;">OFF</div>%include('component/file_menu.tpl')%include('component/console_dlg.tpl')%include('component/add_folder_dlg.tpl')%include('component/edit_request_dlg.tpl')%include('component/save_request_dlg.tpl')%include('component/variables_show_dlg.tpl')%include('component/add_files_watch_dlg.tpl')%include('component/modify_variable_dlg.tpl')%include('component/breakpoint_add_dialog.tpl')%include('component/add_variables_watch_dlg.tpl')%include('component/setting_dlg.tpl')</body>
</html>

上述简单的描述,便可以组织出下图的界面。这种模板组织方式还是非常方便使用的。


        除了上述几个大的模板,还有代码中列出的小的模板文件。这些文件一般是一些弹窗界面描述,以console_dlg.tpl为例,它是

由于该调试器界面元素非常多,我也不可能在一篇博文中将所有实现和细节讲完。但是为了契合该博文的标题,我就以上图界面为例,讲解下该界面的实现和工作原理。

我们先看下界面描述内容

<div id="console_dlg" class="easyui-dialog" title="Debug Console" style="width:900px;height:800px;padding:10px" data-options="iconCls:'icon-search',resizable:true,modal:true" closed="true"><div id="console_dlg_div" style="width:100%;height:100%;"><div data-options="region:'center'" style="width:100%;"><div style="margin:0px 0;width:100%;height:100%"><textarea id="console_dlg_view" style="width:100%;height:100%;" readonly="true"></textarea></div></div><div data-options="region:'south'" style="height:26px;width:100%;"><div style="margin:0px 0;width:100%;height:100%"><input id="console_dlg_cmd" class="easyui-textbox" data-options="multiline:false" value="" style="width:100%;height:100%"></div></div></div>
</div>

该界面的ID是console_dlg。它是该窗体的唯一标示,我们在主界面中点下Console按钮后,执行下面Javascript以打开该窗口

function console_dlg_open() {$('#console_dlg').dialog('open').dialog('center');
}

console_dlg的控件类型是其class描述的easyui-dialog。它可以通过东、南、西、北、中五个模块去组合。我们主界面就是通过这五个模块组合的。而console_dlg窗口只使用了中、南两个模块。位于中间的这个模块是调试窗口的输出内容的载体,其核心是一个ID为console_dlg_view的textarea控件。当我们在调试窗口输入调试指令后,它会显示在该区域中,然后该指令的结果也会显示在该区域。这些都是通过下面这个函数实现的

function append_debug_view(text) {var new_text = $('#console_dlg_view')[0].value + "\n" +  text;$('#console_dlg_view')[0].value = new_text;var scrollTop = $("#console_dlg_view")[0].scrollHeight ;  $("#console_dlg_view").scrollTop(scrollTop);
}

为了更加人性化,第4、5两行实现了滚动条的自动更新。

位于南部的是一个输入框,它的ID是console_dlg_cmd。我们在这个输入框中输入命令,按回车使得命令执行并清除该输入框内容。还可以按上下键翻看前后的历史命令记录。这块内容我们放在窗口初始化后的执行事件中。

var console_cmd_list = [];
var console_cmd_list_index = 0;$(document).ready(function(){$('#console_dlg_cmd').textbox('textbox').bind('keydown', function(e){if (e.keyCode == 13){ // when press ENTER key, accept the inputed value.var cmd = $(this).val();console_cmd_list.push(cmd);  console_cmd_list_index = console_cmd_list.length;excute_console_cmd(cmd);$('#console_dlg_cmd').textbox('setValue', '');} else if (e.keyCode == 38){if (console_cmd_list_index > 0) {console_cmd_list_index = console_cmd_list_index -1;var cmd = console_cmd_list[console_cmd_list_index];$('#console_dlg_cmd').textbox('setValue', cmd);}} else if (e.keyCode == 40){if (console_cmd_list_index < console_cmd_list.length - 1) {console_cmd_list_index = console_cmd_list_index +1;var cmd = console_cmd_list[console_cmd_list_index];$('#console_dlg_cmd').textbox('setValue', cmd);}}});$('#console_dlg_div').layout();
});

第10行的excute_console_cmd方法才是界面和我们调试器核心交互的地方。我们看下它的实现

function excute_console_cmd(cmd) {var param = '{"cmd":"' + base64_encode(cmd) + '"}';var param_en = base64_encode(param);$.post("do", {"action":"query", "param":param_en},function(data){append_debug_view(cmd);append_debug_view(eval(data));console.log(data);}, "");
}

第二行我们先组织一个Json型的字符串指令,然后第三行对其进行Base64编码。第四行我们向服务器请求一个路径为do的请求,其参数分为action和param两个部分。在Python的Web服务器层,我们通过Bottle框架的如下代码实现对该请求的响应

@route('/do', method='POST')
def request_do_post():action = request.forms.get("action")if None == action or len(action) == 0:return template('index')param = request.forms.get("param")global debugger(ret,type) = debugger.do(action, param);if type == "json":return json.dumps(ret)else:return template('index', **request.forms)

服务器返回结果后,我们便可以通过append_debug_view方法将内容显示到界面中了。
        除了一般的界面,我们还有个非常重要的控件——highlight。它负责将源码文件进行渲染,否则网页中打开的代码可能就是文本文件风格,非常不友好。它的地址是https://highlightjs.org/。目前这个控件还不支持显示行号,所以这块我们做了特殊处理。具体的处理方法可以参见之后公开的源码。

跨平台PHP调试器设计及使用方法——界面设计和实现相关推荐

  1. 跨平台PHP调试器设计及使用方法——拾遗

    之前七篇博文讲解了跨平台PHP调试器从立项到实现的整个过程,并讲解了其使用方法.但是它们并不能全部涵盖所有重要内容,所以新开一片博文,用来讲述其中一些杂项.(转载请指明出于breaksoftware的 ...

  2. 跨平台PHP调试器设计及使用方法——高阶封装

    在<跨平台PHP调试器设计及使用方法--协议解析>一文中介绍了如何将pydbgp返回的数据转换成我们需要的数据.我们使用该问中的接口已经可以构建一个简单的调试器.但是由于pydbgp存在的 ...

  3. 跨平台PHP调试器设计及使用方法——协议解析

    在<跨平台PHP调试器设计及使用方法--探索和设计>一文中,我介绍了将使用pydbgp作为和Xdebug的通信库,并让pydbgp以(孙)子进程的方式存在.<跨平台PHP调试器设计及 ...

  4. 跨平台PHP调试器设计及使用方法——通信

    首先引用<跨平台PHP调试器设计及使用方法--探索和设计>中的结构图(转载请指明出于breaksoftware的csdn博客) 本文要介绍的是我们逻辑和pydbgp通信的实现(图中红框内内 ...

  5. 跨平台PHP调试器设计及使用方法——探索和设计

    在<跨平台PHP调试器设计及使用方法--立项>一文中,我确定了使用xdebug作为调试器插件部分的基础组件.xdebug提供了一个远程调试的功能(相关资料可以详见https://xdebu ...

  6. UI设计/GUI开发-入门界面设计

    概述 从几个概念谈起,GUI(图形用户界面).UI开发.前端.Web开发,这些个耳熟的词语,是否想过它们正真的含义!一款使用体验极好的软件,好看.好用,必然不是凭空想出来的,而是有理论依据.多方配合设 ...

  7. 跨平台PHP调试器设计及使用方法——使用

    经过之前六篇博文的分析和介绍,大家应该对这套调试器有个初步的认识.本文我将讲解它的使用方法.(转载请指明出于breaksoftware的csdn博客) 上图是该软件界面的布局,我们之后的讲解也将围绕着 ...

  8. 跨平台PHP调试器设计及使用方法——立项

    作为一个闲不住且希望一直能挑战自己的人,我总是在琢磨能做点什么.自从今年初开始接触PHP,我也总想能在这个领域内产生点贡献.那能做点什么呢?我经常看到很多phper说自己设计了一个什么框架,或者说自己 ...

  9. 【MPC5744P】劳特巴赫调试器Trace32的使用方法

    对于大部分MCU来说,官方IDE一般都带有调试功能,配合JTAG接口使用即可,也支持变量查看.断点等功能.绝大多数工业开发的中小型程序,使用自带调试器即可,价格便宜,几十到几千不等. 在某些特殊领域( ...

最新文章

  1. 美国物流管理协会更名标志全球物流进入供应链时代
  2. 18、Power Query-SQL筛选
  3. 怎么给当前点击的a标签添加一个样式(跳转页面后)
  4. Windows Server 2012系列之一安装初体验
  5. [css] 你是怎样对css文件进行压缩合并的?
  6. 2017.5.15 项链工厂 思考记录
  7. Toast-Android 专属浮动小提示
  8. 一个老程序员的心里话---想创业的技术人员可以看
  9. 带你入门Java网络爬虫
  10. 架构师日常-技术or业务
  11. 从删库到跑路,论运维的自我修养
  12. oracle加密传输的种子,oracle net manager 数据传输安全步骤详解
  13. BZOJ1135: [POI2009]Lyz
  14. 手游公司运维之初识MongoDB
  15. android studio编程时出现的错误:Cannot get property 'XXXX' on extra properties extension as it does not exis
  16. 【存储】RAID0、RAID1、RAID3、RAID5、RAID6、混合RAID10、混合RAID50
  17. 中国平安真牛,把中国人寿给替了!!!!
  18. 浅谈spring之IoC控制反转
  19. 2022腾讯云年终双十一活动攻略汇总!
  20. Linux下搭建SFTP服务器

热门文章

  1. 老码农绝密:使用 TS(TypeScript) 的 10 大理由
  2. Java多线程,Thread,Runnable,Callable Task,Future<Task>,CompletionService
  3. 利用最小二乘法求解仿射变换参数
  4. 如何优雅地保留两位有效数字,又规避末尾出现多余的“0”?
  5. lua中正则表达式的坑
  6. 从paxos到raft zab,为何raft能够“独领风骚”
  7. 修改Ubuntu的启动logo
  8. MongoDB安装和MongoChef可视化管理工具的使用
  9. scp遇到路径中有空格
  10. C++入门经典-例6.14-通过指针连接两个字符数组