Para implementar todos esses recursos avançados (como arrastar abas com efeito ghost, abas flutuantes transparentes e reanexação automática) diretamente no Tab Control nativo do Windows (que a HwGUI encapsula), teríamos limitações severas do próprio sistema operacional.A melhor estratégia em Harbour e HwGUI para entregar essa experiência moderna e fluida é criar um componente customizado (Custom Control). Nós desenhamos as abas manualmente usando a classe HPanel, capturamos os eventos do mouse (bLButtonDown, bLButtonUp, bMouseMove) e gerenciamos a exibição das páginas.
Abaixo está o código-fonte estruturado de uma solução robusta que implementa os requisitos solicitados.
Código: Selecionar todos
#include "hwgui.ch"
#include "hbclass.ch"
#define TAB_HEIGHT 35
// Classe que representa cada aba individualmente
CLASS MTabItem
DATA cTitle
DATA hBitmap // Ícone à esquerda (Requisito 1)
DATA lEnabled // Estado da página (Requisito 2)
DATA oPanelPage // Container do conteúdo da página
DATA nWidth
DATA nXProgressive // Posição X calculada para desenho
// Retângulos de colisão para cliques internos
DATA rChkBox
DATA rBtnClose
METHOD New( cTitle, hBitmap, oPanelPage )
ENDCLASS
METHOD New( cTitle, hBitmap, oPanelPage ) CLASS MTabItem
::cTitle := cTitle
::hBitmap := hBitmap
::lEnabled := .T.
::oPanelPage := oPanelPage
::nWidth := 120 + Iif( hBitmap != Nil, 20, 0 )
RETURN Self
// Classe Principal do Controle de Abas Moderno
CLASS ModernTabControl FROM HPanel
DATA aTabs INIT {}
DATA nActiveTab INIT 0
// Controle de Drag & Drop interno e externo (Requisitos 4, 5 e 6)
DATA lIsDragging INIT .F.
DATA nDraggedIdx INIT 0
DATA nMouseOffsetX INIT 0
DATA oGhostWindow INIT Nil
DATA lIsFloating INIT .F.
METHOD New( oWindow, nId, nLeft, nTop, nWidth, nHeight )
METHOD AddPage( cTitle, cBitmapPath )
METHOD RedrawTabs()
METHOD OnClickTab( nX, nY )
METHOD OnMouseMoveTab( nX, nY, nFlags )
METHOD OnReleaseTab()
METHOD DetachTab( nIdx )
METHOD ReattachTab( nIdx )
ENDCLASS
METHOD New( oWindow, nId, nLeft, nTop, nWidth, nHeight ) CLASS ModernTabControl
// Inicializa o painel base que conterá o cabeçalho das abas
::HPanel:New( oWindow, nId, , nLeft, nTop, nWidth, TAB_HEIGHT )
// Vincula os blocos de código para capturar os movimentos do mouse (Requisito 6)
::bPaint := { |o| ::RedrawTabs() }
::bLButtonDown := { |o, x, y| ::OnClickTab( x, y ) }
::bMouseMove := { |o, x, y, f| ::OnMouseMoveTab( x, y, f ) }
::bLButtonUp := { |o| ::OnReleaseTab() }
RETURN Self
METHOD AddPage( cTitle, cBitmapPath ) CLASS ModernTabControl
LOCAL hBmp := Nil
LOCAL oPagePanel
LOCAL oParentWin := ::oParent
if !Empty( cBitmapPath ) .and. File( cBitmapPath )
hBmp := HBitmap():AddFile( cBitmapPath ) Mills():Handle
endif
// Cria o painel oculto que guardará os controles desta página específica
INIT PANEL oPagePanel OF oParentWin ;
STYLE WS_CHILD ;
POS ::nLeft, ::nTop + TAB_HEIGHT ;
SIZE ::nWidth, ::nHeight - TAB_HEIGHT
oPagePanel:Hide()
AAdd( ::aTabs, MTabItem():New( cTitle, hBmp, oPagePanel ) )
if ::nActiveTab == 0
::nActiveTab := 1
oPagePanel:Show()
endif
::Refresh()
RETURN oPagePanel
METHOD RedrawTabs() CLASS ModernTabControl
LOCAL oPS := ::GetDC()
LOCAL nX := 5
LOCAL i, oTab
// Limpa o fundo do cabeçalho
SelectObject( oPS, GetStockObject( 5 ) ) // LTGRAY_BRUSH
Rectangle( oPS, 0, 0, ::nWidth, TAB_HEIGHT )
FOR i := 1 TO Len( ::aTabs )
oTab := ::aTabs[i]
oTab:nXProgressive := nX
// Define cor se a aba estiver ativa ou desabilitada
IF i == ::nActiveTab
SelectObject( oPS, GetStockObject( 0 ) ) // WHITE_BRUSH
ELSE
SelectObject( oPS, GetStockObject( 4 ) ) // GRAY_BRUSH
ENDIF
// Desenha o corpo da aba (Animação de slide simulada via redesenho posicional)
Rectangle( oPS, nX, 2, nX + oTab:nWidth, TAB_HEIGHT )
// 1. Desenha o Ícone à esquerda se existir
IF oTab:hBitmap != Nil
DrawBitmap( oPS, oTab:hBitmap, 0, nX + 5, 8, 16, 16 )
nX += 20
ENDIF
// Desenha o Texto do Título
IF !oTab:lEnabled
SetTextColor( oPS, RGB( 128, 128, 128 ) ) // Texto cinza se disabled
ELSE
SetTextColor( oPS, RGB( 0, 0, 0 ) )
ENDIF
DrawText( oPS, oTab:cTitle, nX + 5, 10, nX + 70, TAB_HEIGHT )
// 2. Checkbox para propriedade Enabled/Disabled (À direita do título)
oTab:rChkBox := { nX + 75, 10, nX + 87, 22 ]
// Simula visualmente o Checkbox marcado (.T.) ou desmarcado (.F.)
Rectangle( oPS, oTab:rChkBox[1], oTab:rChkBox[2], oTab:rChkBox[3], oTab:rChkBox[4] )
IF oTab:lEnabled
// Desenha um "X" interno indicando marcado
MoveTo( oPS, oTab:rChkBox[1], oTab:rChkBox[2] )
LineTo( oPS, oTab:rChkBox[3], oTab:rChkBox[4] )
ENDIF
// 3. Botão Fechar "X" mais à direita
oTab:rBtnClose := { nX + 95, 10, nX + 107, 22 ]
SetTextColor( oPS, RGB( 200, 0, 0 ) )
DrawText( oPS, "X", oTab:rBtnClose[1], oTab:rBtnClose[2], oTab:rBtnClose[3], oTab:rBtnClose[4] )
nX += oTab:nWidth - Iif( oTab:hBitmap != Nil, 20, 0 ) + 5
NEXT
::ReleaseDC()
RETURN Nil
METHOD OnClickTab( nX, nY ) CLASS ModernTabControl
LOCAL i, oTab
FOR i := 1 TO Len( ::aTabs )
oTab := ::aTabs[i]
// Verifica se o clique foi dentro da área desta aba específica
IF nX >= oTab:nXProgressive .AND. nX <= ( oTab:nXProgressive + oTab:nWidth )
// Verifica clique no Checkbox (Ativar/Desativar - Requisito 2)
IF nX >= oTab:rChkBox[1] .AND. nX <= oTab:rChkBox[3] .AND. nY >= oTab:rChkBox[2] .AND. nY <= oTab:rChkBox[4]
oTab:lEnabled := !oTab:lEnabled
IF !oTab:lEnabled .AND. ::nActiveTab == i
oTab:oPanelPage:Hide()
ELSEIF oTab:lEnabled .AND. ::nActiveTab == i
oTab:oPanelPage:Show()
ENDIF
::RedrawTabs()
RETURN Nil
ENDIF
// Verifica clique no Botão Fechar (Requisito 3)
IF nX >= oTab:rBtnClose[1] .AND. nX <= oTab:rBtnClose[3] .AND. nY >= oTab:rBtnClose[2] .AND. nY <= oTab:rBtnClose[4]
oTab:oPanelPage:End() // Destrói o painel de conteúdo
ADel( ::aTabs, i )
ASize( ::aTabs, Len( ::aTabs ) - 1 )
::nActiveTab := Min( ::nActiveTab, Len( ::aTabs ) )
IF ::nActiveTab > 0
::aTabs[::nActiveTab]:oPanelPage:Show()
ENDIF
::RedrawTabs()
RETURN Nil
ENDIF
// Se a aba estiver desabilitada, não permite focar nem arrastar
IF !oTab:lEnabled ; RETURN Nil ; ENDIF
// Ativa o Drag & Drop interno/externo (Requisito 4 e 6)
::lIsDragging := .T.
::nDraggedIdx := i
::nMouseOffsetX := nX - oTab:nXProgressive
IF ::nActiveTab != i
::aTabs[::nActiveTab]:oPanelPage:Hide()
::nActiveTab := i
oTab:oPanelPage:Show()
ENDIF
::RedrawTabs()
EXIT
ENDIF
NEXT
RETURN Nil
METHOD OnMouseMoveTab( nX, nY, nFlags ) CLASS ModernTabControl
LOCAL oTab, i, nTargetIdx
LOCAL nGlobalX, nGlobalY
IF !::lIsDragging
RETURN Nil
ENDIF
oTab := ::aTabs[::nDraggedIdx]
// --- DRAG INTERNO: Rearranjo de Abas (Requisito 4 e Requisito 6 - Smooth sliding) ---
IF nY >= 0 .AND. nY <= TAB_HEIGHT .AND. nX >= 0 .AND. nX <= ::nWidth
IF ::lIsFloating
// Se estava flutuando e voltou para a barra, reanexa (Requisito 6 - Auto-reattach)
::ReattachTab( ::nDraggedIdx )
RETURN Nil
ENDIF
// Detecta se moveu o suficiente para trocar de posição com a aba vizinha
FOR i := 1 TO Len( ::aTabs )
IF i != ::nDraggedIdx
IF nX > ::aTabs[i]:nXProgressive .AND. nX < (::aTabs[i]:nXProgressive + ::aTabs[i]:nWidth)
// Efeito Swap / Smooth sliding: Inverte as posições no Array e força o redesenho
nTargetIdx := i
ASwap( ::aTabs, ::nDraggedIdx, nTargetIdx )
::nActiveTab := nTargetIdx
::nDraggedIdx := nTargetIdx
::RedrawTabs()
EXIT
ENDIF
ENDIF
NEXT
// --- DRAG EXTERNO: Tear-off / Aba Flutuante (Requisito 5 e Requisito 6 - Ghost preview) ---
ELSE
// O mouse saiu dos limites verticais da barra de abas: Destacar Aba
IF !::lIsFloating
::DetachTab( ::nDraggedIdx )
ENDIF
// Atualiza a posição da janela flutuante dinamicamente para seguir o cursor
// (Requisito 6 - Semi-transparent floating tab following cursor)
nGlobalX := wlang_GetCursorX() // Captura coordenada absoluta da tela
nGlobalY := wlang_GetCursorY()
IF ::oGhostWindow != Nil
MoveWindow( ::oGhostWindow:handle, nGlobalX - ::nMouseOffsetX, nGlobalY - 15, oTab:nWidth, 150, .T. )
ENDIF
ENDIF
RETURN Nil
METHOD OnReleaseTab() CLASS ModernTabControl
IF !::lIsDragging
RETURN Nil
ENDIF
::lIsDragging := .F.
// Se foi solta fora (Flutuando), confirma o estado estável da janela destacada
IF ::lIsFloating
// Remove o efeito de transparência Ghost e fixa a janela onde ela foi solta
SetLayeredWindowAttributes( ::oGhostWindow:handle, 0, 255, 2 )
::oGhostWindow:Refresh()
ENDIF
::nDraggedIdx := 0
RETURN Nil
METHOD DetachTab( nIdx ) CLASS ModernTabControl
LOCAL oTab := ::aTabs[nIdx]
::lIsFloating := .T.
oTab:oPanelPage:Hide()
// Cria uma Janela flutuante no estilo TOOLWINDOW (Requisito 5 e 6)
INIT WINDOW ::oGhostWindow MAIN ;
STYLE WS_POPUP + WS_VISIBLE + WS_CAPTION + WS_SYSMENU ;
EXSTYLE WS_EX_TOOLWINDOW + WS_EX_LAYERED ; // LAYERED habilita opacidade/semi-transparência
TITLE oTab:cTitle ;
SIZE oTab:nWidth + 100, 300
// Define a semi-transparência do efeito "Ghost Rectangle/Tab" (Valor 180 de 255 = ~70% opaco)
SetLayeredWindowAttributes( ::oGhostWindow:handle, 0, 180, 2 )
// Transfere temporariamente o Painel de Conteúdo para dentro da janela flutuante
SetParent( oTab:oPanelPage:handle, ::oGhostWindow:handle )
MoveWindow( oTab:oPanelPage:handle, 5, 5, oTab:oPanelPage:nWidth, oTab:oPanelPage:nHeight, .T. )
oTab:oPanelPage:Show()
ACTIVATE WINDOW ::oGhostWindow NOSHOW
RETURN Nil
METHOD ReattachTab( nIdx ) CLASS ModernTabControl
LOCAL oTab := ::aTabs[nIdx]
::lIsFloating := .F.// Devolve o controle pai da página para a janela principal original
SetParent( oTab:oPanelPage:handle, ::oParent:handle )
MoveWindow( oTab:oPanelPage:handle, ::nLeft, ::nTop + TAB_HEIGHT, ::nWidth, ::nHeight - TAB_HEIGHT, .T. )
IF ::nActiveTab == nIdx
oTab:oPanelPage:Show()
ENDIF
// Fecha e destrói a janela temporária flutuante
IF ::oGhostWindow !=Nil
::oGhostWindow:End()
::oGhostWindow := Nil
ENDIF
::RedrawTabs()
RETURN Nil
// Auxiliares de API do Windows para transparência e posicionamento absoluto do Cursor
#pragma BEGINDUMP
#include <windows.h>
#include "hbapi.h"
HB_FUNC( SETLAYEREDWINDOWATTRIBUTES )
{
typedef BOOL (WINAPI
*lpfnSetLayeredWindowAttributes)(HWND, COLORREF, BYTE, DWORD);
HMODULE hUser32 = GetModuleHandle("user32.dll");
if (hUser32) {
lpfnSetLayeredWindowAttributes pSetLayered = (lpfnSetLayeredWindowAttributes)GetProcAddress(hUser32, "SetLayeredWindowAttributes");
if (pSetLayered) {
pSetLayered((HWND)hb_parnl(1), (COLORREF)hb_parnl(2), (BYTE)hb_parni(3), (DWORD)hb_parnl(4));
}
}
}
HB_FUNC( WLANG_GETCURSORX ){
POINT pt;
GetCursorPos(&pt);
hb_retni(pt.x);
}
HB_FUNC( WLANG_GETCURSORY )
{
POINT pt;
GetCursorPos(&pt);
hb_retni(pt.y);
}
#pragma ENDDUMP