1、Web页面中的树形菜单的功能实现
利用树形菜单可以实现层次化的命令集,方便导航用户的操作。目前在 Web页面中可以采用许多不同的技术实现树形菜单的应用效果——如基于Ajax技术实现Web树状菜单、采用XML+JavaScript脚本创建树形菜单或者利用CSS(Cascading Style Sheets,层叠样式表)实现静态效果的树形菜单等。
DHTMLXTree是Web开发中运用较多的一个开源的树型菜单控件,允许开发人员快速开发Web界面中的树形菜单,它基于Ajax的JavaScript脚本库,允许在线编辑、拖拽、三种状态(全选、不选、半选)、复选框等模式。在加载大数据量的时候,仍然可以保持非常高效的速度。
DHTMLXTree本质上其实是一个在Web页面上实现树状显示效果的JavaScript控件,因此应用它不仅可以实现树型菜单,也可以实现树型目录等"树"状结构的数据显示效果。如下示图为应用DHTMLXTree控件实现的一个树形结构的数据显示效果的应用示例局部截图。
而如下示图为DHTMLXTree官方网站页面提供的有关技术文档局部截图,读者可以根据待开发的软件应用系统项目的需要下载相关的系统库文件和在线浏览技术参考文档资料。
在示例项目银行账户信息管理系统的后台页面的设计和实现中,也采用了DHTMLXTree树型控件实现应用系统中的后台管理功能的树形菜单,请见下图所示页面效果局部截图。
2、Web页面数据的分页显示功能实现
由于在软件应用系统中某个功能的页面需要显示大量的数据,为了方便应用系统的操作者对数据的快速浏览,应该要对待显示的目标数据进行分页显示。Web应用系统中数据分页显示技术也是Web表示层开发中比较通用的功能实现要求——所谓的数据分页显示功能要求,也就是将从软件应用系统后台数据库表中获得的各种结果数据人为地分成特定长度的数据块、并在Web页面中只显示其中某一块的数据,并为操作者提供继续浏览其它块数据的链接或者按钮(包括前、后页、第一和最后页、快速定位某页等方式)。
下图所示为示例项目银行账户信息管理系统中对用户账户信息进行数据分页显示的效果的局部截图,该分页实现技术是每次只向数据库系统请求页面大小的数据,大大地提高了每次前/后翻页时的查询速度。
当然,在具体实现时一定要达到将软件应用系统的表示层数据显示与数据的业务逻辑处理相互分离的设计和开发实现的基本目标。
3、解决Web表单重复提交的问题以保证数据的唯一性
由于各个Web浏览器都提供有【刷新】功能菜单——请见下图所示,为了防止操作者点击【刷新】功能菜单而造成对软件应用系统后台的多次重复请求,在软件应用系统的表示层开发中应该要避免产生表单重复提交的问题。
对于解决表单重复提交的技术问题,许多基于MVC体系架构的应用框架都提供了对应的技术支持和具体的实现方法。早期的Struts框架是通过提供Token(令牌)机制很好地解决了表单重复提交的问题——其基本的实现原理是:
Struts框架的服务器端程Action类程序在处理到达的请求之前,会将请求中包含的令牌值与保存在当前用户会话中的令牌值进行比较,看是否匹配;而在处理完该次请求后,并且在将响应发送给客户端浏览器之前,将会产生一个新的令牌(一个随机的字符串),该令牌除传给客户端浏览器以外,也会将用户Session会话中保存的旧的令牌进行替换。
此时如果用户回退到刚才的提交页面并再次重复地提交请求时,客户端Web浏览器再次传递过来的令牌字符串就和服务器端保存的令牌字符串内容也就不一致,从而有效地防止了Web表单重复提交的发生。
而在Struts2应用框架中利用系统内带的<s:token />标签产生一个GUID(Globally Unique Identifier,全局唯一标识符)字符串值的隐藏输入框,同时还将GUID字符串放到Session会话中;在执行某个Web请求之前,Struts2应用框架中的缺省的"token"拦截器将会话中的令牌字符串与此时的Web请求令牌字符串比较,如果两者相同,则将会话中的令牌字符串删除并继续往下执行,否则向actionErrors程序类加入相关的错误信息。
如此一来,如果用户通过某种手段提交了两次相同的请求,两个令牌字符串就会不同。从而也有效地防止了Web表单重复提交的发生。
在示例项目银行账户信息管理系统中为了不依赖于Struts2等MVC框架系统,没有简单地采用这些MVC应用框架系统对Web表单重复提交的功能实现的技术支持。而是采用Web页面表单中的图形验证码进行限制,同样也能够达到相同的应用效果。
由于图形验证码在每次不同的Web请求时都会产生不同的值,从而控制重复提交的行为产生。在示例项目中的"开户"功能成功后,用户如果再次点击Web浏览器中的"回退"按钮后,将回到原来的Web表单请求页面。但此时Web页面表单中的验证码已经不一致,请见下图所示的功能操作状态图示,此时提交后,软件应用系统后台的验证码验证程序将报告错误而终止重复的Web请求。
但如果用户在Web表单中输入正确的验证码后再发送本次请求,软件应用系统后台将通过业务数据中的逻辑性和数据的一致性加以限制,而避免在物理数据库系统的数据库表中出现完全一样的两条记录数据。
4、Web表单中的数据有效性验证
软件应用系统的操作者可能由于操作的失误或者恶意的攻击或者无意识的错误数据输入等行为,在Web页面表单中将产生错误的输入数据。为了保证在软件应用系统后台能够正确地获得所需要的目标数据,应该要对Web表单中的各种关键性的输入数据进行数据验证(数据有效性验证)。
对于Web表单中提交的数据不仅需要应用Web客户端的数据验证(一般是采用JavaScript脚本语言实现),更应该要应用Web服务器端数据验证技术以真正达到软件应用系统的数据安全性、正确性和有效性等应用的要求。
这主要是由于目前有许多浏览器能够禁止执行Web客户端页面中所内嵌的JavaScript脚本语言程序代码而导致Web客户端的数据验证功能失效、或者恶意的攻击者直接通过Web客户端应用程序请求访问软件应用系统中的后台Web服务器端相关程序。下图所示为FireFox浏览器对Web页面中的JavaScript脚本语言的控制项目的局部截图。
在示例项目银行账户信息管理系统中应用Apache Validator验证器框架实现Web服务器端的数据验证,由于Apache Validator验证器框架是Jakarta的公共项目的一部分,读者可以从Apache Validator验证器官方网站中下载Validator框架的系统包文件。如下示图为Apache Validator验证器官方网站中技术特性介绍、系统库文件下载等信息页面局部截图。
下图所示为在示例项目银行账户信息管理系统中的AccountInfoManageAction类的checkVerifyCodeOK方法中应用Validator框架的代码片段局部截图。
5、统计软件应用系统的在线人数
Web应用系统的在线人数实时反映软件应用系统的访问状态,如果能够显示当前软件应用系统的在线人数,一方面能够让软件应用系统的管理人员及时地了解软件应用系统的当前负载情况,另一方面也让访问者了解到本软件应用系统的热门程度。
但要想实时准确地统计Web应用系统的在线人数,其实也是一件不太现实的事情。这是因为HTTP协议本身是无状态的协议,当Web客户端程序(一般为Web浏览器程序)向Web服务器发出一个Web请求时,Web服务器会马上建立一个新的TCP/IP连接(也就是会话Session);在该Web页面完全载入后,这个会话连接也就关闭了;另外,正是由于HTTP协议是无状态的,所以也就无法知道某个Web请求访问是否已经离开或者Web请求者已经关闭了Web浏览器。
因此,Web应用中所谓的在线人数其实应该是指在"一定时间段内"同时访问Web站点的人数,而不是基于HTTP协议的并发连接数。但这个"一定时间段内"对于不同的Web服务器也是有差别的,如Tomcat服务器默认的Session会话超时为30分钟(但一般的Web网站都采用15分种的时间标准)。
在示例项目银行账户信息管理系统中也提供了该功能的具体实现,是利用实现HttpSessionListener接口的Web监听器统计在线Session个数、并将Tomcat服务器默认的Session会话超时时间改变为15分钟,最后在Web页面中显示输出本软件应用系统的在线人数。本示例项目最后实现的效果局部截图如图所示。
6、在软件应用系统中实现文件上传功能
文件上传或者下载也是Web应用系统中需要提供的功能,在示例项目银行账户信息管理系统中利用Apache Commons-FileUpload组件实现文件的上传功能,该FileUpload组件可以实现每次上传一个或多个文件,并可限制文件大小——比如在用户注册表单中允许注册者自行上传自己的图像文件,请见下图所示的局部截图。
7、在软件应用系统中实现文件下载功能
在Web应用系统的开发实现中,大部分的文件下载都是直接通过建立下载文件的URL文件链接方式直接进行下载。但是考虑到在Web应用中的文件下载具体实现时的盗链、跨服务器下载访问等方面的因素,直接文件流下载的方式也是必要的。因为,此时Web应用系统能够对下载的文件以及下载的操作者进行更多方面的安全及身份验证的检查和控制。
在Struts2应用框架中实现数据流下载时只需要改变Struts2应用框架中的result Type为Stream类型即可以实现本功能要求——请见下面的代码示例所示的某个实现文件下载功能的DownLoadFileAction的配置定义示例内容中的黑体标识的内容。如下为某个实现文件下载功能的DownLoadFileAction的配置定义示例
<action name="oneDownLoadGIFFile"
class="com.px1987.struts2.action.DownLoadFileAction">
<param name="inputPath">/downImages/logo.gif</param>
<result name="success" type="stream">
<param name="contentType">image/gif</param>
<param name="inputName">inputStream</param>
<param name="contentDisposition">filename="logo.gif"</param>
<param name="bufferSize">4096</param>
</result>
</action>
在示例项目银行账户信息管理系统中采用在Servlet程序中直接控制文件输出流的方式实现文件下载的控制。具体的功能实现代码请见如下所附的代码示例,该doDownLoadFile方法作为某个J2EE Servlet程序中一个功能方法被doGet方法所调用,并请注意其中的黑体部分的语句代码——实现文件下载功能的代码示例
public void doDownLoadFile(HttpServletRequest request, String downFileName,
HttpServletResponse response, String downFilePath)
throws ServletException, IOException{
ServletOutputStream servletOutputStream=null;
FileInputStream fileInputStream=null;
BufferedInputStream bufferedInputStream=null;
File downLoadTargetFile=new File(downFilePath+"/"+downFileName);
if(!downLoadTargetFile.exists()){
response.sendError(404,"没有找到要下载的目标文件!");
return;
}
try {
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment; filename=\"" + downFileName + "\"");
servletOutputStream=response.getOutputStream(); fileInputStream=new java.io.FileInputStream(downFilePath +
"\\"+ downFileName);
bufferedInputStream=new BufferedInputStream(fileInputStream);
byte[] oneBuffer=new byte[10240];
int byteNumber;
while ((byteNumber=bufferedInputStream.read(oneBuffer)) !=-1){
servletOutputStream.write(oneBuffer,0,byteNumber);
}
}
catch (FileNotFoundException e) {
}
catch (IOException e) {
}
finally {
if(bufferedInputStream !=null){
bufferedInputStream.close();
}
if(servletOutputStream !=null){
servletOutputStream.close();
}
}
}
其中的response.setContentType("application/octet-stream");语句主要是设置Http响应头和要下载保存的文件名,Web浏览器将根据开发人员在Http数据流的Head部分的MIME(Multipurpose Internet Mail Extensions,多用途互联网邮件扩展类型)头的设置内容"application/octet-stream"直接产生出文件下载的对话框提示、并且按照设置的内容采用二进制数据格式传输文件流数据。
DOM(Document Object Model)即文档对象模型。通过DOM树这样一种结构,不仅可以直观的看到HTML的整体结构,还可以利用DOM树的一些属性获取到某个元素的子节点和节点名称等信息。
HTML文档结构:
树形结构:
childNodes属性:获取当前节点的子节点。
<div id="box">
<p>第一个child节点</p>
<h4>第二个child节点</h4>
<div>第三个child节点</div>
</div>
<script>
let box=document.getElementById("box");
let boxChild=box.childNodes;
console.log(boxChild);
</script>
空格和换行也属于一个节点,用text表示。
for(let i=1; i < boxChild.length; i +=2)
console.log(boxChild[i]);
获取1、3、5……奇数节点。
nodeName属性:返回节点名称。
for(let i=1; i < boxChild.length; i +=2)
console.log(boxChild[i].nodeName);
appendChild(node):在子节点最后一位插入新节点,node为新节点的名称。
let newnode=document.createElement("p");
newnode.innerHTML="新节点";
box.appendChild(newnode);
console.log(boxChild);
removeChild(node):删除指定父级元素的某个子节点。
项目目标:点击删除按钮,依次删除列表中书籍。
btn.onclick=function(){
list.removeChild(list.childNodes[1]);
}
parentNode属性:返回指定节点的父节点。
<div id="box">
<p id="box-item1">第一个child节点</p>
<h4>第二个child节点</h4>
<div>第三个child节点</div>
</div>
<script>
let box_item1=document.getElementById("box-item1");
console.log(box_item1.parentNode);
</script>
项目目标:点击叉号删除内容。
x.onclick=function(){
document.body.removeChild(this.parentNode);
}
replaceChild(newnode,oldnode)方法:用新节点替换之前的节点。
<div id="box">
<p id="box-item1">第一个child节点</p>
<h4>第二个child节点</h4>
<div id="box-item3">第三个child节点</div>
</div>
<script>
let box_item1=document.getElementById("box-item1");
console.log(box_item1.parentNode);
let h1=document.createElement("h1");
h1.innerHTML="新节点 第三个child节点";
let box_item3=document.getElementById("box-item3");
let box=document.getElementById("box");
box.replaceChild(h1, box_item3);
</script>
insertBefore可以在已有的子节点前插入一个新的子节点。
项目目标:点击按钮,在ul标记子节点的第一位插入包含内容“我的世界”,文字颜色为红色的h4节点。
let btn=document.getElementById("button");
let game=document.getElementById("game");
btn.onclick=function() {
let newGame=document.createElement("h4");
newGame.innerHTML="我的世界";
newGame.style.color="red";
newGame.style.paddingLeft="40px";
game.insertBefore(newGame, game.firstChild);
}
setAttribute属性:添加指定的属性,并为其赋指定的值。
项目目标:点击“变”按钮,将输入框变为按钮。
let btn=document.getElementById("btn");
let input=document.getElementById("put");
btn.onclick=function() {
input.setAttribute("type", "button");
}
战wxPython系列-039
wxPythontigo提供了一些高级控件。例如,树形控件、HTML窗口、网格控件、列表控件、或具有高级样式功能的编辑器等。
一、wx.ListBox列表框
wx.ListBox列表框控件从一个字符串列表中选择一个或者多个字符串。所选字符串显示在一个可以滚动的列表框中,所选中的字符串将特别标记。列表框可以是单选 (如果选择了其中的一个项,则清除先前的选择项)或者多重选择(选择一个项的时,不影响对其他项的选择)。
列表框元素从0开始编号,虽然元素的最大数量是无限的,但通常最好使用虚拟控件,不需要一次性将所有项添加到其中。由于这个控件没有做优化,比如在wx.dataview.DataViewCtrl或者使用LC_VIRTUAL样式的wx.ListCtrl时,需要加载超过上百的项时,性能会有所影响。
注意,列表框不支持除制表符以外的控制字符。
wx.ListBox支持的窗口样式:
注意:LB_SINGLE, LB_MULTIPLE和LB_EXTENDED样式是互斥的,最多可以指定其中一个样式(单选是默认设置)。
wx.ListBox发出的事件:
wx.ListBox常用方法:
图1:wx.ListBox类继承关系
二、wx.ListBox演示
#列表框(wx.ListBox)
import wx
class SampleListBox(wx.Frame):
def __init__(self, *args, **kw):
super(SampleListBox, self).__init__(*args, **kw)
self.InitUi()
def InitUi(self):
#设置标题
self.SetTitle("实战wxPython: ListBox演示")
#设置窗口尺寸
self.SetSize(400, 240)
panel=wx.Panel(self)
#水平布局
hbox=wx.BoxSizer(wx.HORIZONTAL)
# 添加一个列表框
self.listbox=wx.ListBox(panel)
hbox.Add(self.listbox, wx.ID_ANY, wx.EXPAND | wx.ALL, 20)
# 按钮面板
btnPanel=wx.Panel(panel)
vbox=wx.BoxSizer(wx.VERTICAL)
newButon=wx.Button(btnPanel, wx.ID_ANY, "新建", size=(90, 30))
renButton=wx.Button(btnPanel, wx.ID_ANY, "重命名", size=(90, 30))
delButton=wx.Button(btnPanel, wx.ID_ANY, "删除", size=(90, 30))
clrButton=wx.Button(btnPanel, wx.ID_ANY, "清理", size=(90, 30))
newButon.Bind(wx.EVT_BUTTON, self.NewItem)
renButton.Bind(wx.EVT_BUTTON, self.OnRename)
delButton.Bind(wx.EVT_BUTTON, self.OnDelete)
clrButton.Bind(wx.EVT_BUTTON, self.OnClear)
self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnRename)
vbox.Add((-1, 20)) #距离顶端20像素
vbox.Add(newButon)
vbox.Add(renButton, 0, wx.TOP, 5)
vbox.Add(delButton, 0, wx.TOP, 5)
vbox.Add(clrButton, 0, wx.TOP, 5)
btnPanel.SetSizer(vbox)
hbox.Add(btnPanel, 0.6, wx.EXPAND | wx.RIGHT, 20)
panel.SetSizer(hbox)
self.Centre()
def NewItem(self, e):
text=wx.GetTextFromUser("输入一个新项", "插入对话框")
if text !="":
self.listbox.Append(text)
def OnRename(self, e):
sel=self.listbox.GetSelection()
text=self.listbox.GetString(sel)
renamed=wx.GetTextFromUser("项重命名", "重命名对话框", text)
if renamed !="":
self.listbox.Delete(sel)
item_id=self.listbox.Insert(renamed, sel)
self.listbox.SetSelection(item_id)
def OnDelete(self, e):
sel=self.listbox.GetSelection()
if sel !=-1:
self.listbox.Delete(sel)
def OnClear(self, e):
self.listbox.Clear()
def main():
app=wx.App()
sample=SampleListBox(None)
sample.Show()
app.MainLoop()
if __name__=="__main__":
main()
这个例子展示了如何从wx.ListBox中添加、修改和删除项。
self.listbox=wx.ListBox(panel)
hbox.Add(self.listbox, wx.ID_ANY, wx.EXPAND | wx.ALL, 20)
创建一个空的wx.ListBox。设置列表框边框距离20px。
self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnRename)
绑定wx. EVT_LISTBOX_DCLICK事件到方法OnRename(),这样,如果双击列表框中的特定元素,就会显示重命名对话框。
def NewItem(self, e):
text=wx.GetTextFromUser("输入一个新项", "插入对话框")
if text !="":
self.listbox.Append(text)
通过单击新建按钮调用NewItem()方法。在NuwItem方法中,使用包装器wx.GetTextFromUser()方法显示一个wx.TextEntryDialog。将对话框中输入的文本返回给文本变量。如果文本不是空的,我们用append()方法将它添加到列表框中。
if renamed !="":
self.listbox.Delete(sel)
item_id=self.listbox.Insert(renamed, sel)
self.listbox.SetSelection(item_id)
通过删除项并在同一位置插入新项来重命名项。将选择设置回已修改的项。
def OnDelete(self, e):
sel=self.listbox.GetSelection()
if sel !=-1:
self.listbox.Delete(sel)
要删除一个项,通过调用GetSelection()方法找到所选项的索引。然后使用delete()方法删除该项。
def OnClear(self, e):
self.listbox.Clear()
调用Clear()方法清除整个列表框。
图2:wx.ListBox演示
三、wx.CheckListBox复选列表框
wx.CheckListBox是wx.ListBox的派生类,继承了它的功能,它在每个选项上额外显示一个复选框。
wx.CheckListBox发出的事件:
wx.CkeckListBox常用方法:
图3:wx.CheckListBox类继承关系
将节二中的演示代码:
self.listbox=wx.ListBox(panel)
修改成
self.listbox=wx.CheckListBox(panel)
运行,就可以演示使用wx.CheckListBox,效果如图4:
图4:wx.CheckListBox演示
四、本文知识点
前一篇:wxPython - 状态栏StatusBar
欢迎关注,评论,收藏,点赞,和转发。
*请认真填写需求信息,我们会在24小时内与您取得联系。