STemWin开发过程中遇到的二三事

前言

最近负责的产品是一个小型网关,搭载了一块3.5寸的显示屏,需要实时显示一部分数据,还能进行参数设置,为了方便的管理多个窗口和界面的内容展示,所以这里采用了STemWin,因为使用的主控芯片是STM32F29,所以可以免费使用STemWin,开发过程中遇到了部分问题,这里记录一下,方便自己查阅,也希望可以帮助到正在看文章的你。

记录

1.Keil字符编码对中文显示的影响

在开发过程中发现中文字符显示有问题,但是中文字库都是已经转换好的,刚开始不知道是什么原因,通过与同事探讨才发现了问题所在。

原因

文件默认的字符编码格式与字库索引的编码格式不同导致的。

大家应该知道我们平常使用Keil的时候中文注释会出现乱码,一个中文字符会被拆分成两个字符,因为这是使用的 Encode in ANSI 编码格式,我们来看一下 Encode in ANSI 编码格式的百科:

ANSI是一种字符代码,为使计算机支持更多语言,通常使用 0x00~0x7f 范围的1 个字节来表示 1 个英文字符。超出此范围的使用0x80~0xFFFF来编码,即扩展的ASCII编码。
为使计算机支持更多语言,通常使用 0x80~0xFFFF 范围的 2 个字节来表示 1 个字符。比如:汉字 ‘中’ 在中文操作系统中,使用 [0xD6,0xD0] 这两个字节存储。

可以看出,由于一个中文符号被拆分成两个字节来保存,所以就导致假如你用中文注释的时候,会因为删除中间的一个字符导致后面的字符全乱码,大家可以自己尝试下。
正式由于这个原因,导致我想要在显示屏上显示中文字符的时候没法正确索引到正确汉字的位置,从而无法显示出中文来。

解决办法

解决办法也很简单,我们只需要将字符的编码格式修改一下即可,如下图:
字符编码
Encode in ANSI修改为Encode in UTF-8 without signature即可,下面是中文显示效果:

不过还有几个字没有制作进字库当中去,所以没有显示,大家能猜出是哪几个字吗?

2.DialogBox窗口不支持定时器

因为想在某个子窗口上实时更新一些数据,原本以为直接用STemWin的定时器就可以搞定了,由于子窗口是通过 GUI_CreateDialogBox 函数创建的,也就是我们常说的对话框,但是居然不起作用!但是安富莱的例程也是这样用的,这就有点让我摸不到头脑了。
搜索了一下,发现大家也都遇到类似的问题,但是都没有很好的解决方案,经过跟领导的探讨,终于通过另一种方式将这个问题解决掉了,这里分享出来给大家参考,当然不一定是最好的方案,但是因为项目急,暂且先用着吧。

解决办法

建立一个子窗口,当然这个子窗口是一个空窗口,我们在这个子窗口的定时器中不断无效化你要定时刷新的界面,从而达到窗口重绘的效果,下面展示部分代码:

