Creating Sticky Windows



Click here for a larger image.

Environment: VC6

I've wanted to implement a variation on the constrained window alignment available in popular software titles like Winamp and CorelDRAW for some time and finally got the opportunity. Although the test application is MFC, the core functionality is contained in the non-MFC class CStickyDragManager.

CStickyDragManager handles the calculation of window bounding rectangles relative to the position of other registered windows. Typically, the calling application invokes the window rectangle calculation phase during a WM_SIZING, a WM_MOVING message, or in a synthetic window dragger (for example, a MFC controlbar dragging code). CStickyDragManager also handles drag grouping by nominating a window candidate as a "Docker." Docker windows can drag all adjacent registered windows.

//StickyDragManager.h...

#include <windows.h>
    #include <vector>
    using namespace std;

    class CStickyDragManager
    {
    public:
      CStickyDragManager(bool isDocker = false);
      ~CStickyDragManager();
      void Init(HWND hWnd = 0);
                   vector<RECT>& dockedRects);
      void SnapMoveRect(LPRECT r);
      void SnapSize(LPRECT r);
      void GetDockedWindows(HWND hWnd, int index,
           vector<HWND> &windows);
      void StartTrack();

      inline void SetDockerState(bool bDockerState) {
             m_bIsDocker = bDockerState; }
      vector<HWND> *SnapMove(LPRECT activeRect,
      inline void AddWindow(HWND hWnd) {
             m_windows.push_back(hWnd); }
      inline HWND GetActiveWindow() { return m_activeWindow; }
      inline bool IsCloseTo(int ms, int ss) { return ((ms 
             > (ss - m_slack)) && (ms < (ss + m_slack)))
             ? true : false; }
      inline vector<HWND>* DockedWindows() { return 
             &m_dockedWindows; }
    protected:
      RECT m_lastDragRect;
      bool m_bIsDocker;
      POINT m_dragPos;
      int m_slack;
      bool m_stickFlags[4];
      int m_stickPos[4];
      HWND m_activeWindow;
      HWND m_exclusionWindow;
      vector<HWND> m_windows;
      vector<HWND> m_dockedWindows;
      vector<bool> m_windowDock;
      vector<bool> m_windowDockProcess;
    };

