C# 和 AJAX 白板






4.71/5 (13投票s)
这是一个基于 Web 的白板。它使用 C# 和 AJAX 在服务器和客户端之间进行通信。通过 AJAX 实现不同用户之间的数据共享。绘画内容可以实时地在多个客户端之间共享。
引言
这是一个基于 AJAX 的白板应用程序。通常,与桌面应用程序不同,Web 应用程序需要设计以优化服务器资源。这是 AJAX 流行的一个原因。在这里,我演示了 AJAX 的一个强大用途,即实现两个或多个客户端之间的通信。
那么,它能做什么?正如我所说,这是一个白板应用程序。用户可以使用提供的绘图工具、颜色选择等。基本思想是将用户绘制的内容共享给所有客户端。所有查看主页的用户都可以参与绘图并与其他用户共享。还可以清除本地画布、服务器数据等。
背景
几年前,我被分配了研究 AJAX 功能的任务。作为技术爱好者,我决心说服客户使用当时**新颖且热门**的技术。我花了几天时间思考设计和 GUI 的正确方法。我想让应用程序看起来像桌面应用程序,但又能在浏览器中运行。我的目标是完全避免回发(以给客户留下深刻印象)。我搜索了 Google 上的所有结果,阅读了所有关于 AJAX 的文章,以便获得完美的演示应用程序。
我偶然看到了 Walter Zorn 的 JavaScript 代码 (wz_jsgraphics.js) 链接。那时,我萌生了构建一个基于 Web 的白板的想法。我将所有功劳归于 Walter 出色的 JavaScript 代码。一旦我学会使用他的脚本,我的基于 Web 的白板很快就上线运行了。
基础知识...
与所有 AJAX 应用程序一样,我有一个向用户显示的主页面。服务器上的一个页面处理该页面的所有请求和响应。所有的绘图部分都使用 Walter 的 JavaScript 代码完成。我设计了一个消息机制来识别客户端请求并相应地服务客户端。我根据功能将 JavaScript 分为不同的文件。理解应用程序流程非常简单。
请注意,在任何时候,**客户端都会发起数据请求**。服务器仅作为存储数据的中心(使用 `ArrayList`)。客户端发送一个数字,指示其请求数据的起始位置。
Using the Code
您可以在 Walter 的网站上找到图形库的详细说明:Walter's website。
我将从简要解释每个文件的内容开始。
JavaScript 文件
- ajaxfunctions.js - 包含发送和接收服务器数据的函数。它还包含在从服务器获取数据后更新屏幕画布的 JavaScript 函数。选择工具和颜色、发送请求清除服务器数据等的函数也放置在此文件中。
- ajaxobjects.js - 包含创建 AJAX 对象和发送 `XMLHTTP` 请求的函数。
- common.js - 包含我認為會佔用主頁面的函數。主要是 1-2 行的重用函數。
- constants.js - 包含应用程序使用的所有客户端常量。分类为颜色、工具、查询字符串等。
- drawing.js - 包含将处理绘图并与服务器建立联系的函数。
- wz_jsgraphics.js - 当然,这是最重要的绘图库,没有它,此应用程序就是个空壳。它包含所有实际在客户端画布上绘制形状的函数。
ASP.NET 文件 (服务器端 C#)
- Constants.cs - 包含所有服务器端常量。
- HandleData.aspx - 这是所有 AJAX 请求都发布到的文件。它有函数来决定客户端想要什么,然后用用户数据进行响应。
- index.aspx - 这是包含所有控件的主窗体。它只是一个服务用户。该页面作为 GUI,初始化共享过程。
总而言之,这是一个简单的解释
- 用户“A”在他的屏幕上加载“index.aspx”页面。
- 他选择了一个工具和颜色,并在屏幕上开始设计。
- 另一个用户“B”访问“index”页面。
- 由于“B”是新用户,他从服务器获取所有数据,用户 A 的画布会在用户 B 的屏幕上复制。
- 现在,AJAX 进入了图片。为了让两个用户保持同步,AJAX 脚本会定期运行。
- 当用户更改所选工具、颜色或在屏幕上绘图时,AJAX 脚本会更新服务器。
- 这个步骤会与任意数量的用户重复。
- 我认为,理想情况下,在 LAN/WAN 环境中,可以轻松容纳 15 个用户。
现在,我们开始详细解释一些重要的客户端函数。
//Function to set the drawing data on the server
function setData() {
var requestUrl;
//Get the url path
requestUrl = CONST_URL_PATH;
requestUrl += getToolString();
xmlHttp = GetXmlHttpObject(setDataHandler);
xmlHttp_Send_Request(CONST_METHOD_POST, xmlHttp, requestUrl);
}//End of setData
//Function to get the drawing data from server
function getData() {
var requestUrl;
//Get the url path
requestUrl = CONST_URL_PATH;
requestUrl += CONST_QUERY_PARAM + CONST_QUERY_PARAM_GETDATA;
requestUrl += '&' + CONST_QUERY_READ_FROM + getValue('ReadFrom');
xmlHttp = GetXmlHttpObject(getDataHandler);
xmlHttp_Send_Request(CONST_METHOD_POST, xmlHttp, requestUrl);
}//End of setData
//Function to get the query string for the current tool
function getToolString() {
var strOut;
strOut = CONST_QUERY_PARAM + CONST_QUERY_PARAM_SETDATA;
strOut += '&' + CONST_QUERY_ACTION + CurrentTool;
strOut += '&' + CONST_QUERY_COLOR + CurrentColor;
strOut += '&' + CONST_QUERY_STROKE + CurrentStroke;
strOut += '&' + CONST_QUERY_STARTX + StartX;
strOut += '&' + CONST_QUERY_STARTY + StartY;
strOut += '&' + CONST_QUERY_EndX + EndX;
strOut += '&' + CONST_QUERY_EndY + EndY;
return strOut;
} //End of getToolString
在联系服务器之前,`setData` 函数会调用 `getToolString` 函数。`getToolString` 函数以所需格式构造一个字符串以发送到服务器。然后,`setData` 函数使用 AJAX 对象,并将数据字符串发送到服务器。`getData` 函数创建一个请求字符串,并再次使用 AJAX 对象从服务器请求数据。
**请注意**,在这两个函数中,回调数据处理程序都不同。(数据处理程序是一个函数,它决定如何处理从服务器返回的数据。)
//Function to handle the callback for the set data ajax function
//THIS HANDLER IS USED FOR TWO AJAX FUNCTIONS
function setDataHandler() {
//readyState of 4 or 'complete' represents that data has been returned
//Check the state of the page
if (xmlHttp.readyState == CONST_INT_READY_STATE ||
xmlHttp.readyState == CONST_READY_STATE){
//Get the response text
var strText = xmlHttp.responseText;
//Check if error occured
if (strText.indexOf(CONST_ERROR) >= 0) {
//Error occured
errHandler(strText.split(CONST_INTERNAL_SEPERATOR)[1]);
} else {
//Reset the status bar text msg for success
errHandler(CONST_MSG_DATASET);
//Change the value in the hidden so that next
//read starts after the
//current value
setValue('ReadFrom', parseInt(getValue('ReadFrom')) + 1);
}//End of checking if error occured
//Toggle the waiting div
toggleLoading(false);
} //End of checking if the script is over
} //setDataHandler
`setDataHandler` 函数首先检查浏览器是否处于就绪状态。然后,它读取 AJAX 对象返回的 `ResponseText`,检查是否有任何错误,然后相应地重置状态栏消息。最后,它还设置从服务器收到的当前指令编号。
//Function to handle the call back of the get data ajax function
function getDataHandler() {
//readyState of 4 or 'complete' represents that data has been returned
//Check the state of the page
if (xmlHttp.readyState == CONST_INT_READY_STATE ||
xmlHttp.readyState == CONST_READY_STATE){
//Get the response text
var strText = xmlHttp.responseText;
//Check if error occured
if (strText.indexOf(CONST_ERROR) >= 0) {
//Error occured
errHandler(strText.split(CONST_INTERNAL_SEPERATOR)[1]);
} else {
//Reset the status bar text msg for success
errHandler(CONST_MSG_DATAGET);
//Update the screen
var iNum = updateScreen(strText);
//Change the value in the hidden so that next
//read starts after the set value
setValue('ReadFrom', parseInt(getValue('ReadFrom')) + iNum);
}//End of checking if error occured
//Toggle the waiting div
toggleLoading(false);
//Set a call to the same function again after the specified time interval
window.setTimeout('getData()', CONST_DATA_TIMEOUT);
} //End of chekcing if the script is over
} //End of getDataHandler
`getDataHandler` 函数也检查浏览器是否处于就绪状态。然后,它读取 AJAX 对象返回的 `ResponseText`,检查是否有任何错误,并相应地重置状态栏消息。与 `setDataHandler` 函数不同,此函数首先更新屏幕(调用 `updateScreen` 函数),然后设置当前指令编号。
//function that will handle the data
//that is received from the getData function
function updateScreen(strText) {
//Split the text into lines
var strLines = strText.split(CONST_LINE_SEPERATOR);
var i;
//Check the length of the lines
if (strLines.length > 0) {
//Loop on the no of lines
for (i = 0; i < strLines.length; i++) {
//Get the individual properties by splitting each string
var strMainText = strLines[i].split(CONST_INTERNAL_SEPERATOR);
//Check if CLEAR IS CALLED
if (strMainText[0] == CONST_ACTION_TOOL_CLEAR) {
//CLEAR THE CANVAS
clearCanvas();
} else {
//Set all the variables before calling the draw function
setTool(strMainText[0]);
setColor(strMainText[1]);
setStroke(parseInt(strMainText[2]));
setStart(parseInt(strMainText[3]), parseInt(strMainText[4]));
setEnd(parseInt(strMainText[5]), parseInt(strMainText[6]));
//Now call the draw function
drawPic();
} //End of checking the call for CLEAR
} //End of looping on the no of lines
//Return the count so the next search starts after this number
return i;
} //End of checking the no of lines
return 0;
} //End of updateScreen
`updateScreen` 函数根据预定义的特殊字符拆分接收到的文本。第一次拆分实际上是指令的拆分。第二次拆分是指令间的拆分(以获取工具、颜色、起始点和终点等)。拆分数据后,它会在屏幕上绘制所需的图像(调用 `drawPic` 函数)。现在,让我们看看服务器端。服务器端由 `HandleData.aspx` 页面的加载事件处理。根据请求,会调用 `getData` 或 `setData` 函数。
//Function to get the data and set it to the page
private void getData()
{
string strReadFrom =
Request.QueryString.Get(Constants.CONST_QUERY_READ_FROM);
//Check the read value
if (strReadFrom != null && strReadFrom.Length > 0)
{
StringBuilder strText = new StringBuilder("");
int iFrom = Int32.Parse(strReadFrom);
//Get the array list from the application object
ArrayList arrData =
(ArrayList)Application[Constants.CONST_APP_DATA_ARRAY_NAME];
//Check if ifrom is equal to the length of the arraylist
//Means no new data to read
if (iFrom >= arrData.Count)
{
//Screen is already updated
//Write the error with the first word set to identify as error
Response.Write(Constants.CONST_ERROR +
Constants.CONST_INTERNAL_SEPERATOR +
Constants.CONST_ERROR_ALREADY_UPDATED);
return;
} //End of checking the length
//Add the first element
strText.Append(arrData[iFrom]);
//Do the process to create a response string
for (int i = iFrom + 1; i < arrData.Count; i++)
{
strText.Append(Constants.CONST_LINE_SEPERATOR + arrData[i]);
} //End of appending the data to the string builder
//Write the string builder to the page
Response.Write(strText.ToString());
}
else
{
//Write the error with the first word set to identify as error
Response.Write(Constants.CONST_ERROR +
Constants.CONST_INTERNAL_SEPERATOR +
Constants.CONST_ERROR_PARAM);
}//End of checking the read value
} //End of getData
此 `getData` 函数首先读取客户端请求数据的计数器。然后,它遍历存储指令的服务器端 `ArrayList`。在这里,它还会检查新数据的存在。如果存在新数据,则创建输出字符串,然后使用 `Response.Write` 语句返回。(这是返回给调用 AJAX 函数的字符串。)
//Function to set the data on the server
private void setData()
{
//Get the array list from the application object
ArrayList arrData = (ArrayList)Application
[Constants.CONST_APP_DATA_ARRAY_NAME];
//Get all the drawing values from the request querstring
string strAction = Request.QueryString.Get(Constants.CONST_QUERY_ACTION);
string strColor = Request.QueryString.Get(Constants.CONST_QUERY_COLOR);
string strStroke = Request.QueryString.Get(Constants.CONST_QUERY_STROKE);
string strStartX = Request.QueryString.Get(Constants.CONST_QUERY_STARTX);
string strStartY = Request.QueryString.Get(Constants.CONST_QUERY_STARTY);
string strEndX = Request.QueryString.Get(Constants.CONST_QUERY_ENDX);
string strEndY = Request.QueryString.Get(Constants.CONST_QUERY_ENDY);
if (strAction != null && strAction.Length > 0)
{
if (strAction.Equals(Constants.CONST_ACTION_TOOL_CLEAR))
{
arrData.Add(strAction + Constants.CONST_INTERNAL_SEPERATOR);
}
else
{
//Check if all are present
if (strColor != null && strStroke != null &&
strStartX != null && strStartY != null &&
strEndX != null && strEndY != null &&
strColor.Length > 0 && strStroke.Length > 0 &&
strStartX.Length > 0 && strStartY.Length > 0 &&
strEndX.Length > 0 && strEndY.Length > 0)
{
//Set the data in the application array
arrData.Add(strAction + Constants.CONST_INTERNAL_SEPERATOR +
strColor + Constants.CONST_INTERNAL_SEPERATOR +
strStroke + Constants.CONST_INTERNAL_SEPERATOR +
strStartX + Constants.CONST_INTERNAL_SEPERATOR +
strStartY + Constants.CONST_INTERNAL_SEPERATOR +
strEndX + Constants.CONST_INTERNAL_SEPERATOR +
strEndY);
//Write a blank string as an identification that no error occured
Response.Write("");
}
else
{
//Write the error with the first word set to identify as error
Response.Write(Constants.CONST_ERROR +
Constants.CONST_INTERNAL_SEPERATOR +
Constants.CONST_ERROR_PARAM);
}//End of Checking if all querystrings are valid
}
}
else
{
//Write the error with the first word set to identify as error
Response.Write(Constants.CONST_ERROR +
Constants.CONST_INTERNAL_SEPERATOR +
Constants.CONST_ERROR_PARAM);
}//Check if the action is not null
} //End of setData
`setData` 函数执行完全相反的任务。它获取请求中的所有参数,并从中创建一个字符串。然后,它将该字符串添加到服务器端的 `ArrayList`。如果出现错误,它会使用 `Response.Write` 语句写入错误。其他需要关注的地方是设置在 `index.aspx` 页面上的函数调用。您可以轻松添加新颜色和新功能。只需添加相关的常量并整合所需的函数。消息传递架构保持不变。
关注点
可以使用简单的消息传递架构来连接许多计算机。当应用程序运行时,这似乎是一项艰巨的任务。由于我们在客户端完成了大部分工作,因此用户体验也非常棒。
历史
- 2007/10/01 - 添加了文章 (等待建议)。
- 2009/10/26 - 更新了发送文本消息的功能。新版本在 VS 2008 中开发。