半透明信息显示浮动窗口的实现

实现目的

在一些画图软件中,经常需要向用户展示鼠标移动到的位置的对象的一些参数信息。此时,完成一个交互性友好的信息显示界面就相当的重要了。因为一个软件的好坏,在用户的眼中,第一感觉甚至是第一重要的就是视觉效果和可操作性。当然,软件本身的稳定性和效率也很重要。特别对于产品性的软件,在用户展示时,一个优秀的界面效果可以大大加深软件在用户心里的印象分。

功能简介

本功能是作者根据自身软件在用户实际使用过程中对交互性的更高要求而开发的。浮动窗口其实是一个对话框,设置为无标题的风格,然后进行自绘制而成。能够根据需要显示的内容自动调整窗口的大小,保证正好能够容纳需要显示的内容。如以下效果:

半透明信息显示浮动窗口的实现

图中黄色条为栏目分隔,同时显示其下内容所属的信息域。如图中表示下方信息为台风即时预报信息的内容。下方信息分成两栏,栏间绘制线条进行分割。左侧为信息标题,右侧为信息内容。比如台风名称是奥麦斯。

如果鼠标移动的位置,同时选中多项内容,那么,提示信息可以同时包括多个栏目,如下图:

半透明信息显示浮动窗口的实现

本图中包括两个栏目,即本公司船位置信息和美气导的等压线信息。从理论上讲,本功能可以支持任意多的栏目,前提是你的显示器能够容纳得下。

整个界面的效果是半透明的。包括透明度,字体大小和信息内容的颜色都可以由用户自行定义,配置界面如下:

半透明信息显示浮动窗口的实现

透明度范围为0-255;背景色列表框是作者在博客中已提供的一个自定义的线条、填充和颜色选择列表控件。

修改完成后,可以改变浮动窗口的显示效果,比如一种修改后的效果:

半透明信息显示浮动窗口的实现

这样,即使用户对浮动窗口内容的展示方式不满意,那么也完全可以由用户自行定义自己喜欢的风格。当然,在此基础上,可以进一步扩大自定义的范围。本例中,栏目分割条的背景色和信息内容标题文字的颜色是固定的,信息内容文字的颜色是背景色的反色,这些都可以考虑加入到自定义项中。

代码实现

以下是消息提示项的数据结构:

typedef struct _MSG_TIP//消息提示数据项描述信息 { CString sMsgItemName; //信息标题 CString sMsgItemInfo; //信息内容 COLORREF nInfoShowColor; //显示颜色(此项值目前暂时没有使用,原意是使每个栏目的文字颜色均可自定义;因为考虑到设定的颜色可能和背景色冲突,因此目前文字颜色使用背景色的反色) }MSG_TIP; typedef CArray<MSG_TIP,MSG_TIP&> CMsgTipArray;//消息提示内容

头文件,MsgTipDlg.h

#if !defined(AFX_MSGTIPDLG_H__9759E9A8_93DE_4003_BCB2_87F390641270__INCLUDED_) #define AFX_MSGTIPDLG_H__9759E9A8_93DE_4003_BCB2_87F390641270__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 // MsgTipDlg.h : header file // ///////////////////////////////////////////////////////////////////////////// // CMsgTipDlg dialog class CMsgTipDlg : public CDialog { // Construction public: CMsgTipDlg(CWnd* pParent = NULL); // standard constructor CSize ShowMsgTip(CMsgTipArray &arMsgTip); //供外部函数传递需要显示的内容,并计算尺寸 void UpdateTrans(); //修改透明度等配置参数 void DrawMsgTip(); //绘制提示信息 // Dialog Data //{{AFX_DATA(CMsgTipDlg) enum { IDD = IDD_MSGTIP_DLG }; //}}AFX_DATA // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CMsgTipDlg) public: virtual BOOL PreTranslateMessage(MSG* pMsg); protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: CMsgTipArray m_arMsgTip; //提示信息数组 CFont m_MsgTipFont; //用于消息提示的字体 int m_nTipNameWidth; //标题名最大宽度,决定竖线绘制位置 int m_nRowHeight; //行高,决定横线绘制位置 private: void DrawGrid(CDC *pDC,CRect &rc); //绘制网格 void DrawMsgTip(CDC *pDC,CPoint topPos,MSG_TIP &mt); //绘制提示信息(某一条) void DrawMsg(CDC *pDC); //绘制信息 // Generated message map functions //{{AFX_MSG(CMsgTipDlg) virtual BOOL OnInitDialog(); afx_msg void OnPaint(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AFX_MSGTIPDLG_H__9759E9A8_93DE_4003_BCB2_87F390641270__INCLUDED_)

