在安富莱论坛看了一位朋友的演示, 发现emWin竟然可以做出Toast提示窗口, 查阅资料后经过一天时间做出了一款Toast窗口控件, 原理就是利用窗口的Paint事件设置窗口的透明度, 直接上代码吧
#include
#include // va_list va_start va_end等在此头文件中定义
#include "SxSkin.h"
#include "DlgMsgBox.h"
#include "Debug.h"
#define ID_WINDOW_0 (GUI_ID_USER + 0x00)
#define ID_LBL_MSG (GUI_ID_USER + 0x02)
// Toast显示的Y轴坐标
#define TOAST_Y 410
// Toast显示的高度
#define TOAST_HEIGHT 36
// Toast宽度上限
#define TOAST_WIDTH_MAX 780
// Toast显示X轴间隔
#define TOAST_X_SPACE 10
PRIVATE const GUI_WIDGET_CREATE_INFO _aDialogCreate[] = {
{ WINDOW_CreateIndirect, "DlgToast", ID_WINDOW_0, TOAST_X_SPACE, TOAST_Y, TOAST_WIDTH_MAX, TOAST_HEIGHT, 0, 0x0, 0 },
{ TEXT_CreateIndirect, "lblMsg", ID_LBL_MSG, 0, 0, TOAST_WIDTH_MAX, TOAST_HEIGHT, 0, 0x64, 0 },
};
// 一行最多显示28个汉字
#define TOAST_MSG_MAX 1024
// 默认显示时长 5秒
#define DEFAULT_TIME 500
// 定时器中断值 10毫秒
#define TIMER_VALUE 10
// 提示框内容
PRIVATE char *m_Text = NULL;
// 定时器设备
PRIVATE WM_HMEM m_Timer = WM_HMEM_NULL;
// 本窗体句柄
PRIVATE WM_HWIN m_This = WM_HWIN_NULL;
// 显示时长
PRIVATE UINT16 m_ShowTime = DEFAULT_TIME;
static void _initDialog(WM_MESSAGE* pMsg)
{
if (pMsg->MsgId == WM_INIT_DIALOG)
{
//
// Initialization of 'DlgMsg'
//
WM_HWIN hItem = pMsg->hWin;
WINDOW_SetBkColor(hItem, TOAST_BACK_COLOR);
WIDGET_SetFocusable(hItem, 0);
//
// Initialization of 'lblMsg'
//
hItem = WM_GetDialogItem(pMsg->hWin, ID_LBL_MSG);
TEXT_SetFont(hItem, SX_FONT);
TEXT_SetTextAlign(hItem, GUI_TA_LEFT | GUI_TA_VCENTER);
TEXT_SetTextColor(hItem, TOAST_FRONT_COLOR);
}
else
{
WM_DefaultProc(pMsg);
}
}
static void _cbDialog(WM_MESSAGE * pMsg)
{
WM_HWIN hItem;
switch (pMsg->MsgId)
{
case WM_INIT_DIALOG:
{
GUI_RECT rect;
hItem = pMsg->hWin;
WM_GetWindowRectEx(hItem, &rect);
// 更新文本
hItem = WM_GetDialogItem(pMsg->hWin, ID_LBL_MSG);
// DEBUG_I("Toast rect: %d, %d, %d, %d.", rect.x0, rect.y0, rect.x1, rect.y1);
WM_SetWindowPos(hItem, rect.x0 + TOAST_X_SPACE, rect.y0, rect.x1 + 1 - rect.x0 - (TOAST_X_SPACE * 2), rect.y1 + 1 - rect.y0);
TEXT_SetText(hItem, m_Text);
break;
}
case WM_PAINT:
// 背景重绘
GUI_SetAlpha(TOAST_TRANSPARENCY);
WINDOW_Callback(pMsg);
GUI_SetAlpha(0);
break;
case WM_TIMER:
// 定时器消息(定时到时程序跑到这里)
if (m_ShowTime == 0)
{
WM_HideWindow(pMsg->hWin);
}
else
{
m_ShowTime--;
WM_RestartTimer(m_Timer, TIMER_VALUE);
WM_BringToTop(m_This);
}
break;
default:
WM_DefaultProc(pMsg);
break;
}
}
/*******************************************************************************
** 功能描述: 设置Toast提示窗体的大小
** 参数说明: None
** 返回说明: None
** 创建人员:
********************************************************************************/
PRIVATE void SetWindowSize(void)
{
int x = 0;
int width = GUI_GetStringDistX(m_Text) + TOAST_X_SPACE * 2;
// DEBUG_I("Toast width is %d.", width);
if (width > TOAST_WIDTH_MAX)
{
width = TOAST_WIDTH_MAX;
}
x = TOAST_X_SPACE + (TOAST_WIDTH_MAX - width) / 2;
// DEBUG_I("Toast rect: %d, %d, %d, %d.", x, TOAST_Y, width, TOAST_HEIGHT);
WM_SetWindowPos(m_This, x, TOAST_Y, width, TOAST_HEIGHT);
}
/*******************************************************************************
** 功能描述: 显示Toast提示框, 默认5秒消失, 最多显示28个汉字
** 参数说明: None
** 返回说明: None
** 创建人员:
********************************************************************************/
void ShowToast(const char *aMsg, ...)
{
WM_HWIN hWin;
va_list arg_ptr;
if (m_Text == NULL)
{
m_Text = (char *)SxMalloc(TOAST_MSG_MAX);
}
va_start(arg_ptr, aMsg);
vsnprintf(m_Text, TOAST_MSG_MAX, aMsg, arg_ptr);
va_end(arg_ptr);
if (m_This != WM_HWIN_NULL)
{
// 窗口已经创建
// 让对话框窗口置顶
SetWindowSize();
WM_SendMessageNoPara(m_This, WM_INIT_DIALOG);
m_ShowTime = DEFAULT_TIME;
WM_RestartTimer(m_Timer, TIMER_VALUE);
WM_ShowWindow(m_This);
WM_BringToTop(m_This);
return;
}
hWin = GUI_CreateDialogBox(_aDialogCreate, GUI_COUNTOF(_aDialogCreate), _initDialog, WM_HBKWIN, 0, 0);
WM_SetHasTrans(hWin);
WM_SetCallback(hWin, _cbDialog);
m_This = hWin;
// 创建一个软件定时器 2000毫秒周期
m_ShowTime = 0;
m_Timer = WM_CreateTimer(WM_GetClientWindow(m_This), 1, TIMER_VALUE, 0);
WM_BringToBottom(m_This);
}
代码贴上, 说下实现的功能先:
窗体根据字符数显示大小, 字符显示距离窗体左右有10个像素的间隔, 位置为屏幕居中对齐, 默认显示时间是5秒, 使用了软定时器, 定时时间为10毫秒, 为什么不直接定义为5秒呢?因为防止其他窗口覆盖, 在定时器内将本窗口Z序提高到顶端.
注意下的是, 本窗口的设计思路是一直保存, 只隐藏不销毁, 考虑到Toast窗口使用一般比较频繁, 就没必要频繁销毁创建该窗口, 所以在界面载入完成后先调用一次ShowToast("");进行一次初始化, 在创建窗体时需要注意的是创建时使用了回调函数后又重新设置了一个新的回调函数, 创建时的回调函数主要用来初始化窗口基本参数, 后面的回调函数是进行窗口的半透明绘制以及控件大小设置及内容填充的, WM_SetHasTrans(hWin);这句一定要在设置新回调函数之前调用.
还有需要注意的一点是GUI_GetStringDistX这个函数在VS模拟器里是无效的, 返回一直为0, 但在STM32中是有效的, 也有可能是我的模拟器驱动没有做好.
代码里面有些宏定义就是颜色透明度的定义, 没有给出, 可自由进行设置.