//StickyDragManager.cpp...
#include "StickyDragManager.h"

    CStickyDragManager::CStickyDragManager(bool isDocker)
    {
      Init();
      m_bIsDocker = isDocker;
    }

    CStickyDragManager::~CStickyDragManager()
    {
    }

    void CStickyDragManager::Init(HWND hWnd)
    {
      m_exclusionWindow = 0;
      m_activeWindow = hWnd;
      m_dockedWindows.clear();
      m_windows.clear();
      m_slack = 5; // slack determines how strongly attracted
                   // registered windows are to each other.
      for (int i = 0; i < 4; i++)
      {
        m_stickFlags[i] = false;
        m_stickPos[i] = 0;
      }
    }

    vector<HWND> *CStickyDragManager::SnapMove(LPRECT
                                      activeRect,
    vector<RECT>& dockedRects)
    {
      int dx = 0, dy = 0;
      RECT ssar, sar, sr;
      dockedRects.clear();
      ssar = *activeRect;
      int dxy[2];
      dxy[0] = activeRect->left - m_lastDragRect.left;
      dxy[1] = activeRect->top - m_lastDragRect.top;
      if (!m_dockedWindows.size())
      {
        SnapMoveRect(activeRect);
        m_lastDragRect = *activeRect;
        return &m_dockedWindows;
      }
      for (unsigned int i = 0; i < m_dockedWindows.size(); i++)
      {
        RECT r;
        GetWindowRect(m_dockedWindows[i], &r);
        OffsetRect(&r, dxy[0], dxy[1]);
        dockedRects.push_back(r);
      }
      do
      {
        sar = *activeRect;
        SnapMoveRect(activeRect);
        dx = sar.left - activeRect->left;
        dy = sar.top - activeRect->top;
        for (unsigned i = 0; i < m_dockedWindows.size(); i++)
        {
          OffsetRect(&dockedRects[i], -dx, -dy);
          sr = dockedRects[i];
          SnapMoveRect(&dockedRects[i]);
          dx = sr.left - dockedRects[i].left;
          dy = sr.top - dockedRects[i].top;
          for (unsigned int j = 0; j < m_dockedWindows.size();
                                   j++)
            if (j != i)
              OffsetRect(&dockedRects[j], -dx, -dy);
              OffsetRect(activeRect, -dx, -dy);
        }
        dx = sar.left - activeRect->left;
        dy = sar.top - activeRect->top;
      }
      while (dx || dy);
      dx = ssar.left - activeRect->left;
      dy = ssar.top - activeRect->top;
      for (i = 0; i < m_dockedWindows.size(); i++)
      {
        GetWindowRect(m_dockedWindows[i], &dockedRects[i]);
        OffsetRect(&dockedRects[i], dxy[0]-dx, dxy[1]-dy);
      }
      m_lastDragRect = *activeRect;
      return &m_dockedWindows;
    }

    void CStickyDragManager::SnapMoveRect(LPRECT r)
    {
      POINT cPos;
      GetCursorPos(&cPos);
      int mPos[4] = {cPos.x, cPos.y, cPos.x, cPos.y};
      for (int j = 0; j < 4; j++)
      {
        if (m_stickFlags[j])
        {
          if (abs(m_stickPos[j] - mPos[j]) > 2 * m_slack)
          {
            m_stickFlags[j] = false;
            int d = mPos[j] - m_stickPos[j];
            switch(j)
              {
                case 0: OffsetRect(r, d, 0); break;
                case 1: OffsetRect(r, 0, d); break;
                case 2: OffsetRect(r, d, 0); break;
                case 3: OffsetRect(r, 0, d); break;
              }
          }
        }
      }
      int dx = 0;
      int dy = 0;
      int iPos = -1;
      int jPos = -1;
      RECT cr, dr, sr;
      for (unsigned int i = 0; i < m_windows.size(); i++)
      {
        if (m_windows[i] == m_exclusionWindow)
          continue;
        GetWindowRect(m_windows[i], &cr);
        sr = cr;
        InflateRect(&sr, m_slack, m_slack);
        if (!IntersectRect(&dr, &sr, r))
          continue;
        bool b1 = false;
        if ((b1 = IsCloseTo(r->right, cr.left)) ||
                 (IsCloseTo(r->right, cr.right)))
        {
          dx = r->right - (b1 ? cr.left : cr.right); iPos = 2;
        }
        else if ((b1 = IsCloseTo(r->left, cr.left)) ||
                      (IsCloseTo(r->left, cr.right)))
        {
          dx = r->left - (b1 ? cr.left : cr.right); iPos = 0;
        }
        if ((b1 = IsCloseTo(r->top, cr.top)) ||
                 (IsCloseTo(r->top, cr.bottom)))
        {
          dy = r->top - (b1 ? cr.top : cr.bottom); jPos = 1;
        }
        else if ((b1 = IsCloseTo(r->bottom, cr.top)) ||
                      (IsCloseTo(r->bottom, cr.bottom)))
        {
          dy = r->bottom - (b1 ? cr.top : cr.bottom); jPos = 3;
        }
        OffsetRect(r, -dx, -dy);
        if (iPos > -1 && !m_stickFlags[iPos])
        {
          m_stickFlags[iPos] = true;
          m_stickPos[iPos] = mPos[iPos];
        }
        if (jPos > -1 && !m_stickFlags[jPos])
        {
          m_stickFlags[jPos] = true;
          m_stickPos[jPos] = mPos[jPos];
        }
        if (dx || dy)
          break;
      }
    }

    void CStickyDragManager::SnapSize(LPRECT r)
    {
      RECT cr, dr, sr;
      for (unsigned int i = 0; i < m_windows.size(); i++)
      {
        GetWindowRect(m_windows[i], &cr);
        sr = cr;
        InflateRect(&sr, m_slack, m_slack);
        if (!IntersectRect(&dr, &sr, r))
          continue;
        bool b1 = false;
        if ((b1 = IsCloseTo(r->right, cr.left)) ||
                 (IsCloseTo(r->right, cr.right)))
          r->right -= r->right - (b1 ? cr.left : cr.right);
        if ((b1 = IsCloseTo(r->left, cr.left)) ||
                 (IsCloseTo(r->left, cr.right)))
          r->left -= r->left - (b1 ? cr.left : cr.right);
        if ((b1 = IsCloseTo(r->top, cr.top)) ||
                 (IsCloseTo(r->top, cr.bottom)))
          r->top -= r->top - (b1 ? cr.top : cr.bottom);
        if ((b1 = IsCloseTo(r->bottom, cr.top)) ||
                 (IsCloseTo(r->bottom, cr.bottom)))
          r->bottom -= r->bottom - (b1 ? cr.top : cr.bottom);
       }
    }

    void CStickyDragManager::GetDockedWindows(HWND hWnd,
                             int index, vector<HWND>
                             &windows)
    {
      RECT wr, twr, dr;
      if (index == -1)
      {
        m_windowDock.clear();
        m_windowDockProcess.clear();
        for (unsigned int i = 0; i < m_windows.size(); i++)
        {
          m_windowDock.push_back(false);
          m_windowDockProcess.push_back(false);
        }
      }
    else
        m_windowDockProcess[index] = true;
    GetWindowRect((index == -1) ? hWnd : m_windows[index],
                                         &wr);
        for (unsigned int i = 0; i < m_windows.size(); i++)
        {
        if (i != index && !m_windowDock[i])
        {
          GetWindowRect(m_windows[i], &twr);
          RECT cwr = wr;
          InflateRect(&cwr, 1, 1);
            if (!IntersectRect(&dr, &twr, &cwr))
              continue;
          if ((twr.left == wr.left) || (twr.left == wr.right))
                                        m_windowDock[i] = true;
          if ((twr.right == wr.left) || (twr.right == wr.right))
                                        m_windowDock[i] = true;
          if ((twr.top == wr.top) || (twr.top == wr.bottom))
                                        m_windowDock[i] = true;
          if ((twr.bottom == wr.top) || 
             (twr.bottom == wr.bottom)) m_windowDock[i] = true;
          if (!m_windowDockProcess[i] && m_windowDock[i])
            GetDockedWindows(m_windows[i], i, windows);
        }
      }
      if (index == -1)
      {
        windows.clear();
        for (unsigned int i = 0; i < m_windows.size(); i++)
        if (m_windowDock[i])
          windows.push_back(m_windows[i]);
      }
    }

    void CStickyDragManager::StartTrack()
    {
    if (m_bIsDocker)
      {
        if (m_activeWindow)
          GetDockedWindows(m_activeWindow, -1, m_dockedWindows);
    // Exclude docked windows from dock candidates.
        vector<HWND> windows;
        for (vector<HWND>::iterator i = m_windows.begin();
                                    i != m_windows.end(); i++)
        {
          bool docked = false;
          for (unsigned int j = 0; j < m_dockedWindows.size();
                                   j++)
          if (*i == m_dockedWindows[j]) docked = true;
          if (!docked)
          windows.push_back(*i);
        }
        m_windows.clear();
        m_windows = windows;
      }
      GetWindowRect(m_activeWindow, &m_lastDragRect);
    }