CPP文件,MsgTipDlg.cpp

// MsgTipDlg.cpp : implementation file // #include "stdafx.h" #include "MsgTipDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CMsgTipDlg dialog CMsgTipDlg::CMsgTipDlg(CWnd* pParent /*=NULL*/) : CDialog(CMsgTipDlg::IDD, pParent) { //{{AFX_DATA_INIT(CMsgTipDlg) //}}AFX_DATA_INIT } void CMsgTipDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CMsgTipDlg) //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CMsgTipDlg, CDialog) //{{AFX_MSG_MAP(CMsgTipDlg) ON_WM_PAINT() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CMsgTipDlg message handlers BOOL CMsgTipDlg::OnInitDialog() { CDialog::OnInitDialog(); m_MsgTipFont.CreateFont( theApp.m_sysINIFile.GetMsgTipConfig().nMsgTipFontSize,// nHeight 0, // nWidth 0, // nEscapement 0, // nOrientation FW_SEMIBOLD, // nWeight FALSE, // bItalic FALSE, // bUnderline 0, // cStrikeOut DEFAULT_CHARSET, // nCharSet OUT_DEFAULT_PRECIS, // nOutPrecision CLIP_DEFAULT_PRECIS, // nClipPrecision DEFAULT_QUALITY, // nQuality DEFAULT_PITCH | FF_SWISS, // nPitchAndFamily "Times New Roman");//"MS Sans Serif");//Arial m_nTipNameWidth = 0; m_nRowHeight = 0; SetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE, GetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE)^0x80000); //WS_EX_LAYERED HINSTANCE hInst = LoadLibrary("User32.DLL"); if(hInst) { typedef BOOL (WINAPI *MYFUNC)(HWND,COLORREF,BYTE,DWORD); MYFUNC fun = NULL; //取得SetLayeredWindowAttributes函数指针 fun=(MYFUNC)GetProcAddress(hInst, "SetLayeredWindowAttributes"); if(fun) //fun(this->GetSafeHwnd(),0,400,2); fun(this->GetSafeHwnd(),0,theApp.m_sysINIFile.GetMsgTipConfig().nMsgTipTrans,2); FreeLibrary(hInst); } return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } //供外部函数传递需要显示的内容,同时获得需要显示的内容的尺寸,以决定本界面的大小 CSize CMsgTipDlg::ShowMsgTip(CMsgTipArray &arMsgTip) { CSize size(0,0); ////////////////////////////////////// m_arMsgTip.Copy(arMsgTip); int nSize = m_arMsgTip.GetSize(); if(nSize == 0) return size; CDC *pDC = GetDC(); pDC->SelectObject(&m_MsgTipFont); int nMaxNameLen = 0; int nMaxInfoLen = 0; int nMaxTitleLen = 0; CSize sz; for(int i=0; i<nSize; i++) { MSG_TIP mt = m_arMsgTip.GetAt(i); if(mt.nInfoShowColor == -1) { ::GetTextExtentPoint32(pDC->m_hDC, mt.sMsgItemName,mt.sMsgItemName.GetLength(),&sz); if(sz.cx > nMaxTitleLen) nMaxTitleLen = sz.cx; continue; } ::GetTextExtentPoint32(pDC->m_hDC, mt.sMsgItemName,mt.sMsgItemName.GetLength(),&sz); if(sz.cx > nMaxNameLen) nMaxNameLen = sz.cx; if(mt.sMsgItemInfo.GetLength() == 0) continue; else { ::GetTextExtentPoint32(pDC->m_hDC, mt.sMsgItemInfo,mt.sMsgItemInfo.GetLength(),&sz); if(sz.cx > nMaxInfoLen) nMaxInfoLen = sz.cx; } } m_nTipNameWidth = nMaxNameLen + 8; m_nRowHeight = sz.cy + 4; // int nTitleWidth = nMaxTitleLen; size.cx = m_nTipNameWidth + nMaxInfoLen+5; if(size.cx < nTitleWidth) size.cx = nTitleWidth; size.cx += 5; size.cy = m_nRowHeight * nSize; ReleaseDC(pDC); return size; } void CMsgTipDlg::OnPaint() { CPaintDC dc(this); // device context for painting DrawMsg(&dc); // Do not call CDialog::OnPaint() for painting messages } void CMsgTipDlg::DrawMsg(CDC *pDC) { CRect rc; GetClientRect(&rc); CDC memDC; memDC.CreateCompatibleDC(pDC); CBitmap bmp; bmp.CreateCompatibleBitmap(pDC,rc.Width(),rc.Height()); CBitmap *pOldBmp = memDC.SelectObject(&bmp); memDC.FillSolidRect(rc,theApp.m_sysINIFile.GetMsgTipConfig().nMsgTipBkColor); CFont *pOldFont = memDC.SelectObject(&m_MsgTipFont); DrawGrid(&memDC,rc); // int nSize = m_arMsgTip.GetSize(); CPoint topPos(0,0); for(int i=0; i<nSize; i++) { MSG_TIP mt = m_arMsgTip.GetAt(i); DrawMsgTip(&memDC,topPos,mt); topPos.y += m_nRowHeight; } pDC->BitBlt(0, 0, rc.Width(),rc.Height(), &memDC, 0, 0, SRCCOPY) ; pDC->SelectObject(pOldBmp); pDC->SelectObject(pOldFont); bmp.DeleteObject(); memDC.DeleteDC(); } void CMsgTipDlg::DrawMsgTip(CDC *pDC, CPoint topPos, MSG_TIP &mt) { int nBkMode = pDC->SetBkMode(TRANSPARENT); if(mt.nInfoShowColor == -1) { CRect rc; GetClientRect(&rc); CRect rowRc(topPos,CSize(rc.Width(),m_nRowHeight+2)); pDC->FillSolidRect(rowRc,COLOR_BUFF); COLORREF nTC = pDC->SetTextColor(COLOR_MAGENTA); pDC->TextOut(topPos.x+4,topPos.y+4,mt.sMsgItemName); pDC->SetTextColor(nTC); } else { COLORREF nTC = pDC->SetTextColor(COLOR_MAGENTA); pDC->TextOut(topPos.x+4,topPos.y+4,mt.sMsgItemName); nTC = pDC->SetTextColor(COLOR_WHITE-theApp.m_sysINIFile.GetMsgTipConfig().nMsgTipBkColor);//mt.nInfoShowColor);// pDC->TextOut(topPos.x+m_nTipNameWidth+8,topPos.y+4,mt.sMsgItemInfo); pDC->SetTextColor(nTC); } pDC->SetBkMode(nBkMode); } void CMsgTipDlg::DrawGrid(CDC *pDC,CRect &rc) { CPen pen(PS_SOLID,1,COLOR_BUFF); CPen *pOldPen = pDC->SelectObject(&pen); CPoint ptTop(m_nTipNameWidth,0); CPoint ptBottom(m_nTipNameWidth,rc.bottom); pDC->MoveTo(ptTop); pDC->LineTo(ptBottom); // int nSize = m_arMsgTip.GetSize(); for(int i=0; i<nSize-1; i++) { CPoint ptLeft(0,(m_nRowHeight)*(i+1)+2); CPoint ptRight(rc.right,(m_nRowHeight)*(i+1)+2); pDC->MoveTo(ptLeft); pDC->LineTo(ptRight); } pDC->SelectObject(pOldPen); } void CMsgTipDlg::UpdateTrans() { HINSTANCE hInst = LoadLibrary("User32.DLL"); if(hInst) { typedef BOOL (WINAPI *MYFUNC)(HWND,COLORREF,BYTE,DWORD); MYFUNC fun = NULL; //取得SetLayeredWindowAttributes函数指针 fun=(MYFUNC)GetProcAddress(hInst, "SetLayeredWindowAttributes"); if(fun) //fun(this->GetSafeHwnd(),0,400,2); fun(this->GetSafeHwnd(),0,theApp.m_sysINIFile.GetMsgTipConfig().nMsgTipTrans,2); FreeLibrary(hInst); } if(m_MsgTipFont.m_hObject != NULL) { m_MsgTipFont.DeleteObject(); m_MsgTipFont.CreateFont( theApp.m_sysINIFile.GetMsgTipConfig().nMsgTipFontSize,// nHeight 0, // nWidth 0, // nEscapement 0, // nOrientation FW_SEMIBOLD, // nWeight FALSE, // bItalic FALSE, // bUnderline 0, // cStrikeOut DEFAULT_CHARSET, // nCharSet OUT_DEFAULT_PRECIS, // nOutPrecision CLIP_DEFAULT_PRECIS, // nClipPrecision DEFAULT_QUALITY, // nQuality DEFAULT_PITCH | FF_SWISS, // nPitchAndFamily "Times New Roman");//Arial } } } void CMsgTipDlg::DrawMsgTip() { CDC *pDC = GetDC(); DrawMsg(pDC); ReleaseDC(pDC); } //鼠标不能移动到本界面,一旦移动到本界面,立即隐藏 BOOL CMsgTipDlg::PreTranslateMessage(MSG* pMsg) { if(pMsg->message == WM_MOUSEMOVE) { ShowWindow(SW_HIDE); } return CDialog::PreTranslateMessage(pMsg); }

外部调用:

//显示提示信息 void CPetrelProView::ShowMsgTip(BOOL bShow,CMsgTipArray &arMsgTip) { if(m_pMsgTipDlg == NULL) { m_pMsgTipDlg = new CMsgTipDlg(); m_pMsgTipDlg->Create(IDD_MSGTIP_DLG,this); } if(!bShow) { m_pMsgTipDlg->ShowWindow(SW_HIDE); } else { CSize size = m_pMsgTipDlg->ShowMsgTip(arMsgTip); if(size.cx == 0) return; CPoint pt; GetCursorPos(&pt); CRect rc(pt,size); rc.OffsetRect(15,20); CRect mRc; GetWindowRect(&mRc); if(rc.bottom > mRc.bottom) rc.OffsetRect(0,mRc.bottom-rc.bottom-5); if(rc.right > mRc.right) { rc.OffsetRect(-rc.Width()-30,0); } m_pMsgTipDlg->SetWindowPos(&wndTop,rc.left,rc.top,rc.Width(),rc.Height(),SWP_SHOWWINDOW|SWP_NOACTIVATE); m_pMsgTipDlg->DrawMsgTip(); } }

那么,剩下的工作,就是需要在每个实体类中增加类似这样的函数:

CMsgTipArray arMsgTip;
pDoc->m_pShipMan->GetMsgTipInfo(arMsgTip);
在OnMouseMove事件中,从各个实体类中获取对象选中的情况。