//主要处理定时器的回调
static void _cbTimerWindow(WM_MESSAGE *pMsg)
{
  static WM_HTIMER hTimerTime2;
  switch(pMsg->MsgId)
  {
    case WM_CREATE:
      /* Create timer */
      hTimerTime2 = WM_CreateTimer(pMsg->hWin, ID_TIMER_TIME2, 1000, 0);
      break;
    case WM_TIMER:
      /* Reset Timer */
      //我们通过无效化你要刷新数据界面,从而进入窗口重绘选项,之所以这样做,是因为直接在Dialog中定时器不起作用
      WM_InvalidateWindow(WM_GetClientWindow(TempWin));
      WM_RestartTimer(pMsg->Data.v, 1000);
      break;
    case WM_DELETE:
      WM_DeleteTimer(hTimerTime2);
      break;
    default:
      WM_DefaultProc(pMsg);
  }
}
//下图左侧的按钮界面的回调函数
static void _cbDeviceInfoMenuDialog(WM_MESSAGE *pMsg)
{
  WM_HWIN hItem;
  int     NCode;
  int     Id;
  // USER START (Optionally insert additional variables)
  // USER END
  switch(pMsg->MsgId)
  {
    case WM_INIT_DIALOG:
      //
      // Initialization of 'DeviceInfo'
      //
      hItem = pMsg->hWin;
      FRAMEWIN_SetTitleVis(hItem, 0);
      // USER START (Optionally insert additional code for further widget initialization)
      BUTTON_SetFont(WM_GetDialogItem(hItem, ID_BUTTON_0), &GUI_FontHeiTi_12);
      BUTTON_SetFont(WM_GetDialogItem(hItem, ID_BUTTON_1), &GUI_FontHeiTi_12);
      BUTTON_SetFont(WM_GetDialogItem(hItem, ID_BUTTON_2), &GUI_FontHeiTi_12);
      BUTTON_SetFont(WM_GetDialogItem(hItem, ID_BUTTON_3), &GUI_FontHeiTi_12);
      //下面这条语句就是建立的子窗口,其中区域建议大家选在你要更新的界面区域中,我们在_cbTimerWindow这个回调函数中处理定时器
      WM_CreateWindowAsChild(80, 45, 200, 40, pMsg->hWin, WM_CF_SHOW | WM_CF_HASTRANS, _cbTimerWindow, 0);
      // USER END
      break;
    case WM_NOTIFY_PARENT:
      Id    = WM_GetId(pMsg->hWinSrc);
      NCode = pMsg->Data.v;
      switch(Id)
      {
        case ID_BUTTON_0: // Notifications sent by 'DateTime'
          switch(NCode)
          {
            // USER START (Optionally insert additional code for further notification handling)
            case WM_NOTIFICATION_GOT_FOCUS:
            //按钮0获取到焦点展示界面1
              BUTTON_SetTextColor(WM_GetDialogItem(pMsg->hWin, ID_BUTTON_0), BUTTON_CI_UNPRESSED, GUI_RED);
              TempWin = GUI_CreateDialogBox(_aDatetimeCreate, GUI_COUNTOF(_aDatetimeCreate), _cbDatetimeDialog, WM_GetParent(pMsg->hWin), 0, 0);
              break;
            case WM_NOTIFICATION_LOST_FOCUS:
            //按钮0失去焦点结束界面1
              BUTTON_SetTextColor(WM_GetDialogItem(pMsg->hWin, ID_BUTTON_0), BUTTON_CI_UNPRESSED, GUI_BLACK);
              GUI_EndDialog(TempWin, 0);
              break;
              // USER END
          }
          break;
      }
      break;
    // USER START (Optionally insert additional message handling)
    case WM_KEY:
      switch(((WM_KEY_INFO *)(pMsg->Data.p))->Key)
      {
        case GUI_KEY_ESCAPE:
          brea;
      }
      break;
    // USER END
    default:
      WM_DefaultProc(pMsg);
      break;
  }
}
//主要处理界面的刷新
static void _cbDatetimeDialog(WM_MESSAGE *pMsg)
{
  WM_HWIN hItem;
  // USER START (Optionally insert additional variables)
  char dateTimeTemp[48] = {0};
  // USER END
  hItem = pMsg->hWin;
  switch(pMsg->MsgId)
  {
    case WM_INIT_DIALOG:
      //
      // Initialization of 'Datetime'
      //
      //
      FRAMEWIN_SetTitleVis(hItem, 0);
      //
      // Initialization of 'Date'
      //
      hItem = WM_GetDialogItem(hItem, ID_TEXT_YEAR);
      TEXT_SetTextAlign(hItem, GUI_TA_HCENTER | GUI_TA_VCENTER);
      // USER START (Optionally insert additional code for further widget initialization)
      // USER END
      break;
    // USER START (Optionally insert additional message handling)
    case WM_KEY:
    case WM_PAINT:
      //每次进入更新当前时间
      GUI_DrawBitmap(&bmdate, 10, 10);
      sprintf(dateTimeTemp, "%4d年", pClock_DateTime->TYY);
      TEXT_SetFont(WM_GetDialogItem(hItem, ID_TEXT_YEAR), &GUI_FontHeiTi_20);
      TEXT_SetText(WM_GetDialogItem(hItem, ID_TEXT_YEAR), dateTimeTemp);
      sprintf(dateTimeTemp, "%02d月", pClock_DateTime->TMD);
      TEXT_SetFont(WM_GetDialogItem(hItem, ID_TEXT_MONTH), &GUI_FontHeiTi_20);
      TEXT_SetText(WM_GetDialogItem(hItem, ID_TEXT_MONTH), dateTimeTemp);
      sprintf(dateTimeTemp, "%02d日", pClock_DateTime->TDD);
      TEXT_SetFont(WM_GetDialogItem(hItem, ID_TEXT_DATE), &GUI_FontHeiTi_20);
      TEXT_SetText(WM_GetDialogItem(hItem, ID_TEXT_DATE), dateTimeTemp);
      sprintf(dateTimeTemp, "%02d时", pClock_DateTime->THH);
      TEXT_SetFont(WM_GetDialogItem(hItem, ID_TEXT_HOURS), &GUI_FontHeiTi_20);
      TEXT_SetText(WM_GetDialogItem(hItem, ID_TEXT_HOURS), dateTimeTemp);
      sprintf(dateTimeTemp, "%02d分", pClock_DateTime->TMM);
      TEXT_SetFont(WM_GetDialogItem(hItem, ID_TEXT_MINUTE), &GUI_FontHeiTi_20);
      TEXT_SetText(WM_GetDialogItem(hItem, ID_TEXT_MINUTE), dateTimeTemp);
      sprintf(dateTimeTemp, "%02d秒", pClock_DateTime->TSS);
      TEXT_SetFont(WM_GetDialogItem(hItem, ID_TEXT_SECOND), &GUI_FontHeiTi_20);
      TEXT_SetText(WM_GetDialogItem(hItem, ID_TEXT_SECOND), dateTimeTemp);
      //系统自上电依赖运行的时间,以天数为最小单位
      GUI_SetFont(&GUI_FontHeiTi_20);
      GUI_SetColor(GUI_BLACK);
      sprintf(dateTimeTemp, "系统已运行%3d天", 48);
      GUI_DispStringAt(dateTimeTemp, 40, 125);
      break;
    // USER END
    default:
      WM_DefaultProc(pMsg);
      break;
  }
}

