emWin SWIPELIST绕过删除Item后Attach窗口不自动对齐Bug
删除Item后附着在Item上的窗口不对齐Bug的现象
使用SWIPELIST的时候有时会附着在Item上面一些窗口用于显示某些信息或者某些选项,按钮等。
同样某些情况下我们需要删除Item但Item被删除后会自动对齐,而附着在其上的窗口却不会移动这应该是emWin的一个BUG
绕过此Bug的思路
删除Item后把SWIPELIST窗口内的可见的Item的附着窗口全部先Detach再Attach,且把附着窗口未显示的出来的部分WM_ShowWin
使其显示。
所需注意此控件的一些特性(或坑)
1.在添加Item的时候SepSize在SeparatorItem之前的一个Item会出现异常,需要在添加完Item后再重新设置其SpearatorSize。
2.删除Item后在Detach 附着窗口再Attach上时虽然附着窗口已跟随到了Item的位置,但之前未显示的附着窗口 仍然不会显示,这时就需要WM_ShowWin
来把未显示的显示出来。
3.显示未显示的 附着窗口时,**不能把所有的附着窗口的未显示窗口都显示出来。**否则会出现显示异常。
4当最后一个Item和其SepSize都完整显示时,如果其后还有Item那么这个Item的附着窗口也需要WM_ShowWin
出来,否则滑动SWIPELIST表时接下来的附着窗口不会显示
具体思路
1.首先得计算出SWIPELIST窗口中可以完整显示的Item个数。
2.计算出SWIPELIST窗口中是否有未完全显示的部分已及这部分的Size。
3.根据当前的ScrollPos计算出SWIPELIST窗口顶部Item是否移出一部分已及移出量
4.删除Item后根据1,2,3
得出最后一个Item是否完全显示。
5.根据1,4
算出需要处理的Item个数。
如此便可以绕过这个BUG正常显示和操作。
实现细节和代码
管理数据
在绕过这个BUG的时候我也做了一个其它的实现:点击选定Item的时候Item背景色改变指示其选定状态,更改上一个Item的选定状态。
而且由于:
1.我们需要对AttachWin的频繁操作,AttachWin和Item又是对应起来的。
2.Item是可以增删,不定长的。
3.在添加Item的时候不可随意指定其添加位置(如把Item插入到某两个Item之间)而很多时候我时候我们都需要在Item中添加一个或多个SeparatorItem来分组不同类别的Item。我们必要要按照顺序一组一组的添加Item到SWIPELIST中去。
因此这里我使用了链表,有列表和列表项的思想在里面。SeparatorItem表包含SeparatorItem。在SeparatorItem项中又有一个普通Item的列表,这些表项都存储着其Item信息,如是否有焦点
,其附着窗口句柄
,Item名称
,Item所开辟空间的句柄
等。
以下部分代码展示的是创建一个窗口来放置SWIPELSIT并创建SWIPELSIT和一个按钮来模拟应用中删除Item,和模拟应用中添加Item和SeparatorItem。以及把链表中的数据遍历后添加Item到SWIPELSIT中去且把每一个Item数据项的的指针传递给Item做其UserData
void MainTask(void) {
int xSize, ySize;
int textYPos;
SWIPELIST_Handle hSwpLst;
GUI_HWIN hSwpParent;
//GUI_HWIN hAttachText;
BUTTON_Handle hButton;
Separator_t * pSepNode;
Item_t * pItemNode;
int i, devCnt, itemIndex;
char text[10];
char sepInfoText[50];
Header.pSepaHeader = NULL;
//LastFocus = -1; //
WM_SetCreateFlags(WM_CF_MEMDEV);
GUI_Init();
GUI_SelectLayer(0);
WM_MULTIBUF_Enable(1);
WM_MOTION_Enable(1);
xSize = LCD_GetXSize();
ySize = LCD_GetYSize();
hSwpParent = WM_CreateWindowAsChild(0, 0, xSize/2, ySize, WM_HBKWIN, WM_CF_SHOW | WM_CF_MEMDEV | WM_CF_HASTRANS, _cbParent, 0);
//
//创建SWIPELIST小部件
//
hSwpLst = SWIPELIST_CreateEx(1, 1, xSize / 2-40, ySize - 1, hSwpParent, WM_CF_SHOW | WM_CF_MOTION_Y | WM_CF_HASTRANS, 0, GUI_ID_SWIPELIST0);
SWIPELIST_SetOwnerDraw(hSwpLst, _OwnerDraw);
SWIPELIST_SetBkColor(hSwpLst, SWIPELIST_CI_BK_SEP_ITEM, GUI_MAKE_COLOR(0xD74580));
SWIPELIST_SetDefaultSepSize(SEP_SIZE);
//
//创建一个按键用于删除测试
//
hButton = BUTTON_CreateEx(203, 5, 35, 20, hSwpParent, WM_CF_SHOW | WM_CF_HASTRANS, 0, GUI_ID_BUTTON0);
BUTTON_SetText(hButton, "Del");
//
//模拟添加Item
//
AddSepLst(4);
AddItemLst(Header.pSepaHeader, 5);//对头SeparatorItem添加
AddItemLst(Header.pSepaHeader->pSepNext, 4);
AddItemLst(Header.pSepaHeader->pSepNext->pSepNext,4);
AddItemLst(Header.pSepaHeader->pSepNext->pSepNext->pSepNext, 4);
//遍历一次SeparatorLst把每个的Item添加进去
pSepNode = Header.pSepaHeader;
GUI_SetColor(GUI_WHITE);
textYPos = 5;
i = 0;
itemIndex = 0;
//
//遍历所有SeparatorItem和Item数据并把它们添加到SwipeLst中去。在下面的代码中做了对数据的填充,在实际中数据由添加设备获得
//
while (pSepNode != NULL) {
//pSepNode->pItemHead = AddItemLst(pSepNode->pItemHead, 4);
//
//打印出一些SeparatorItem信息在窗口
//
strcpy((char*)(pSepNode->SepaName), SepName[i++]);
sprintf(sepInfoText, "Index:%d\tmHand:%d\tName:%s",pSepNode->SepaIndex,pSepNode->hAllocSpace,pSepNode->SepaName);
GUI_SetFont(GUI_FONT_16B_ASCII);
GUI_SetColor(GUI_RED);
GUI_DispStringAt(sepInfoText, 240, textYPos);
textYPos += 17;
//
//添加一个SeparatorItem时把其所属的Item也完全遍历并添加进SwipLst 并添加Item UserData
//
SWIPELIST_AddSepItem(hSwpLst, pSepNode->SepaName, ITEM_SIZE);
SWIPELIST_SetItemUserData(hSwpLst, itemIndex++, (U32)(pSepNode));
//
//记录itemIndex, 设备计数归0,节点移动到下一个节点
//
devCnt = 0;
pItemNode = pSepNode->pItemHead;
while (pItemNode != NULL) {
//
//打印出一些Item信息在窗口
//
sprintf(pItemNode->ItemName, "Device%d",devCnt++);
sprintf(sepInfoText, "--Item:%d\tmHand:%d\tName:%s", pItemNode->ItemIndex,pItemNode->hAllocSpace,pItemNode->ItemName);
GUI_SetFont(GUI_FONT_10_ASCII);
GUI_SetColor(GUI_WHITE);
GUI_DispStringAt(sepInfoText, 240, textYPos);
textYPos += 17;
//
//添加Item到SiwpeList中
//
SWIPELIST_AddItem(hSwpLst, pItemNode->ItemName, ITEM_SIZE);
//
//建一个Win并把其Attach到Item上
//
pItemNode->hAttachWin = TEXT_CreateEx(0, 0, 50, ITEM_SIZE - 2, hSwpParent, WM_CF_SHOW, 0, USER_ID_TEXT + i, "3癈");//"3癈"
TEXT_SetTextAlign(pItemNode->hAttachWin, TEXT_CF_VCENTER | TEXT_CF_HCENTER);
TEXT_SetBkColor(pItemNode->hAttachWin, GUI_GRAY);
TEXT_SetFont(pItemNode->hAttachWin, GUI_FONT_16B_1);
SWIPELIST_ItemAttachWindow(hSwpLst, itemIndex, pItemNode->hAttachWin, 138, 1);
//把Item的Data的指针传递给这个Item User Data
SWIPELIST_SetItemUserData(hSwpLst, itemIndex, (U32)(pItemNode));
//指向下一个节点
pItemNode = pItemNode->pItemNext;
itemIndex++;
}
//指向SeparatorItem表的下一个节点
pSepNode = pSepNode->pSepNext;
}
//移动到敏感位置测试是否成功
SWIPELIST_SetScrollPos(hSwpLst, 15);
//
//遍历所有的数据并把每个SeparatorItem的上一个Item的SepSize重新设置一下绕过这个BUG
//
itemIndex = 0;
pSepNode = Header.pSepaHeader;
while (pSepNode != NULL) {
devCnt = 0;
pItemNode = pSepNode->pItemHead;
itemIndex++;
while (pItemNode != NULL) {
pItemNode = pItemNode->pItemNext;
if (pItemNode == NULL && pSepNode->pSepNext != NULL) {
//SWIPELIST_SetItemSize(hSwpLst, itemIndex, ITEM_SIZE + SEP_SIZE);
SWIPELIST_SetSepSize(hSwpLst, itemIndex, SEP_SIZE);
}
itemIndex++;
}
pSepNode = pSepNode->pSepNext;
}
while(1){
GUI_Delay(50);
}
SWIPELIST 自定义绘制
以下代码展示的是自定义绘制的处理,主要目的是指示哪一个Item获取了焦点
static int _OwnerDraw(const WIDGET_ITEM_DRAW_INFO * pDrawItemInfo) {
int width;//height,
GUI_RECT rInside;
Item_t *pItemData;
Separator_t * pSepaData;
//U32 pItemData;
GUI_COLOR color;
static GUI_COLOR colorBk = -1;
//int releaseItem;
if (colorBk == -1) {
colorBk = SWIPELIST_GetBkColor(pDrawItemInfo->hWin, SWIPELIST_CI_BK_SEP_ITEM);
}
switch (pDrawItemInfo->Cmd) {
case WIDGET_ITEM_GET_XSIZE:
WM_GetInsideRectEx(pDrawItemInfo->hWin, &rInside);
width = rInside.x1;
return width;
case WIDGET_ITEM_DRAW_TEXT:
GUI_SetTextMode(GUI_TM_TRANS);
SWIPELIST_OwnerDraw(pDrawItemInfo);
break;
case WIDGET_ITEM_DRAW_BACKGROUND:
//
//这里下面的代码是根据其Item参数是否有focuse来改变其背景颜色
//
pItemData = (Item_t *)SWIPELIST_GetItemUserData(pDrawItemInfo->hWin, pDrawItemInfo->ItemIndex); //从Item的User Data里获取到相关的数据
if (pItemData->ItemType == SEPA_TYPE) {
//SWIPELIST_OwnerDraw(pDrawItemInfo);
//color = SWIPELIST_GetBkColor(pDrawItemInfo->hWin, SWIPELIST_CI_BK_SEP_ITEM);
//color = GUI_DARKGRAY;
if (colorBk) {
color = colorBk;
}
}
else {
if (pItemData->Focus) {
//根据焦点状态绘制不同的background
//color = GUI_BLUE;
color = GUI_MAKE_COLOR(0xB06245);
}
else {
//color = GUI_DARKGRAY;
color = GUI_MAKE_COLOR(0x6B563E);
//GUI_ALLOC_AllocZero()
}
}
GUI_SetColor(color);
//GUI_ClearRect(pDrawItemInfo->x0, pDrawItemInfo->y0, pDrawItemInfo->x1, pDrawItemInfo->y1);
GUI_FillRect(pDrawItemInfo->x0, pDrawItemInfo->y0, pDrawItemInfo->x1, pDrawItemInfo->y1);
break;
default:
SWIPELIST_OwnerDraw(pDrawItemInfo);
break;
}
return 0;
}
核心部分删除Item的处理
删除Item所需要注意的事项前面以说明过了此部分展示的是删除需要处理的一些细节
static void _cbParent(WM_MESSAGE * pMsg) {
int ID;
int NCode;
static int i,cnt;
static int itemIndex, numItems, cpltDisItm;
static int tpOutPixel, btmInPixel; //窗口顶部露在外面和和底部露在里面的像素
int scrollPos;
SWIPELIST_Handle hSwp;
Item_t * pItemData;
itemIndex = 0;
cnt = 0;
switch (pMsg->MsgId) {
case WM_PAINT:
GUI_SetBkColor(GUI_MAKE_COLOR(0xFAFBEA));
GUI_Clear();
break;
case WM_NOTIFY_PARENT:
ID = WM_GetId(pMsg->hWinSrc);
NCode = pMsg->Data.v;
switch (NCode) {
case WM_NOTIFICATION_CLICKED:
break;
case WM_NOTIFICATION_RELEASED:
switch (ID) {
case GUI_ID_BUTTON0 :
if (LastFocus >= 0) { //需要大于0的原因是因为0一般都做为SeparatorItem
//
//取出SWPLST的句柄 且只有Item才能被删除
//
hSwp = WM_GetDialogItem(pMsg->hWin, GUI_ID_SWIPELIST0);
pItemData = (Item_t *)SWIPELIST_GetItemUserData(hSwp, LastFocus);
if (pItemData->ItemType == SEPA_TYPE) {
LastFocus = 0;
}
else {
//
//删除Item,并且在之前先获取到它的数据先删除数据再删除Item
//
pItemData = (Item_t *)SWIPELIST_GetItemUserData(hSwp, LastFocus);
DelItem(pItemData);
SWIPELIST_DeleteItem(hSwp, LastFocus);
//
//这里需要注意的是如果删除最后一个item时LastFocus便成为非法所以需要处理一下。
//
numItems = SWIPELIST_GetNumItems(hSwp);
LastFocus = (LastFocus >= numItems) ? numItems - 1 : LastFocus;
//标记需要在主函数里遍历一次两个表打印出来
Traversal = 1;
//
//如果删除后 最后的焦点是普通Item 标记焦点,否则焦点住上移动或清零
//
pItemData = (Item_t *)SWIPELIST_GetItemUserData(hSwp, LastFocus);
if (pItemData->ItemType == ITEM_TYPE) {
pItemData->Focus = 1;
}
else {
pItemData = (Item_t *)SWIPELIST_GetItemUserData(hSwp, LastFocus-1);
if (pItemData->ItemType == ITEM_TYPE) {
pItemData->Focus = 1;
LastFocus -= 1;
}else{
LastFocus = 0;
}
}
/*删除对齐的核心代码从这里开始*/
//
//删除后获得其scrollPos 根据其获得当前最顶部的Itemindex
//
scrollPos = -SWIPELIST_GetScrollPos(hSwp);
itemIndex = scrollPos / (ITEM_SIZE + SEP_SIZE);
//
//顶部Item移出的pixel和底部还Item还在窗口里面的pixel
//
tpOutPixel = scrollPos % (ITEM_SIZE + SEP_SIZE);
btmInPixel = (WM_GetWindowSizeY(hSwp) % (ITEM_SIZE + SEP_SIZE));
cpltDisItm = WM_GetWindowSizeY(hSwp) / (ITEM_SIZE + SEP_SIZE);
//
//计算出cnt是为了让
//
numItems = SWIPELIST_GetNumItems(hSwp);
cnt = (numItems <= cpltDisItm) ? numItems - 1 : itemIndex + cpltDisItm;
//cnt = itemIndex + 6;
if (tpOutPixel + btmInPixel >= ITEM_SIZE + SEP_SIZE) {
cnt += 1;
}
//
//从窗口内的第一个Item开始循环
//
for (i = itemIndex; i <= cnt; i++) {
if (i == numItems) break; //需要保证查找的ItemIndex不得越界
pItemData = (Item_t *)SWIPELIST_GetItemUserData(hSwp, i);
if (pItemData->ItemType == ITEM_TYPE) {
SWIPELIST_ItemDetachWindow(hSwp, pItemData->hAttachWin);
SWIPELIST_ItemAttachWindow(hSwp, i, pItemData->hAttachWin, 138, 1);
if (!WM_IsVisible(pItemData->hAttachWin)) {
WM_ShowWin(pItemData->hAttachWin);
}
}
}
//SWIPELIST_SetScrollPosItem(hSwp, 6);
}
}
break;
case GUI_ID_SWIPELIST0:
itemIndex = SWIPELIST_GetReleasedItem(pMsg->hWinSrc);
//
//清除上一个获得焦点的Item标记位
//
if (LastFocus > 0) {
pItemData = (Item_t *)SWIPELIST_GetItemUserData(pMsg->hWinSrc, LastFocus);
pItemData->Focus = 0;
}
//
//获取松开的Item的UserData 这里不用判断是Sepa Item 还是普通Item 因为只有Item才能有点击松开通知
//
pItemData = (Item_t *)SWIPELIST_GetItemUserData(pMsg->hWinSrc, itemIndex);//把其值强制转换成一个指针 //*pItemData &= 0xFFFFFF00; //这里清除温度的低8位
pItemData->Focus = 1;
//
//记录最后一次按下的Index
//
LastFocus = itemIndex;
//无效化Swpelist父窗口使之重绘
//
WM_InvalidateWindow(pMsg->hWin);
break;
} //end for switch(ID)
break;
case WM_NOTIFICATION_SEL_CHANGED:
//scrollPos = -SWIPELIST_GetScrollPos(hSwp);
//itemIndex = scrollPos / ITEM_SIZE;
//itemIndex +=6
break;
}//end for switch(NCode)
break;
default:
WM_DefaultProc(pMsg);
}
}
运行效果:
整个代码已上传到:github.