Downloads

Download source - 10Kb


Comments

  • Use CStickyManager class in C#.Net

    Posted by anil.baviskar on 01/29/2010 12:15am

    Hi Jim, I want to use this StickyManager in my C#.Net application. Will you please provide some help on this? Thanks

    Reply
  • Draggable Dialogs

    Posted by Legacy on 10/31/2003 12:00am

    Originally posted by: Don

    Thanks, this is what I was looking for. Just one question, for anyone who knows.

    Prior to implementing this, I was already capturing WM_LBUTTONDOWN and using the "::SendMessage(m_hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0);" trick to make the entire dialog draggable. So, how can I use these techniques together?

    Cheers,
    Don

    Reply
  • Non-MFC

    Posted by Legacy on 08/08/2003 12:00am

    Originally posted by: Mark

    Can you add a small writeup about the basics for making this work outside of an MFC project? I'm a little confused on the "big picture" of what goes on inside this class. (newby here).

    Reply
  • Update??

    Posted by Legacy on 02/12/2003 12:00am

    Originally posted by: Corneliu I. Tusnea

    Hi,

    Pls. provide update details: What has changed, why, and maybe how.
    I don't feel like downloading the code again, compile and start digging to see how it behaves.
    So, a short "Release Notes" might help.

    Thanks,
    Corneliu.

    Reply
  • Great..but still jumpy windows

    Posted by Legacy on 02/07/2003 12:00am

    Originally posted by: Abu D

    now that cursor and desktop (more client side bogus :) not CSticky) were fixed still rest this:

    stick more windows...and try to stick and unstick to desktop margins...it's a nasty jumpy activity...that is more pregnant if the windows are bitmapped..and showcontentwhile dragging on
    winamp is more stable regarding windows manipulation

    this class is great!

    Reply
  • Bogus

    Posted by Legacy on 02/02/2003 12:00am

    Originally posted by: Abu D

    1.Cursor position is lost from the window Rect when draging
    2.If we stick windows to desktop window (ex. top left etc.) and the docker is sticked too...when moving will drag all other windows..
    3.Strange windows flickering..when sticking/unsticking at desktop borders..(more windows at a time..all sticked together)

    pleas fix..

    ad

    Reply
  • Can someone convert this to Borland c++ Builder v.6?

    Posted by Legacy on 01/16/2003 12:00am

    Originally posted by: ARian

    Thanks

    Reply
  • degug errors

    Posted by Legacy on 11/01/2002 12:00am

    Originally posted by: x029

    fatal error C1083: Cannot open precompiled header file: 'Debug/stickyWindows.pch': No such file or directory.
    
    

    when I compile them with microsoft studio ,would you tell me why?

    Reply
  • reset cursor pos after sticking windows

    Posted by Legacy on 10/22/2002 12:00am

    Originally posted by: Catchang

    You should offset the cursor pos too right after moving windows.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds