windows-nt/Source/XPSP1/NT/shell/shell32/cplnkele.cpp
2020-09-26 16:20:57 +08:00

997 lines
28 KiB
C++

//+-------------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (C) Microsoft Corporation, 2000
//
// File: cplnkele.cpp
//
// This module implements a 'link' element in the Control Panel's DUI
// view. Link elements have a title, infotip, icon and an associated
// command that is invoked when the link is selected. The CLinkElement
// object is an extension of the DUI::Button class. Direct UI automatically
// creates an instance of CLinkElement when a 'linkelement' item from
// cpview.ui is instantiated.
//
//--------------------------------------------------------------------------
#include "shellprv.h"
#include "cpviewp.h"
#include "cpaction.h"
#include "cpduihlp.h"
#include "cpguids.h"
#include "cpuiele.h"
#include "cplnkele.h"
#include "cputil.h"
#include "defviewp.h"
#include "dobjutil.h"
#include "ids.h"
using namespace CPL;
CLinkElement::CLinkElement(
void
) : m_pUiCommand(NULL),
m_eIconSize(eCPIMGSIZE(-1)),
m_hwndInfotip(NULL),
m_idTitle(0),
m_idIcon(0),
m_iDragState(DRAG_IDLE)
{
TraceMsg(TF_LIFE, "CLinkElement::CLinkElement, this = 0x%x", this);
SetRect(&m_rcDragBegin, 0, 0, 0, 0);
}
CLinkElement::~CLinkElement(
void
)
{
TraceMsg(TF_LIFE, "CLinkElement::~CLinkElement, this = 0x%x", this);
_Destroy();
}
//
// This is called by the DUI engine when the link element is
// created.
//
HRESULT
CLinkElement::Create( // [static]
DUI::Element **ppElement
)
{
HRESULT hr = E_OUTOFMEMORY;
CLinkElement *ple = DUI::HNewAndZero<CLinkElement>();
if (NULL != ple)
{
hr = ple->_Initialize();
if (FAILED(hr))
{
ple->Destroy();
ple = NULL;
}
}
*ppElement = ple;
return THR(hr);
}
//
// This is called by the Control Panel UI code creating
// the link element.
//
HRESULT
CLinkElement::Initialize(
IUICommand *pUiCommand,
eCPIMGSIZE eIconSize
)
{
ASSERT(NULL == m_pUiCommand);
ASSERT(NULL != pUiCommand);
(m_pUiCommand = pUiCommand)->AddRef();
m_eIconSize = eIconSize;
HRESULT hr = _CreateElementTitle();
if (SUCCEEDED(hr))
{
//
// We don't fail element creation if the icon
// cannot be created. We want to display the
// title without an icon so that we know there's
// a problem retrieving the icon.
//
THR(_CreateElementIcon());
}
//
// Note that we don't fail element creation if accessibility
// initialization fails.
//
THR(_InitializeAccessibility());
if (FAILED(hr))
{
ATOMICRELEASE(m_pUiCommand);
}
return THR(hr);
}
HRESULT
CLinkElement::_InitializeAccessibility(
void
)
{
HRESULT hr = THR(SetAccessible(true));
if (SUCCEEDED(hr))
{
hr = THR(SetAccRole(ROLE_SYSTEM_LINK));
if (SUCCEEDED(hr))
{
LPWSTR pszTitle;
hr = THR(_GetTitleText(&pszTitle));
if (SUCCEEDED(hr))
{
hr = THR(SetAccName(pszTitle));
CoTaskMemFree(pszTitle);
pszTitle = NULL;
if (SUCCEEDED(hr))
{
LPWSTR pszInfotip;
hr = THR(_GetInfotipText(&pszInfotip));
if (SUCCEEDED(hr))
{
hr = THR(SetAccDesc(pszInfotip));
CoTaskMemFree(pszInfotip);
pszInfotip = NULL;
if (SUCCEEDED(hr))
{
TCHAR szDefAction[80];
if (0 < LoadString(HINST_THISDLL,
IDS_CP_LINK_ACCDEFACTION,
szDefAction,
ARRAYSIZE(szDefAction)))
{
hr = THR(SetAccDefAction(szDefAction));
}
else
{
hr = THR(ResultFromLastError());
}
}
}
}
}
}
}
return THR(hr);
}
void
CLinkElement::OnDestroy(
void
)
{
_Destroy();
DUI::Button::OnDestroy();
}
void
CLinkElement::OnInput(
DUI::InputEvent *pev
)
{
if (GINPUT_MOUSE == pev->nDevice)
{
//
// Use a set of states to control our handling of
// the mouse inputs for drag/drop.
//
// DRAG_IDLE - We have not yet detected any drag activity.
// DRAG_HITTESTING - Waiting to see if user drags cursor a minimum distance.
// DRAG_DRAGGING - User did drag cursor a minimum distance and we're now
// inside the drag loop.
//
//
// START -+-> DRAG_IDLE --> [ GMOUSE_DRAG ] --> DRAG_HITTESTING --+
// | |
// | [ GMOUSE_DRAG + |
// | moved SM_CXDRAG |
// | or SM_CYDRAG ] |
// | |
// +-<--------------- [ GMOUSE_UP ] <--- DRAG_DRAGGING <---+
//
DUI::MouseEvent *pmev = (DUI::MouseEvent *)pev;
switch(pev->nCode)
{
case GMOUSE_UP:
m_iDragState = DRAG_IDLE;
break;
case GMOUSE_DRAG:
switch(m_iDragState)
{
case DRAG_IDLE:
{
//
// This is the same way comctl's listview calculates
// the begin-drag rect.
//
int dxClickRect = GetSystemMetrics(SM_CXDRAG);
int dyClickRect = GetSystemMetrics(SM_CYDRAG);
if (4 > dxClickRect)
{
dxClickRect = dyClickRect = 4;
}
//
// Remember where the mouse pointer is on our first
// indication that a drag operation is starting.
//
SetRect(&m_rcDragBegin,
pmev->ptClientPxl.x - dxClickRect,
pmev->ptClientPxl.y - dyClickRect,
pmev->ptClientPxl.x + dxClickRect,
pmev->ptClientPxl.y + dyClickRect);
m_iDragState = DRAG_HITTESTING;
break;
}
case DRAG_HITTESTING:
if (!PtInRect(&m_rcDragBegin, pmev->ptClientPxl))
{
//
// Begin the drag/drop operation only if we've moved the mouse
// outside the "drag begin" rectangle. This prevents us from
// confusing a normal click with a drag/drop operation.
//
m_iDragState = DRAG_DRAGGING;
//
// Position the drag point at the middle of the item's image.
//
UINT cxIcon = 32;
UINT cyIcon = 32;
CPL::ImageDimensionsFromDesiredSize(m_eIconSize, &cxIcon, &cyIcon);
_BeginDrag(cxIcon / 2, cyIcon / 2);
}
break;
case DRAG_DRAGGING:
break;
}
break;
default:
break;
}
}
Button::OnInput(pev);
}
void
CLinkElement::OnEvent(
DUI::Event *pev
)
{
if (DUI::Button::Click == pev->uidType)
{
pev->fHandled = true;
DUI::ButtonClickEvent * pbe = (DUI::ButtonClickEvent *) pev;
if (1 != pbe->nCount)
{
return; // ingore additional clicks - don't forward.
}
_OnSelected();
}
else if (DUI::Button::Context == pev->uidType)
{
DUI::ButtonContextEvent *peButton = reinterpret_cast<DUI::ButtonContextEvent *>(pev);
_OnContextMenu(peButton);
pev->fHandled = true;
}
Button::OnEvent(pev);
}
void
CLinkElement::OnPropertyChanged(
DUI::PropertyInfo *ppi,
int iIndex,
DUI::Value *pvOld,
DUI::Value *pvNew
)
{
//
// Don't trace this function. It's called very often.
//
// Perform default processing.
//
Button::OnPropertyChanged(ppi, iIndex, pvOld, pvNew);
if (IsProp(MouseWithin))
{
_OnMouseOver(pvNew);
}
}
//
// Called to begin a drag-drop operation from the control panel.
// This is used for dragging CPL applet icons to shell folders
// for shortcut creation.
//
HRESULT
CLinkElement::_BeginDrag(
int iClickPosX,
int iClickPosY
)
{
DBG_ENTER(FTF_CPANEL, "CLinkElement::_BeginDrag");
HRESULT hr = E_FAIL;
HRESULT hrCoInit = SHCoInitialize();
if (SUCCEEDED(hrCoInit))
{
hr = hrCoInit;
IDataObject *pdtobj;
hr = _GetDragDropData(&pdtobj);
if (SUCCEEDED(hr))
{
//
// Ignore any failure to set the drag image. Drag images
// are not supported on some video configurations.
// In these cases, we still want to be able to create a shortcut.
//
THR(_SetDragImage(pdtobj, iClickPosX, iClickPosY));
HWND hwndRoot;
hr = THR(Dui_GetElementRootHWND(this, &hwndRoot));
if (SUCCEEDED(hr))
{
DWORD dwEffect = DROPEFFECT_LINK;
hr = THR(SHDoDragDrop(hwndRoot, pdtobj, NULL, dwEffect, &dwEffect));
}
pdtobj->Release();
}
SHCoUninitialize(hrCoInit);
}
DBG_EXIT_HRES(FTF_CPANEL, "CLinkElement::_BeginDrag", hr);
return THR(hr);
}
//
// Get and prepare the data object used in a drag-drop operation.
// The returned data object is suitable for use by SHDoDragDrop.
//
HRESULT
CLinkElement::_GetDragDropData(
IDataObject **ppdtobj
)
{
DBG_ENTER(FTF_CPANEL, "CLinkElement::_GetDragDropData");
ASSERT(NULL != ppdtobj);
ASSERT(!IsBadWritePtr(ppdtobj, sizeof(*ppdtobj)));
ASSERT(NULL != m_pUiCommand);
*ppdtobj = NULL;
ICpUiCommand *puic;
HRESULT hr = m_pUiCommand->QueryInterface(IID_PPV_ARG(ICpUiCommand, &puic));
if (SUCCEEDED(hr))
{
//
// Note that this call will fail with E_NOTIMPL for links that don't
// provide drag-drop data. Only CPL applet links provide data.
// This is how we limit drag-drop to only CPL applets.
//
IDataObject *pdtobj;
hr = THR(puic->GetDataObject(&pdtobj));
if (SUCCEEDED(hr))
{
hr = _SetPreferredDropEffect(pdtobj, DROPEFFECT_LINK);
if (SUCCEEDED(hr))
{
(*ppdtobj = pdtobj)->AddRef();
}
pdtobj->Release();
}
puic->Release();
}
DBG_EXIT_HRES(FTF_CPANEL, "CLinkElement::_GetDragDropData", hr);
return THR(hr);
}
HRESULT
CLinkElement::_SetPreferredDropEffect(
IDataObject *pdtobj,
DWORD dwEffect
)
{
DBG_ENTER(FTF_CPANEL, "CLinkElement::_SetPreferredDropEffect");
HRESULT hr = S_OK;
static CLIPFORMAT cf;
if ((CLIPFORMAT)0 == cf)
{
cf = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
if ((CLIPFORMAT)0 == cf)
{
hr = THR(ResultFromLastError());
}
}
if (SUCCEEDED(hr))
{
hr = THR(DataObj_SetDWORD(pdtobj, cf, dwEffect));
}
DBG_EXIT_HRES(FTF_CPANEL, "CLinkElement::_SetPreferredDropEffect", hr);
return THR(hr);
}
//
// Set up the drag image in the data object so that our icon is
// displayed during the drag operation.
//
// I took this code from the old webvw project's fldricon.cpp
// implementation (shell\ext\webvw\fldricon.cpp). It seems to
// work just fine.
//
HRESULT
CLinkElement::_SetDragImage(
IDataObject *pdtobj,
int iClickPosX,
int iClickPosY
)
{
DBG_ENTER(FTF_CPANEL, "CLinkElement::_SetDragImage");
ASSERT(NULL != pdtobj);
HRESULT hr = S_OK;
HDC hdc = CreateCompatibleDC(NULL);
if (NULL == hdc)
{
hr = THR(ResultFromLastError());
}
else
{
HBITMAP hbm;
LONG lBitmapWidth;
LONG lBitmapHeight;
hr = _GetDragImageBitmap(&hbm, &lBitmapWidth, &lBitmapHeight);
if (SUCCEEDED(hr))
{
IDragSourceHelper *pdsh;
hr = CoCreateInstance(CLSID_DragDropHelper,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARG(IDragSourceHelper, &pdsh));
if (SUCCEEDED(hr))
{
BITMAPINFOHEADER bmi = {0};
BITMAP bm = {0};
UINT uBufferOffset = 0;
//
// This is a screwy procedure to use GetDIBits.
// See knowledge base Q80080
//
if (GetObject(hbm, sizeof(BITMAP), &bm))
{
bmi.biSize = sizeof(BITMAPINFOHEADER);
bmi.biWidth = bm.bmWidth;
bmi.biHeight = bm.bmHeight;
bmi.biPlanes = 1;
bmi.biBitCount = bm.bmPlanes * bm.bmBitsPixel;
//
// This needs to be one of these 4 values
//
if (bmi.biBitCount <= 1)
bmi.biBitCount = 1;
else if (bmi.biBitCount <= 4)
bmi.biBitCount = 4;
else if (bmi.biBitCount <= 8)
bmi.biBitCount = 8;
else
bmi.biBitCount = 24;
bmi.biCompression = BI_RGB;
//
// Total size of buffer for info struct and color table
//
uBufferOffset = sizeof(BITMAPINFOHEADER) +
((bmi.biBitCount == 24) ? 0 : ((1 << bmi.biBitCount) * sizeof(RGBQUAD)));
//
// Buffer for bitmap bits, so we can copy them.
//
BYTE *psBits = (BYTE *)SHAlloc(uBufferOffset);
if (NULL == psBits)
{
hr = THR(E_OUTOFMEMORY);
}
else
{
//
// Put bmi into the memory block
//
CopyMemory(psBits, &bmi, sizeof(BITMAPINFOHEADER));
//
// Get the size of the buffer needed for bitmap bits
//
if (!GetDIBits(hdc, hbm, 0, 0, NULL, (BITMAPINFO *) psBits, DIB_RGB_COLORS))
{
hr = THR(ResultFromLastError());
}
else
{
//
// Realloc our buffer to be big enough
//
psBits = (BYTE *)SHRealloc(psBits, uBufferOffset + ((BITMAPINFOHEADER *) psBits)->biSizeImage);
if (NULL == psBits)
{
hr = THR(E_OUTOFMEMORY);
}
else
{
//
// Fill the buffer
//
if (!GetDIBits(hdc,
hbm,
0,
bmi.biHeight,
(void *)(psBits + uBufferOffset),
(BITMAPINFO *)psBits,
DIB_RGB_COLORS))
{
hr = THR(ResultFromLastError());
}
else
{
SHDRAGIMAGE shdi; // Drag images struct
shdi.hbmpDragImage = CreateBitmapIndirect(&bm);
if (NULL == shdi.hbmpDragImage)
{
hr = THR(ResultFromLastError());
}
else
{
//
// Set the drag image bitmap
//
if (SetDIBits(hdc,
shdi.hbmpDragImage,
0,
lBitmapHeight,
(void *)(psBits + uBufferOffset),
(BITMAPINFO *)psBits,
DIB_RGB_COLORS))
{
//
// Populate the drag image structure
//
shdi.sizeDragImage.cx = lBitmapWidth;
shdi.sizeDragImage.cy = lBitmapHeight;
shdi.ptOffset.x = iClickPosX;
shdi.ptOffset.y = iClickPosY;
shdi.crColorKey = 0;
//
// Set the drag image
//
hr = pdsh->InitializeFromBitmap(&shdi, pdtobj);
}
else
{
hr = THR(ResultFromLastError());
}
if (FAILED(hr))
{
DeleteObject(shdi.hbmpDragImage);
}
}
}
}
}
if (NULL != psBits)
{
SHFree(psBits);
}
}
}
pdsh->Release();
}
DeleteObject(hbm);
}
DeleteDC(hdc);
}
DBG_EXIT_HRES(FTF_CPANEL, "CLinkElement::_SetDragImage", hr);
return THR(hr);
}
HRESULT
CLinkElement::_GetDragImageBitmap(
HBITMAP *phbm,
LONG *plWidth,
LONG *plHeight
)
{
DBG_ENTER(FTF_CPANEL, "CLinkElement::_GetDragImageBitmap");
ASSERT(NULL != phbm);
ASSERT(!IsBadWritePtr(phbm, sizeof(*phbm)));
ASSERT(NULL != plWidth);
ASSERT(!IsBadWritePtr(plWidth, sizeof(*plWidth)));
ASSERT(NULL != plHeight);
ASSERT(!IsBadWritePtr(plHeight, sizeof(*plHeight)));
*phbm = NULL;
*plWidth = 0;
*plHeight = 0;
HICON hIcon;
HRESULT hr = _GetElementIcon(&hIcon);
if (SUCCEEDED(hr))
{
ICONINFO iconinfo;
if (GetIconInfo(hIcon, &iconinfo))
{
BITMAP bm;
if (GetObject(iconinfo.hbmColor, sizeof(bm), &bm))
{
*plWidth = bm.bmWidth;
*plHeight = bm.bmHeight;
*phbm = iconinfo.hbmColor;
}
else
{
DeleteObject(iconinfo.hbmColor);
hr = THR(ResultFromLastError());
}
DeleteObject(iconinfo.hbmMask);
}
else
{
hr = THR(ResultFromLastError());
}
DestroyIcon(hIcon);
}
DBG_EXIT_HRES(FTF_CPANEL, "CLinkElement::_GetDragImageBitmap", hr);
return THR(hr);
}
HRESULT
CLinkElement::_Initialize(
void
)
{
HRESULT hr = Button::Initialize(AE_Mouse | AE_Keyboard);
if (SUCCEEDED(hr))
{
hr = _AddOrDeleteAtoms(true);
}
return THR(hr);
}
void
CLinkElement::_Destroy(
void
)
{
if (NULL != m_hwndInfotip && IsWindow(m_hwndInfotip))
{
SHDestroyInfotipWindow(&m_hwndInfotip);
}
ATOMICRELEASE(m_pUiCommand);
_AddOrDeleteAtoms(false);
}
HRESULT
CLinkElement::_AddOrDeleteAtoms(
bool bAdd
)
{
struct CPL::ATOMINFO rgAtomInfo[] = {
{ L"title", &m_idTitle },
{ L"icon", &m_idIcon },
};
HRESULT hr = Dui_AddOrDeleteAtoms(rgAtomInfo, ARRAYSIZE(rgAtomInfo), bAdd);
return THR(hr);
}
HRESULT
CLinkElement::_CreateElementTitle(
void
)
{
LPWSTR pszTitle;
HRESULT hr = _GetTitleText(&pszTitle);
if (SUCCEEDED(hr))
{
hr = Dui_SetDescendentElementText(this, L"title", pszTitle);
CoTaskMemFree(pszTitle);
}
return THR(hr);
}
HRESULT
CLinkElement::_CreateElementIcon(
void
)
{
HICON hIcon;
HRESULT hr = _GetElementIcon(&hIcon);
if (SUCCEEDED(hr))
{
hr = Dui_SetDescendentElementIcon(this, L"icon", hIcon);
if (FAILED(hr))
{
DestroyIcon(hIcon);
}
}
return THR(hr);
}
HRESULT
CLinkElement::_GetElementIcon(
HICON *phIcon
)
{
ASSERT(NULL != phIcon);
ASSERT(!IsBadWritePtr(phIcon, sizeof(*phIcon)));
ASSERT(NULL != m_pUiCommand);
*phIcon = NULL;
ICpUiElementInfo *pei;
HRESULT hr = m_pUiCommand->QueryInterface(IID_PPV_ARG(ICpUiElementInfo, &pei));
if (SUCCEEDED(hr))
{
hr = pei->LoadIcon(m_eIconSize, phIcon);
pei->Release();
}
return THR(hr);
}
HRESULT
CLinkElement::_OnContextMenu(
DUI::ButtonContextEvent *peButton
)
{
DBG_ENTER(FTF_CPANEL, "CLinkElement::_OnContextMenu");
ICpUiCommand *pcmd;
HRESULT hr = m_pUiCommand->QueryInterface(IID_PPV_ARG(ICpUiCommand, &pcmd));
if (SUCCEEDED(hr))
{
HWND hwndRoot;
hr = Dui_GetElementRootHWND(this, &hwndRoot);
if (SUCCEEDED(hr))
{
if (-1 == peButton->pt.x)
{
//
// Keyboard context menu.
//
SIZE size;
hr = Dui_GetElementExtent(this, &size);
if (SUCCEEDED(hr))
{
peButton->pt.x = size.cx / 2;
peButton->pt.y = size.cy / 2;
}
}
POINT pt;
hr = Dui_MapElementPointToRootHWND(this, peButton->pt, &pt);
if (SUCCEEDED(hr))
{
if (ClientToScreen(hwndRoot, &pt))
{
//
// InvokeContextMenu returns S_FALSE if the command doesn't
// provide a context menu.
//
hr = pcmd->InvokeContextMenu(hwndRoot, &pt);
}
else
{
hr = CPL::ResultFromLastError();
}
}
}
pcmd->Release();
}
else if (E_NOINTERFACE == hr)
{
hr = S_FALSE;
}
DBG_EXIT_HRES(FTF_CPANEL, "CLinkElement::_OnContextMenu", hr);
return THR(hr);
}
HRESULT
CLinkElement::_OnSelected(
void
)
{
ASSERT(NULL != m_pUiCommand);
//
// Delay navigation until double-click time times out occurs.
//
// KB: gpease 05-APR-2001 Fix for Whistler Bug #338552 (and others)
//
// Delaying this prevents the "second click" from being applied
// to the newly navigated frame. Previously, if there happen to
// be a new link in the new frame at the same mouse point at
// which the previous navigation occured, the new link would have
// received the 2nd click and we'd navigate that link as well. This
// causes the current frame to get the 2nd click which we ignore
// since we only care about the single click (see OnEvent above).
//
HWND hwndRoot;
HRESULT hr = Dui_GetElementRootHWND(this, &hwndRoot);
if (SUCCEEDED(hr))
{
SendMessage(hwndRoot, WM_USER_DELAY_NAVIGATION, (WPARAM) NULL, (LPARAM) m_pUiCommand);
}
return THR(hr);
}
void
CLinkElement::_OnMouseOver(
DUI::Value *pvNewMouseWithin
)
{
_ShowInfotipWindow(pvNewMouseWithin->GetBool());
}
//
// Retrieve the title text for the element.
// Caller must free returned buffer using CoTaskMemFree.
//
HRESULT
CLinkElement::_GetTitleText(
LPWSTR *ppszTitle
)
{
ASSERT(NULL != m_pUiCommand);
ASSERT(NULL != ppszTitle);
ASSERT(!IsBadWritePtr(ppszTitle, sizeof(*ppszTitle)));
*ppszTitle = NULL;
ICpUiElementInfo *pei;
HRESULT hr = m_pUiCommand->QueryInterface(IID_PPV_ARG(ICpUiElementInfo, &pei));
if (SUCCEEDED(hr))
{
hr = pei->LoadName(ppszTitle);
pei->Release();
}
return THR(hr);
}
//
// Retrieve the infotip text for the element.
// Caller must free returned buffer using CoTaskMemFree.
//
HRESULT
CLinkElement::_GetInfotipText(
LPWSTR *ppszInfotip
)
{
ASSERT(NULL != m_pUiCommand);
ASSERT(NULL != ppszInfotip);
ASSERT(!IsBadWritePtr(ppszInfotip, sizeof(*ppszInfotip)));
*ppszInfotip = NULL;
ICpUiElementInfo *pei;
HRESULT hr = m_pUiCommand->QueryInterface(IID_PPV_ARG(ICpUiElementInfo, &pei));
if (SUCCEEDED(hr))
{
hr = pei->LoadTooltip(ppszInfotip);
pei->Release();
}
return THR(hr);
}
HRESULT
CLinkElement::_ShowInfotipWindow(
bool bShow
)
{
HRESULT hr = S_OK;
if (bShow)
{
if (NULL == m_hwndInfotip)
{
HWND hwndRoot;
hr = THR(Dui_GetElementRootHWND(this, &hwndRoot));
if (SUCCEEDED(hr))
{
LPWSTR pszInfotip;
hr = THR(_GetInfotipText(&pszInfotip));
if (SUCCEEDED(hr))
{
hr = THR(SHCreateInfotipWindow(hwndRoot, pszInfotip, &m_hwndInfotip));
CoTaskMemFree(pszInfotip);
}
}
}
if (SUCCEEDED(hr))
{
hr = THR(SHShowInfotipWindow(m_hwndInfotip, TRUE));
}
}
else
{
if (NULL != m_hwndInfotip)
{
hr = THR(SHDestroyInfotipWindow(&m_hwndInfotip));
}
}
return THR(hr);
}
//
// ClassInfo (must appear after property definitions).
//
DUI::IClassInfo *CLinkElement::Class = NULL;
HRESULT CLinkElement::Register()
{
return DUI::ClassInfo<CLinkElement,DUI::Button>::Register(L"linkelement", NULL, 0);
}