经过测试,上面的代码是可以正常运行并使用的,目前只能通过这种方式先解决了,后面有时间再研究一下,下面是效果演示:

3.批量转换图标到代码脚本

项目用到了不少图标,这些图标都是我从iconfont上下载的,这上面有很多不错的图标,建议大家有需求也可以去看一下,由于我下载的小图标有点多,而且一个个的转换起来有点复杂,所以就用Python写了个小脚本,批量将图片转换成C代码,当然脚本也很简单,主要就是调用了一下BmoCvt.exe这个官方给的工具,下面分享给大家:

# -*- coding: utf-8 -*-
# author: IAMLIUBO
# blog: https://blogs.oopswow.com
import os 
import sys,time
from PIL import Image
IMAGE_FILTE = [".PNG", ".png", ".JPEG", ".jpeg", ".jpg", ".BMP", ".bmp"]
CODE_DIR = "C_CODE/"
def ConvertImage2Code(path):
    image_list = os.listdir(path)
    if not os.path.exists(path + "/../" + CODE_DIR):
        os.mkdir(path + "/../" + CODE_DIR)
    
    for i in image_list:
        if os.path.splitext(i)[1] in IMAGE_FILTE:
            code_save_path = CODE_DIR + i.split('.')[0]
            command = "BmpCvt.exe " + i + " -saveas" + code_save_path + ",1,16 -exit"
            os.system(command)
    print("Convert done!")
    
def main():
    if len(sys.argv) < 2: 
        print("\r\nNo image folder path provided\r\n"
              "Usage:\r\n"
              "      python Image2Code4STemWin.py [image folder path]")
        exit()
    if not os.path.exists("BmpCvt.exe"):
        print("\r\nPlease put BmpCvt.exe to this folder.\r\n")
        exit()
    print("\r\nImage to C code script for STemWin by IAMLIUBO.\r\n"
        " 1.Convert the image to C code(true color with alpha channel use BmpCvt.exe).\r\n")
    ConvertImage2Code(sys.argv[1])
if __name__ == '__main__':
    main()

记得使用的时候将BmpCvt.exe这个工具放到当前目录下,然后指定图像文件的路径即可,像下面一样:

python Image2Code4STemWin.py images

完成后会在当前目录下生成一个C_CODE文件夹,里面就是转换完成后的代码文件了。

自动化思想很重要呀,有时候程序要比人更可靠,而且非常适合重复性工作。

随时更新…

请我喝一罐冰阔乐 (^o^)/