Maximizing the Active CodeView in our Clipboard History Plug-in for Visual Studio

Mark Miller
10 September 2008

Welcome to Part 10 of the series showing how to build plug-ins for Visual Studio using DXCore.

So far we've:

In today's post, we'll add the ability to quickly maximize and later restore the active CodeView in the Clipboard History.

Maximizing the Active CodeView

I want to improve the Clipboard History UI a bit. While we have increased flexibility by allowing developers the ability to change the dimensions of the Clipboard History, having many rows and/or columns may lead to views that clip a portion of their content, forcing developers to scroll to see everything they contain.

So what I would like to do is make it possible to maximize the active CodeView temporarily, by holding down the Ctrl key. This allows developers working in space-constrained environments the ability to quickly see the contents of a selected clipboard history entry, without having to reach for the mouse to scroll.

Unfortunately I'm not aware of any elegant code which can notify me the moment the Ctrl key is pressed or released, so I'm going to hack this the old-fashioned way: by polling with a Timer (shudder).

Activate FrmClipHistory.cs [Design].

ActivateFrmClipHistoryDesign

From the Toolbox, select a System.Windows.Forms.Timer control...

SelectTimer

Drop the Timer onto the FrmClipHistory design surface...

DropTimer

Set the following properties:

Property Value
(Name) tmrCheckControl
Enabled True

In the Properties grid, click the Events button.

tmrCheckControlClickEventsButton

Double-click the Tick event to add a new handler.

HandleTickEvent

Add the following code...

private static bool _ActiveViewMaximized;

private
void tmrCheckControl_Tick(object sender, EventArgs e)
{
  Keys controlState = Control.ModifierKeys & Keys.Control;
  if (!_ActiveViewMaximized && controlState == Keys.Control ||
      _ActiveViewMaximized && controlState != Keys.Control)
  {
    _ActiveViewMaximized = !_ActiveViewMaximized;
    PositionViews();
  }
}

And let's now modify PositionViews so it can distribute the CodeViews evenly or maximize the view under the cursor. Speaking of PositionViews, this method is getting a little meaty, and with this new code it's going to get even meatier, so I think we should refactor it a bit. Here's the new PositionViews method:

private static void PositionViews()
{
  if (_FrmClipHistory == null)
    return;

  if (_ActiveViewMaximized)
    MaximizeActiveView();
  else 
    DistributeViewsEvenly();
}

DistributeViewsEvenly contains essentially the code from the old PositionViews method:

private static void DistributeViewsEvenly()
{
  int width = AllViewsRect.Width / ClipboardHistory.ColumnCount;
  int height = AllViewsRect.Height / ClipboardHistory.RowCount;
  Size viewSize = new Size(width - CursorBorder * 2, height - CursorBorder * 2);
  Size borderSize = new Size(width, height);
  for (int row = 0; row < ClipboardHistory.RowCount; row++)
    for (int column = 0; column < ClipboardHistory.ColumnCount; column++)
    {
      int index = GetIndex(row, column);
      CodeView thisView = _CodeViews[index];
      Panel thisBorder = _Borders[index];
      if (thisView == null || thisBorder == null)
        continue;

      thisBorder.Size = borderSize;
      thisBorder.Location = new Point(width * column, height * row);
      thisView.Size = viewSize;
    }
}

I replaced access to _FrmClipHistory.ClientRectangle from inside that old PositionViews code with an AllViewsRect property access. The AllViewsRect property simply returns the client rectangle of the form, like this:

private static Rectangle AllViewsRect
{
  get { return _FrmClipHistory.ClientRectangle; }
}

MaximizeActiveView needs this rectangle as well, and now seems like a good time to consolidate this access anyway, because I'm thinking about adding a status bar soon to assist with discoverability. If we do this, that status bar will reduce available space to be something less than ClientRectangle. Later, if/when we need to calculate the new available space (e.g., due to this new status bar I'm thinking about adding to the form), we can make that calculation from a single location.

MaximizeActiveView looks like this:

private static void MaximizeActiveView()
{
  int cursorIndex = GetIndex(_CursorRow, _CursorColumn);
  CodeView cursorView = _CodeViews[cursorIndex];
  Panel cursorBorder = _Borders[cursorIndex];
  if (cursorView == null || cursorBorder == null)
    return;

  Rectangle viewRect = AllViewsRect;
  viewRect.Inflate(-CursorBorder, -CursorBorder);
  cursorView.Size = viewRect.Size;
  cursorBorder.Size = AllViewsRect.Size;
  cursorBorder.Location = new Point(0, 0);
  cursorBorder.BringToFront();
}

That last BringToFront call is important -- it allows us to only size and position the active view and hide all the others behind it (regardless of their positions).

There's one more change required. If the active view is maximized, and the arrow keys are pressed, I still want the cursor to move and the active view to reflect that. To do this, we'll need to add the following code to MoveCursor:

private static void MoveCursor(int rowDelta, int columnDelta)
{
 
HideCursor();
 
_CursorRow += rowDelta;
 
_CursorColumn += columnDelta;
 
KeepCursorInBounds();

  if (_ActiveViewMaximized)
    PositionViews();

 
ShowCursor();
}

If Ctrl is held down and the arrow keys are pressed, I still want MoveCursor to be called. So we'll need to make a small modification to ProcessCmdKey, splitting its switch statement in two, one that switches on keyData, and the other that switches on keyData with the control key removed, like this:

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
 
const int WM_KEYDOWN = 0x100;
 
if (msg.Msg == WM_KEYDOWN)
 
{
   
switch (keyData)
    {
     
case Keys.Enter:
       
CloseAndPaste();
       
return true;
     
     
case Keys.Escape:
       
Close();
       
return true;

    }
    switch (keyData & ~Keys.Control)
    {

     
case Keys.Left:
       
MoveCursor(0, -1);
       
return true;

     
case Keys.Right:
       
MoveCursor(0, 1);
       
return true;

     
case Keys.Up:
       
MoveCursor(-1, 0);
       
return true;

     
case Keys.Down:
       
MoveCursor(1, 0);
       
return true;
   
}
  }
  return base.ProcessCmdKey(ref msg, keyData);
}

 The second switch statement strips out the Control key if present in the keyData parameter (so, for example, Ctrl+Left would be treated like a Left when execution enters the second switch).

That should do it. After making these changes, let's give it a try...

Testing the New CodeView Maximize/Distribute Feature

Click Run. In the second instance of Visual Studio, press Ctrl+Shift+Insert to bring up the Clipboard History.

Use the arrow keys to select an entry with scrollbars...

CodeViewsDistributedEvenly

Now hold down the Ctrl key to maximize the active view...

 ActiveCodeViewMaximized

Nice. Now release the Ctrl key to restore the distributed view default. Try holding down the Ctrl key and using the arrow keys.

Interact with that a bit. See if it feels right to you.

Tomorrow we'll improve discoverability into this new Ctrl-key feature, and add a right-click context menu to our form. See you then!

3 comment(s)
Anonymous
Dew Drop – September 18, 2008 | Alvin Ashcraft's Morning Dew

Pingback from  Dew Drop – September 18, 2008 | Alvin Ashcraft's Morning Dew

18 September, 2008
Anonymous
Mark Miller

Welcome to Part 11 of the series showing how to build plug-ins for Visual Studio using DXCore . So far

18 September, 2008
Anonymous
Mark Miller

Welcome to Part 12 of the series showing how to build plug-ins for Visual Studio using DXCore . So far

19 September, 2008

Please login or register to post comments.