65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 和 AJAX 白板

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (13投票s)

2007年9月30日

CPOL

7分钟阅读

viewsIcon

103601

downloadIcon

2933

这是一个基于 Web 的白板。它使用 C# 和 AJAX 在服务器和客户端之间进行通信。通过 AJAX 实现不同用户之间的数据共享。绘画内容可以实时地在多个客户端之间共享。

引言

这是一个基于 AJAX 的白板应用程序。通常,与桌面应用程序不同,Web 应用程序需要设计以优化服务器资源。这是 AJAX 流行的一个原因。在这里,我演示了 AJAX 的一个强大用途,即实现两个或多个客户端之间的通信。

Screenshot - MainWindow1.jpg

那么,它能做什么?正如我所说,这是一个白板应用程序。用户可以使用提供的绘图工具、颜色选择等。基本思想是将用户绘制的内容共享给所有客户端。所有查看主页的用户都可以参与绘图并与其他用户共享。还可以清除本地画布、服务器数据等。

背景

几年前,我被分配了研究 AJAX 功能的任务。作为技术爱好者,我决心说服客户使用当时**新颖且热门**的技术。我花了几天时间思考设计和 GUI 的正确方法。我想让应用程序看起来像桌面应用程序,但又能在浏览器中运行。我的目标是完全避免回发(以给客户留下深刻印象)。我搜索了 Google 上的所有结果,阅读了所有关于 AJAX 的文章,以便获得完美的演示应用程序。

我偶然看到了 Walter Zorn 的 JavaScript 代码 (wz_jsgraphics.js) 链接。那时,我萌生了构建一个基于 Web 的白板的想法。我将所有功劳归于 Walter 出色的 JavaScript 代码。一旦我学会使用他的脚本,我的基于 Web 的白板很快就上线运行了。

基础知识...

与所有 AJAX 应用程序一样,我有一个向用户显示的主页面。服务器上的一个页面处理该页面的所有请求和响应。所有的绘图部分都使用 Walter 的 JavaScript 代码完成。我设计了一个消息机制来识别客户端请求并相应地服务客户端。我根据功能将 JavaScript 分为不同的文件。理解应用程序流程非常简单。

Screenshot - concept.jpg

请注意,在任何时候,**客户端都会发起数据请求**。服务器仅作为存储数据的中心(使用 `ArrayList`)。客户端发送一个数字,指示其请求数据的起始位置。

Using the Code

您可以在 Walter 的网站上找到图形库的详细说明:Walter's website

我将从简要解释每个文件的内容开始。

JavaScript 文件

  1. ajaxfunctions.js - 包含发送和接收服务器数据的函数。它还包含在从服务器获取数据后更新屏幕画布的 JavaScript 函数。选择工具和颜色、发送请求清除服务器数据等的函数也放置在此文件中。
  2. ajaxobjects.js - 包含创建 AJAX 对象和发送 `XMLHTTP` 请求的函数。
  3. common.js - 包含我認為會佔用主頁面的函數。主要是 1-2 行的重用函數。
  4. constants.js - 包含应用程序使用的所有客户端常量。分类为颜色、工具、查询字符串等。
  5. drawing.js - 包含将处理绘图并与服务器建立联系的函数。
  6. wz_jsgraphics.js - 当然,这是最重要的绘图库,没有它,此应用程序就是个空壳。它包含所有实际在客户端画布上绘制形状的函数。

ASP.NET 文件 (服务器端 C#)

  1. Constants.cs - 包含所有服务器端常量。
  2. HandleData.aspx - 这是所有 AJAX 请求都发布到的文件。它有函数来决定客户端想要什么,然后用用户数据进行响应。
  3. index.aspx - 这是包含所有控件的主窗体。它只是一个服务用户。该页面作为 GUI,初始化共享过程。

总而言之,这是一个简单的解释

  1. 用户“A”在他的屏幕上加载“index.aspx”页面。
  2. 他选择了一个工具和颜色,并在屏幕上开始设计。
  3. 另一个用户“B”访问“index”页面。
  4. 由于“B”是新用户,他从服务器获取所有数据,用户 A 的画布会在用户 B 的屏幕上复制。
  5. 现在,AJAX 进入了图片。为了让两个用户保持同步,AJAX 脚本会定期运行。
  6. 当用户更改所选工具、颜色或在屏幕上绘图时,AJAX 脚本会更新服务器。
  7. 这个步骤会与任意数量的用户重复。
  8. 我认为,理想情况下,在 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 中开发。
© . All rights reserved.