技术开发 频道

WindowMobile上的模拟LED显示屏插件

  考虑实际的LED屏,在滚动起来时,是同样的一段字符串首尾相接的方式,在屏上连续从右向左滚动。因此为了实现的方便,我们实际上建立的逻辑模型是如下图所示:两个相同LED字符串,以一定的像素间距(MEMODISTANCE)首尾连接,我们分别用两个变量记录两个LED字符串的位置:

  g_LedPos1: 第一个字符串的起始位置(LED屏幕客户区中的坐标);

  g_LedPos2: 第二个字符串的起始位置(LED屏幕客户区中的坐标);

  g_LedWidth: 字符串的像素宽度;(LED屏幕坐标)

  MEMODISTANCE:两个字符串之间的像素距离;(LED屏幕坐标);

  MEMOCOUNT:逻辑模型中首尾相接的字符串数量;(在本例中 = 2);

  SCREENWIDTH:LED屏的像素宽度(LED屏幕坐标),在本例中= 60;

  LED 屏幕坐标:在LED屏幕中,每个像素都是在实际位图中占据的是(4*4)尺寸;例如显示屏在该坐标体系下,是60像素宽度,但是实际位图是240像素宽度。

  (a)连续滚动原理;

  ======================================================================================

  ●. 连续滚动需要满足下列条件: MEMODISTANCE >= SCREENWIDTH / MEMOCOUNT;

  =======================================================================

  上面的不等式用语言来描述就是:“相邻字符串 的 “首尾间距” 大于等于 LED屏幕像素宽度 / 屏幕上可同时显示的字符串最大个数”;假如我们把“字符串”看做汽车,LED屏看作“单行道”,那么这里提到的也就是对“车距”的要求。

  屏幕上可同时显示的字符串个数(MEMOCOUNT)实际上就是我们的代码中在维护的逻辑模型的字符串个数,也就是说我们的代码中一共有多少个 g_LedPos 变量,每一个 g_LedPos 变量可以维护一个字符串;在本例中,我们维护了两个 g_LedPos; 这两个字符串首尾相接后成为无限长序列的一个基本单位(basic unit of the memo queue);无限长队列是从这个基本单位扩展而成。因此,MEMOCOUNT 是 这个基本单位的容量(容纳字符串的数量);

  为什么我们会对“车距”有需求呢,想象下如果汽车的宽度(字符串长度)非常小,如果不控制车距,就会导致单行道上可同时容纳的汽车数量增加,而如果这个数量超过了我们代码中的模型(组成无限长字符串队列的基本单位的容量)的字符串个数,那么我们的代码就无法同时控制这么多汽车的出现位置;这将会导致帧突变,体现在视觉上就是,滚动过程中,屏幕中间本来是黑色背景,但是在下一帧忽然在中间冒出“文本内容”;

  在本例中,我们的逻辑模型是两个字符串相接,因此因此我将 MEMODISTANCE 定义为 30 像素;假如不满足这个条件并且输入字符串极短(例如“1”),那么在连续滚动的过程中就可能造成帧突变,在某些时刻的帧和上一帧无法衔接,即在屏幕中的黑暗部分凭空闪现字符串内容。

  (b)定时器中的处理:

  由于是从右向左滚动,所以我们在定时器中只需要把 g_LedPos1, g_LedPos2 递减即可,当发现第一个字符串离开屏幕时,我们立即两个坐标的指向向后移动到它后面的那个字符串,即把 g_LedPos2 赋值给 g_LedPos1; g_LedPos2 根据 g_LedPos1 重新赋值;这样就以两个字符串为一个基本单位,向后逐一进行无穷扩展,形成无数字符串首尾相连的虚拟场景;

  定时器中的代码如下所示:

  case WM_TIMER:

  g_LedPos1--;

  g_LedPos2--;

  //第一条已经完全离开屏幕左侧了吗?如果是,则向后更替!

  if(g_LedPos1 + g_LedWidth < 0)

  {

  g_LedPos1 = g_LedPos2;

  g_LedPos2 = g_LedPos1 + g_LedWidth + MEMODISTANCE;

  }

  InvalidateRect(hwnd, &rcLed, FALSE);

  break;

  (c)在绘制时的处理:

  在绘制时,主要使用 BitBlt 函数来完成。但是需要注意的是,字符串的起始坐标(g_LedPos)是否在 LED 屏内部,将决定了 BitBlt 中的参数有所区别(这里我不详细分析原因)。

  我使用一个辅助函数:GetBltInfo 来帮助我判断 BitBlt 函数中应该使用的参数;它可以计算出 BitBlt 中应该把 LedBitmap 中的何处 拷贝到 DC 的何处,拷贝多少宽度;相关代码如下:

  //根据当前位置,决定贴图的相对位置

  void GetBltInfo(int pos, int ledWidth, RECT* lpRcLed, int* lpDestX, int* lpSrcX, int* lpWidth)

  {

  //头部在显示区

  if(pos>=0)

  {

  *lpDestX = pos * LEDSIZE;

  *lpSrcX = 0;

  *lpWidth = min(lpRcLed->right, (pos + ledWidth)*LEDSIZE) - *lpDestX;

  }

  else

  {

  *lpDestX = 0;

  *lpSrcX = -pos*LEDSIZE;

  *lpWidth = min(lpRcLed->right, (pos + ledWidth)*LEDSIZE);

  }

  }

  //========================================

  //这里是窗口函数中对 WM_PAINT 的处理:

  //========================================

  case WM_PAINT:

  {

  PAINTSTRUCT ps;

  RECT rc;

  //===============================================

  // NOW WE START TO GET DC !

  //===============================================

  hDC = BeginPaint(hwnd, &ps);

  HDC hMemDC = CreateCompatibleDC(hDC);

  HBITMAP hOldBm = (HBITMAP)SelectObject(hMemDC, g_BitmapLed);

  //贴第一个字符串

  int destX, srcX, width;

  GetBltInfo(g_LedPos1, g_LedWidth, &rcLed, &destX, &srcX, &width);

  BitBlt(hDC,destX,rcLed.top, width, (rcLed.bottom - rcLed.top),

  hMemDC,srcX, 0,

  SRCCOPY);

  //贴第二个字符串

  if(g_LedPos2 * LEDSIZE <= rcLed.right)

  {

  GetBltInfo(g_LedPos2, g_LedWidth, &rcLed, &destX, &srcX, &width);

  BitBlt(hDC,destX,rcLed.top, width, (rcLed.bottom - rcLed.top),

  hMemDC,srcX, 0,

  SRCCOPY);

  }

  SelectObject(hMemDC, hOldBm);

  DeleteDC(hMemDC);

  EndPaint(hwnd, &ps);

  }

  break;

0
相关文章