Click here to Skip to main content
15,895,192 members
Articles / Desktop Programming / WTL

Making of a Color Spy utility with WTL

Rate me:
Please Sign up or sign in to vote.
4.92/5 (28 votes)
30 Sep 2003MIT8 min read 92.3K   1.7K   51  
Making of color picker utility using WTL and recap of clipboard management APIs.
//////////////////////////////////////////////////////////////////////
// ColorSpy Copyright 2003 Tom Furuya [tom_furuya@yahoo.com]
//
// Readers of this article may copy the code for use in developing their own applications.
// If the code is used in a commercial application, however, an acknolegement must 
// be included in the following form: 
// "Segment of the code (c) 2003 Tom Furuya for CodeProject.com".
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_MAINDLG_H__64E46429_CDA4_42FB_962A_5894FDB38FC1__INCLUDED_)
#define AFX_MAINDLG_H__64E46429_CDA4_42FB_962A_5894FDB38FC1__INCLUDED_

#pragma once

#include "ColorBox.h"
#include "ColorSpyCBViewer.h"
#include "SysColors.h"
#include "ColorHelper.h"
#include "Magnifier.h"

class CColorSpyDlg 
    : public CDialogImpl<CColorSpyDlg>
    , public CUpdateUI<CColorSpyDlg>
    , public CMessageFilter
    , public CIdleHandler
    , public CColorSpyCBViewer<CColorSpyDlg> // Clipboard viewer window
//    , public CLayeredWindowT<CColorSpyDlg>
{
public:

    CColorBox m_colorBox;
    CContainedWindowT<CEdit> m_infoBox;
    CPoint m_ptCursor;
    bool m_bSticked;
    COLORREF m_clrPixel;
    
    // ToolTip
    CToolTipCtrl m_ToolTip;
    LPTSTR m_szToolTipText;

    // Color format
    CColorHelper::ColorFormat m_colorFormat;

    // Magnifier
    CMagnifierDlg m_Magnifier;

    enum { IDD = IDD_MAINDLG };
    enum { GAP = 3 };

    CColorSpyDlg() 
        : m_bSticked(false)
        , m_infoBox(_T("edit"), this, IDC_INFO)
        , m_szToolTipText(NULL)
        , m_colorFormat(CColorHelper::CF_WEB)
    { }
    
    virtual BOOL PreTranslateMessage(MSG* pMsg)
    {
        if (pMsg->message == WM_CHANGECBCHAIN) 
        {
            ATLTRACE("WM_CHANGECBCHAIN receirved!\n");
        }

        // Tickle tooltip control with mouse messages
        if (WM_MOUSEFIRST <= pMsg->message && pMsg->message <= WM_MOUSELAST)
            RelayEvent(pMsg);

        return IsDialogMessage(pMsg);
    }
    
    virtual BOOL OnIdle()
    {
        return FALSE;
    }

    BEGIN_UPDATE_UI_MAP(CColorSpyDlg)
    END_UPDATE_UI_MAP()
    
    BEGIN_MSG_MAP(CColorSpyDlg)
        MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
        MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
        COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
        COMMAND_ID_HANDLER(ID_APP_EXIT, OnAppExit)
        COMMAND_ID_HANDLER(ID_VIEW_MAGNIFIER, OnViewMagnifier)

        //
        // Color representation format
        //
        COMMAND_RANGE_HANDLER(ID_REP_HEX, ID_REP_HSB, OnColorRep)
        
        //
        // ColorSpy App message handler
        //
        MESSAGE_HANDLER(WM_APP_COLORSPY, OnColorSpy)

        //
        // Note: Since ColorSpy tells Windows that this window is caption 
        // in NCHITTEST message handler, WM_[LR]BUTTON***, WM_CONTEXTMENU 
        // will not be sent to this window, but WM_NC*** ones.
        //
        MESSAGE_HANDLER(WM_NCRBUTTONUP, OnRButtonUp)
        MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
        MESSAGE_HANDLER(WM_NCLBUTTONDOWN, OnLButtonDown)

        // 
        // "Sticky" rubber (to the top edge of screen)
        //
        MESSAGE_HANDLER(WM_MOVING, OnMoving)
  
        //
        // Turn this window class into a CB Vierer for reading color [code|name] 
        //
        CHAIN_MSG_MAP_ALT(CColorSpyCBViewer<CColorSpyDlg>, 1)
        
        // Reflection for Colorbox STATIC
        REFLECT_NOTIFICATIONS()

         // Edit control
    ALT_MSG_MAP(IDC_INFO)

        // Tickle ToolTip control on Infobox to display supplimental info hesitantly.
        // ToolTips is only aware of mouse messages between WM_MOUSEFIRST and WM_MOUSELAST.
		MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseMessage)
		MESSAGE_HANDLER(WM_SYSKEYDOWN, OnSysKeyDown)
    END_MSG_MAP()
        
    // Handler prototypes (uncomment arguments if needed):
    //	LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    //	LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    //	LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
    
// Message handlers

    void UpdateLayout()
    {
        CRect rect;	GetClientRect(&rect);
        CRect rcCB; m_colorBox.GetClientRect(&rcCB);

        // Infobox
        CRect rcIB; rcIB.CopyRect(rcCB); 
        rcIB.left = rcCB.right + GAP;
        int cxInfobox = (CColorHelper::CF_WEB == m_colorFormat) ? 120 : 150;
        rcIB.right = rcIB.left + cxInfobox;
        m_infoBox.SetWindowPos(NULL, &rcIB, SWP_NOZORDER);

        // Dialog
        SIZE size = { rcCB.Width() + GAP + rcIB.Width() + 2 * GAP,
                      rcCB.Height() +  2 * GAP };
        rect.SetRectEmpty();
        rect.right = size.cx; rect.bottom = size.cy;
        ModifyStyle(DS_3DLOOK,0);

        // Create region and assign to window
        CRgn rgn;
        rgn.CreateRectRgn(0, 0, rect.Width(), rect.Height());
        SetWindowRgn(rgn, TRUE);
        SetWindowPos(HWND_TOPMOST, &rect, SWP_NOMOVE);
    }
    
    LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        CProfile profile;

        // Set widnow position
        int xLeft = profile.GetInt(_T("General"), _T("x"), 0);
        int yTop = profile.GetInt(_T("General"), _T("y"), 0);
        if (xLeft == 0 && yTop == 0)
            CenterWindow();
        else
            ::SetWindowPos(m_hWnd, NULL, xLeft, yTop, -1, -1,
			    SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
        
        // Set icons
        HICON hIcon = (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MAINFRAME), 
            IMAGE_ICON, ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR);
        SetIcon(hIcon, TRUE);
        HICON hIconSmall = (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MAINFRAME), 
            IMAGE_ICON, ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);
        SetIcon(hIconSmall, FALSE);
        
        // Register object for message filtering and idle updates
        CMessageLoop* pLoop = _Module.GetMessageLoop();
        ATLASSERT(pLoop != NULL);
        pLoop->AddMessageFilter(this);
        pLoop->AddIdleHandler(this);

        // ToolTip
        m_ToolTip.Create(m_hWnd);
        m_ToolTip.ModifyStyle(0, TTS_ALWAYSTIP);// | TTS_BALLOON); 
        
        // Colorbox
        CRect rcBox; rcBox.SetRectEmpty();
        rcBox.bottom = rcBox.right = 17;
        if (!(HWND)m_colorBox)
            m_colorBox.Create(m_hWnd, rcBox, NULL, 0, 0, IDC_COLORBOX);
        m_colorBox.SetFocus();

        // Infobox
        m_infoBox.SubclassWindow(GetDlgItem(IDC_INFO));
        m_infoBox.SetReadOnly();

        UIAddChildWindowContainer(m_hWnd);
        UpdateLayout();
       
        bHandled = FALSE;
        
        return TRUE;
    }
    
    LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        bHandled = FALSE;
        m_infoBox.UnsubclassWindow();
        return 0;
    }

    LRESULT OnColorRep(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        switch (wID)
        {
        default:
        case ID_REP_HEX: m_colorFormat = CColorHelper::CF_WEB; break;
        case ID_REP_DEC: m_colorFormat = CColorHelper::CF_DEC; break;
        case ID_REP_HSB: m_colorFormat = CColorHelper::CF_HSB; break;
        }
        if (m_colorBox.IsLocked())
            ShowInfo();
        UpdateLayout();
        return 0;
    }

    void ShowInfo()
    {
        CColorHelper helper;
        CString strColorRep = helper.ToString(m_colorFormat, m_clrPixel);

        m_colorBox.SetColor(m_clrPixel);
        CString str; 
        switch (m_colorFormat)
        {
        default:
        case CColorHelper::CF_WEB:
            str.Format(_T("%d, %d #%s"), m_ptCursor.x, m_ptCursor.y, strColorRep);
            break;
        case CColorHelper::CF_DEC:
            str.Format(_T("%d, %d %s"), m_ptCursor.x, m_ptCursor.y, strColorRep);
            break;
        case CColorHelper::CF_HSB:
            str.Format(_T("%d, %d %s"), m_ptCursor.x, m_ptCursor.y, strColorRep);
            break;
        }
        ((CEdit)m_infoBox).SetSel(0,-1);
        ((CEdit)m_infoBox).ReplaceSel(str);
    }

    LRESULT OnColorSpy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        // retrieve pixel color
        ::GetCursorPos (&m_ptCursor);
        HDC hdc = ::GetDC(HWND_DESKTOP); //entire screen
        m_clrPixel = ::GetPixel(hdc, m_ptCursor.x, m_ptCursor.y);
        ::ReleaseDC(NULL, hdc);  //release dc
        
        // show info about the sampled color
        ShowInfo();
        
        // use CTRL+SHIFT keys for sampling
        if (::GetAsyncKeyState(VK_CONTROL) < 0 && ::GetAsyncKeyState(VK_SHIFT) < 0)
        {
            // freeze color box
            m_colorBox.Lock();
            ((CEdit)m_infoBox).SetReadOnly(FALSE);
            // freeze magnifier also
            m_Magnifier.Lock();
        }
        
        return 0;
    }
    
    //
    // Making "sticky" ghost rectangle effect
    // 
    // Spy tells WM_MOVING is the event to watch when you see ghost rubber rectangle.
    // While mouse button is held down, other mouse event won't come.
    //
    LRESULT OnMoving(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {	
        const int sticky_distance = 4;
        RECT* rcGhost = (LPRECT)lParam;
        
        CPoint pt; GetCursorPos(&pt);
        pt -= m_ptCursor;
        
        if (m_bSticked && 
            pt.x * pt.x + pt.y * pt.y > sticky_distance * sticky_distance)
        {
            m_bSticked = false;
            return 0;
        }
        
        // Stick to top edge of the screen
        if (rcGhost->top <= sticky_distance)
        {
            CRect rcWindow; GetWindowRect(rcWindow);
            rcWindow.OffsetRect((rcGhost->left - rcWindow.left), -rcWindow.top);
            // Reposition ghost rubber
            rcGhost->left = rcWindow.left;
            rcGhost->top = rcWindow.top;
            rcGhost->right = rcWindow.right;
            rcGhost->bottom = rcWindow.bottom;
            m_bSticked = true;
        }
        
        ::GetCursorPos(&m_ptCursor);
        return 0;
    }

	LRESULT OnMouseMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
        if (WM_MOUSEMOVE == uMsg)
        {
            static CString strOld;

            CColorHelper helper;
            // install colorname for tooltip
            CString strColorName = helper.GetColorName(m_clrPixel);
            if (strOld != strColorName)
                SetToolTipText(strColorName);
            strOld = strColorName;

            // bring the tooltip window above other popup windows
            ::SetWindowPos(m_ToolTip, HWND_TOP, 0, 0, 0, 0,
                SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);
        }

        bHandled = FALSE;
        return 0;
	}

    LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        bHandled = FALSE; // Run hit test
        
        // Check to see if mouse cursor hit colorBox
        CPoint ptCursor(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
        CRect  rcColorBox; m_colorBox.GetWindowRect(&rcColorBox);
        if (false == rcColorBox.PtInRect(ptCursor))
            return 0;

        CEdit edit = m_infoBox;
        bool lock = !m_colorBox.IsLocked();
        
        m_colorBox.Lock(lock);
        // sync magnifier as well
        m_Magnifier.Lock(lock);

        if (lock)
        {
            // Move caret to the end for easier color code editing
            int len = edit.GetWindowTextLength();
            edit.SetSel(len, len);
            edit.SetFocus();
            edit.SetReadOnly(FALSE);
        }
        else
        {
            m_colorBox.SetFocus();
            edit.SetReadOnly();
        }
        return 0;
    }

    LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        return HTCAPTION; //treated as a CAPTION; WM_CONTEXTMENU will not come.
    }
    //
    // Note: If right mouse button is clicked, for example,
    // you receive message as WM_NCRBUTTON**, instead of WM_RBUTTON**
    // as we say CAPTION.
    LRESULT OnRButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        CMenu menu; menu.LoadMenu(IDR_CONTEXT_MENU);
        CMenuHandle hPopup = menu.GetSubMenu(0);
        hPopup.SetMenuDefaultItem(ID_APP_EXIT);
        if ((HWND)m_Magnifier)
        {
            hPopup.EnableMenuItem(ID_VIEW_MAGNIFIER, MF_GRAYED | MF_BYCOMMAND);
        }

        CPoint pt; GetCursorPos(&pt); 
        hPopup.TrackPopupMenu(0, pt.x, pt.y, m_hWnd); //takes pt in the screen coordinates
        return 0;
    }
    
    //
    // Make ColorSpy a well-behaved window app.
    //
    LRESULT OnSysKeyDown(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
    {
        if (GetKeyState(VK_F4) < 0)
            CloseDialog(0);
        return 0;
    }

    LRESULT OnAppExit(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        CRect rc; GetWindowRect(&rc);
        CProfile iniFile; 
        
        iniFile.WriteInt(_T("General"), _T("x"), rc.left);
        iniFile.WriteInt(_T("General"), _T("y"), rc.top);

        CloseDialog(0);
        return 0;
    }
    
    LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        ShowWindow(SW_MINIMIZE);
        ShowWindow(SW_HIDE);
        CAboutDlg dlg;
        dlg.DoModal();
        ShowWindow(SW_RESTORE);
        return 0;
    }

    LRESULT OnViewMagnifier(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        m_Magnifier.Create(m_hWnd, rcDefault);
        m_Magnifier.ModifyStyle(0, WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME);
        CRect rc; GetWindowRect(rc);
        rc.OffsetRect(0, rc.Height());
        m_Magnifier.MoveWindow(rc);
        m_Magnifier.ShowWindow(SW_SHOW);
        m_Magnifier.Lock(m_colorBox.IsLocked());

        return 0;
    }
    
    void CloseDialog(int nVal)
    {
//        FadeOut(0xFF); 
        DestroyWindow();
        ::PostQuitMessage(nVal);
    }
    
    void SetColor(CString& text)
    {
        // Do nothing if Colorbox is active (Color sampling mode)
        if (false == m_colorBox.IsLocked())
            return;

        // Color name/code scan mode
        DuoT<bool, COLORREF> result = Validate(text);
        if (result.v1())
            SetColor(result.v2());
    }
    void SetColor(COLORREF clr)
    {
        m_clrPixel = clr;
        CColorHelper helper; 

        m_colorBox.SetColor(m_clrPixel);
        CString strText; strText.Format(_T("#%s"), helper.ToString(m_clrPixel));
        m_infoBox.SetWindowText(strText);
    }

    DuoT<bool, COLORREF> Validate(const CString& strText)
    {
        const CString strFmt = _T("#RRGGBB");
        const DuoT<bool, COLORREF> InvalidColor = make_duo(false, 0);

        CColorHelper helper;
        DuoT<bool, COLORREF> result;

        if (strText.Left(1) != _T('#'))
        {
            result = helper.GetColorByName(strText);
            if (result.v1())
                return result;

            result = CSysColors::GetColorByName(strText);
            return result;
        }

        if (strText.GetLength() != strFmt.GetLength())
            return InvalidColor;

        result = helper.GetColor(strText.Right(6));
        return result;
    }

	void RelayEvent(LPMSG lpMsg)
	{
        // Tickle ToolTip control
        if (m_ToolTip.IsWindow())
            m_ToolTip.RelayEvent(lpMsg);
	}

    bool SetToolTipText(LPCTSTR lpszText)
    {
        if (m_szToolTipText != NULL)
        {
            delete [] m_szToolTipText;
            m_szToolTipText = NULL;
        }
        if (lpszText == NULL || lstrlen(lpszText) == 0)
        {
            if(m_ToolTip.IsWindow())
                m_ToolTip.Activate(FALSE);
            return true;
        }

        ATLTRY (m_szToolTipText = new TCHAR[lstrlen(lpszText) + 1]);
        if (m_szToolTipText == NULL)
            return false;
        bool bRet = (lstrcpy(m_szToolTipText, lpszText) != NULL);
        if(bRet && m_ToolTip.IsWindow())
        {
            m_ToolTip.Activate(TRUE);
            m_ToolTip.AddTool(m_infoBox, m_szToolTipText);
        }
        return bRet;
    }
    
    //
    // Simple INI Profile helper 
    //

    class CProfile
    {
    public:

        CProfile(LPCTSTR szIniFile = NULL)
        {
           m_strIniFilePath = GetModuleIniFilePath(szIniFile);
        }

        BOOL WriteInt(
            LPCTSTR szSection,
            LPCTSTR szKey,
            int nVal
            )
        {  
            return _WritePrivateProfileInt(szSection, szKey, nVal, m_strIniFilePath);
        }

        int GetInt(
            LPCTSTR szSection,
            LPCTSTR szKey,
            int nDefault
            )
        {  
            return _GetPrivateProfileInt(szSection, szKey, nDefault, m_strIniFilePath);
        }

        static CString GetModuleIniFilePath(LPCTSTR szIniFile = NULL)
        {
            TCHAR szModule1[_MAX_PATH] = { 0 };
            TCHAR szModule2[_MAX_PATH] = { 0 };
		    TCHAR* pszFileName;
            ::GetModuleFileName(_Module.GetModuleInstance(), szModule1, _MAX_PATH);
		    ::GetFullPathName(szModule1, _MAX_PATH, szModule2, &pszFileName);

            CString strIniFileName = szModule2;
            if (szIniFile)
            {
                int idx = strIniFileName.ReverseFind(_T('\\'));
                strIniFileName.Format("%s\\%s", strIniFileName.Left(idx), szIniFile);
            }
            else 
            {
                int idx = strIniFileName.ReverseFind(_T('.'));
                strIniFileName.Format(_T("%s.ini"), strIniFileName.Left(idx));
            }
            return strIniFileName;
        }

    private:
        CString m_strIniFilePath;

        BOOL _WritePrivateProfileInt(
            LPCTSTR szSection,
            LPCTSTR szKey,
            int nVal,
            LPCTSTR szFile
        )
        {
            TCHAR buffer[1024] = { 0 };
            itoa(nVal, buffer, 10);
            return ::WritePrivateProfileString(szSection, szKey, buffer, szFile);
        }

        int _GetPrivateProfileInt(
            LPCTSTR szSection,
            LPCTSTR szKey,
            int nDefault,
            LPCTSTR szFile
            )
        {
            return ::GetPrivateProfileInt(szSection, szKey, nDefault, szFile); 
        }
    };
};

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_MAINDLG_H__64E46429_CDA4_42FB_962A_5894FDB38FC1__INCLUDED_)

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Japan Japan
Live (1994 "Throwing Copper" till 1999 "The Distance To Here") is one of his favorite bands.

After 9 years life in U.S, he lives in his hometown Yokohama, working in Automotive After-sales business.
He sometimes found himself drunk once in a while in Munich.



He has put the period to his windows development after writing first and last article about a light-weight memory program, called colorspy (which is amusingly running on his latest windows except for dual display support.)
He has a message to the WTL author, "you rock. you proved that WTL kicks ass, M*F*C". F, in the middle, always reminds him of somewhat different wording.


Time lapse



His codepen is live.copper. His main focus has changed to various web technologies, to build fastest EPC services for automotive clients instead of pharmaceutical ones.
Ironically, he has not yet been released from the chaotic Windows software development installations even though he is no longer programming for Windows but for server side development.




Ein Prosit, Ein Prosit! He is still with a company in Munich as he loves to help people all over the world who need to fix their cars.

Comments and Discussions