如下是一个例子:

void CShip::GetMsgTipInfo(CMsgTipArray &arMsgTip) { MSG_TIP mt; mt.nInfoShowColor = -1; mt.sMsgItemName = "它船位置信息"; mt.sMsgItemInfo = ""; arMsgTip.Add(mt); mt.sMsgItemInfo = m_ShipBaseInfo.sEngName; mt.sMsgItemName = "船舶名称"; mt.nInfoShowColor = COLOR_GRASS; arMsgTip.Add(mt); mt.sMsgItemInfo = m_ShipBaseInfo.sMMSIID; mt.sMsgItemName = "MMSI"; arMsgTip.Add(mt); CString sDate = m_ShipBaseInfo.tNowReptTime.Format("%Y-%m-%d %H:%M:%S"); mt.sMsgItemInfo = sDate; mt.sMsgItemName = "报告时间"; arMsgTip.Add(mt); CString sLat = CoordFormatDouble2Str(m_ShipBaseInfo.nowPos.dLatitude,true,theApp.m_sysINIFile.GetLatLongConfig().eLatLongFmt); CString sLong = CoordFormatDouble2Str(m_ShipBaseInfo.nowPos.dLongitude,false,theApp.m_sysINIFile.GetLatLongConfig().eLatLongFmt); mt.sMsgItemInfo = sLat; mt.sMsgItemName = "纬度"; arMsgTip.Add(mt); mt.sMsgItemInfo = sLong; mt.sMsgItemName = "经度"; arMsgTip.Add(mt); if(m_ShipBaseInfo.fNowCOG > 359.9) mt.sMsgItemInfo = "NA"; else mt.sMsgItemInfo.Format("%.1f°",m_ShipBaseInfo.fNowCOG); mt.sMsgItemName = "航向"; arMsgTip.Add(mt); if(m_ShipBaseInfo.fNowSOG > 102) mt.sMsgItemInfo = "NA"; else mt.sMsgItemInfo.Format("%.1f",m_ShipBaseInfo.fNowSOG); mt.sMsgItemName = "航速"; arMsgTip.Add(mt); if(m_ShipBaseInfo.wTrueHeading == 511) mt.sMsgItemInfo = "NA"; else mt.sMsgItemInfo.Format("%d°",m_ShipBaseInfo.wTrueHeading); mt.sMsgItemName = "船首向"; arMsgTip.Add(mt); if(abs(m_ShipBaseInfo.lROT) >= 720) mt.sMsgItemInfo = "NA"; else mt.sMsgItemInfo.Format("%d°/分",m_ShipBaseInfo.lROT); mt.sMsgItemName = "转向率"; arMsgTip.Add(mt); mt.sMsgItemName = "船位来源"; mt.sMsgItemInfo = m_ShipBaseInfo.sPosFromName; if(mt.sMsgItemInfo.GetLength() == 0) mt.sMsgItemInfo = m_ShipBaseInfo.sPosFromCode; arMsgTip.Add(mt); }

以上工作即可实现一个浮动的鼠标移动选中的对象的半透明的提示窗。当鼠标移开或者移动到提示窗上时,提示窗将立刻隐藏。