C++ Q&A: Getting Version Info, CFolderTabCtrl R...

We were unable to locate this content in de-de.

Here is the same content in en-us.

C++ Q&A
Getting Version Info, CFolderTabCtrl Revisited
Paul DiLascia

Code download available at: CQA0210.exe (122 KB)
Browse the Code Online

Q The following expression in my C# code sets a version number:
[assembly: System.Reflection.AssemblyVersion("1.0.0.1")]
I can see the results when I examine the properties of my .exe file in Windows® Explorer, so I know it's working. But how can I dig this information out at run time to display it in my About dialog box?
Q The following expression in my C# code sets a version number:
[assembly: System.Reflection.AssemblyVersion("1.0.0.1")]
I can see the results when I examine the properties of my .exe file in Windows® Explorer, so I know it's working. But how can I dig this information out at run time to display it in my About dialog box?
Doug Harrington

A It seems like a simple request, but the answer isn't obvious—unless you already know the magic voodoo. Where, oh where, are the attributes? In this case, the version number is not a property of the Assembly, but rather of its name:
Assembly a = typeof(MyApp).Assembly;
String version = a.GetName().Version;
Assembly.GetName returns the AssemblyName of an assembly; AssemblyName.Version is its version. If you want to get the version for some other assembly, call Assembly.LoadFrom.
Assembly a = Assembly.LoadFrom("MotLib.NET.dll");
String version = a.GetName().Version;
A It seems like a simple request, but the answer isn't obvious—unless you already know the magic voodoo. Where, oh where, are the attributes? In this case, the version number is not a property of the Assembly, but rather of its name:
Assembly a = typeof(MyApp).Assembly;
String version = a.GetName().Version;
Assembly.GetName returns the AssemblyName of an assembly; AssemblyName.Version is its version. If you want to get the version for some other assembly, call Assembly.LoadFrom.
Assembly a = Assembly.LoadFrom("MotLib.NET.dll");
String version = a.GetName().Version;
Figure 1 shows a little C# program that displays the name and version of any Microsoft® .NET assembly. Figure 2 shows it in action.

////////////////////////////////////////////////////////////////
// MSDN Magazine — October 2002
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
//
// To compile:
//    csc ShowVersion.cs
//
using System;
using System.IO;
using System.Reflection;

[assembly:AssemblyVersion("1.2.3.4")]

class MainApp {
   public static void Main(String[] args) {
      if (args.Length<=0) {
         Console.WriteLine(@"Usage:
   ShowVersion <filename>
   Shows version number and other info for <filename>");

      } else {
         try {
            Assembly a = Assembly.LoadFrom(args[0]);
            AssemblyName an = a.GetName();
            Console.WriteLine(@"
{0}:
Fullname = {1}
Name     = {2}
CodeBase = {3}
Version  = {4}", args[0], an.FullName, an.Name, an.CodeBase, an.Version);

         } catch (Exception e) {
            Console.WriteLine("Error: {0}", e.Message);
         }
      }
   }
};

Q I managed to get your folder tabs from the May 1999 issue of MSJ to work in my application, which is a heavily modified descendent of CView (which displays a drawing). The biggest drawing I have seen so far has 13 sheets, which barely fits all the folder tabs in 1024×768, so I have to add arrow controls to the folder tabs. Did you ever create the code for adding the arrow controls? If not, how can I do it?
Q I managed to get your folder tabs from the May 1999 issue of MSJ to work in my application, which is a heavily modified descendent of CView (which displays a drawing). The biggest drawing I have seen so far has 13 sheets, which barely fits all the folder tabs in 1024×768, so I have to add arrow controls to the folder tabs. Did you ever create the code for adding the arrow controls? If not, how can I do it?
Lynn McGuire

A No, I never added the arrow buttons, so I guess now's the time. But first, let me remind everyone what Lynn is talking about. In the April and May 1999 issues of MSJ I wrote a class CFolderTabCtrl that implements folder tabs like the ones in Microsoft Excel. But what to do if you have lots of tabs, too many to fit next to the scrollbar? Most programs like Excel display Next/Previous buttons to scroll the tabs to and fro. I didn't implement them for CFolderTabCtrl because I was under pressure from Acme's Maximum Leader and the Release Goddess. But adding arrows isn't brain surgery, so now that I have time to relax, I'll show you how.
A No, I never added the arrow buttons, so I guess now's the time. But first, let me remind everyone what Lynn is talking about. In the April and May 1999 issues of MSJ I wrote a class CFolderTabCtrl that implements folder tabs like the ones in Microsoft Excel. But what to do if you have lots of tabs, too many to fit next to the scrollbar? Most programs like Excel display Next/Previous buttons to scroll the tabs to and fro. I didn't implement them for CFolderTabCtrl because I was under pressure from Acme's Maximum Leader and the Release Goddess. But adding arrows isn't brain surgery, so now that I have time to relax, I'll show you how.
The original folder-tab program was DibView4, a viewer for BMPs and DIBs. DibView4 has three folder-tabbed panes: Image, Bitmap Info, and Hex. Figure 3 shows DibView4 with no room for the Hex tab.
Figure 3 No Room 
So, how do you add arrow buttons? The basic idea is to create the buttons as child windows of CFolderTabCtrl and paint the tabs to the right of the buttons. Figure 4 shows a new program, ImgView3, that does it. ImgView3 is based on (what else?) ImgView2, from my March 2002 column. It uses GDI+ and CPicture to display JPG and GIF as well as BMP and DIB files. Whatever. The arrow buttons are implemented in a new class: CFolderButton. CFolderButton is an owner-drawn button with a DrawItem function to draw the buttons. Rather than use a bitmap, I chose to use GDI to draw a left- or right-pointing triangle. That way, I don't have to worry about ratty edges caused by scaling.
Figure 4 ImgView3 with 13 Tabs 
Instead, CFolderButton::DrawItem draws a triangle to fit to the button's size, whatever it may be. It uses the 3D shadow color if the button is disabled and draws everything shifted one pixel southeast if the button is down. CFolderButton also handles mouse messages to implement a couple of special features. Normally, buttons don't respond to double-clicks, but it seems proper that double-clicking an arrow button should scroll two tabs instead of one. In other words, a double-click is like two single-clicks. Here's how to make it happen:
void   
  CFolderButton::OnLButtonDblClk  (UINT nFlags, CPoint pt)
{
  SendMessage(WM_LBUTTONDOWN,   
  nFlags,   
  MAKELONG(pt.x,pt.y));
}
Another mouse feature is this: if the user holds the arrow button down, the tabs continue scrolling until either the user lets go or there are no more tabs to scroll. To implement this, CFolderButton::OnLButtonDown sets a timer. When the timer fires, CFolderButton sends its parent a WM_COMMAND, as if the button had been pressed.
   void CFolderButton::OnTimer(UINT nIDEvent)
   {
     GetParent()->SendMessage(WM_COMMAND, GetDlgCtrlID());
   }
Actually, this is somewhat simplified from the real code in Figure 5. The real CFolderButton::OnTimer implements an initial delay, so the continuous scroll feature works the way keyboards do: there's a slight delay before the repeat kicks in.

Ftab.h
////////////////////////////////////////////////////////////////
// MSDN Magazine — October 2002
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio 6.0 on Windows XP. Tab size=3.
//
// Only the new code relevant to adding CFolderButton is shown
//
#pragma once

// folder tab control style flags
#define FTS_FULLBORDER  0x1 // draw full border
#define FTS_BUTTONS     0x2 // draw next/prev buttons

class CFolderTab; // fwd ref

//////////////////
// Next/prev folder button to navigate tabs when they don't all fit
//
class CFolderButton : public CButton
{
public:
   BOOL Create(DWORD dwStyle, CWnd* pParent, const RECT& rc, UINT nID) {
      return CButton::Create(NULL, dwStyle|BS_OWNERDRAW, rc, pParent, nID);
   }

protected:
   int  m_nTimerClick;                  // for initial scroll delay

   // paint function
   virtual void DrawItem(LPDRAWITEMSTRUCT lpDis);
   afx_msg void OnPaint();
   afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
   afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
   afx_msg void OnLButtonDblClk(UINT nFlags, CPoint pt);
   afx_msg void OnTimer(UINT nIDEvent);
   DECLARE_DYNAMIC(CFolderButton);
   DECLARE_MESSAGE_MAP()
};

//////////////////
// Folder tab control, similar to tab control
//
class CFolderTabCtrl : public CWnd 
{
protected:
   CFolderButton m_wndButton[2];        // folder buttons
   int         m_cxButtons;             // width of buttons
   int         m_iFirstTab;             // first tab to show
   •••
   // helpers
   void UpdateButtons();

protected:
   afx_msg int  OnCreate(LPCREATESTRUCT lpcs);
   afx_msg void OnPaint();
   afx_msg void OnSize(UINT nType, int cx, int cy);
   afx_msg void OnNextTab();
   afx_msg void OnPrevTab();
   DECLARE_DYNAMIC(CFolderTabCtrl);
   DECLARE_MESSAGE_MAP()
};
Ftab.cpp
////////////////////////////////////////////////////////////////
// MSDN Magazine — October 2002
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio 6.0 on Windows XP. Tab size=3.
//
// Only the new code relevant to adding CFolderButton is shown
//
#include "stdafx.h"
#include "ftab.h"

//////////////////////////////////////////////////////////////////
// CFolderTabCtrl

// old stuff as in May 1999
•••
//////////////////
// Folder tab was created: create scroll buttons if style says so.
//
int CFolderTabCtrl::OnCreate(LPCREATESTRUCT lpcs)
{
   if (CWnd::OnCreate(lpcs)!=0)
      return -1;

   if (m_dwFtabStyle & FTS_BUTTONS) {
      CRect rc;
      for (int id=FTBPREV; id<=FTBNEXT; id++) {
         VERIFY(m_wndButton[id-1].Create(WS_VISIBLE|WS_CHILD, this, 
                                         rc, id));
      }
      m_cxButtons = 2*CXBUTTON;
   }
   return 0;
}

//////////////////
// Paint function
//
void CFolderTabCtrl::OnPaint() 
{
   CPaintDC dc(this); // device context for painting

   int xOrigin = m_cxButtons - GetTab(m_iFirstTab)->GetRect().left;
   dc.SetViewportOrg(xOrigin,0);

   // old stuff as in May 1999
   •••
}

//////////////////
// Folder tabs changed size: reposition scroll buttons.
//
void CFolderTabCtrl::OnSize(UINT nType, int cx, int cy)
{
   if (m_wndButton[0].m_hWnd) {
      int w = cy;
      CRect rc(0,0,w,cy);
      for (int i=FTBPREV; i<=FTBNEXT; i++) {
         m_wndButton[i-1].MoveWindow(&rc);
         rc += CPoint(w,0);
      }
      UpdateButtons();
   }
}

//////////////////
// Determine enabled state of scroll buttons
//
void CFolderTabCtrl::UpdateButtons()
{
   if (m_wndButton[0].m_hWnd && !m_lsTabs.IsEmpty()) {
      // enable prev button iff more tabs to left.
      m_wndButton[0].EnableWindow(m_iFirstTab>0);

      // enable next button if more tabs to right
      CRect rc;
      GetClientRect(&rc);
      int xOrigin = m_cxButtons - GetTab(m_iFirstTab)->GetRect().left;
      CRect rcLast = ((CFolderTab*)m_lsTabs.GetTail())->GetRect();
      m_wndButton[1].EnableWindow(xOrigin + rcLast.right>rc.right);
   }
}

//////////////////
// User clicked next button: increment starting tab and repaint
//
void CFolderTabCtrl::OnNextTab()
{
   if (m_iFirstTab < m_lsTabs.GetCount()-1) {
      m_iFirstTab++;
      Invalidate();
      UpdateButtons();
   }
}

//////////////////
// User clicked prev button: decrement starting tab and repaint
//
void CFolderTabCtrl::OnPrevTab()
{
   if (m_iFirstTab > 0) {
      m_iFirstTab—;
      Invalidate();
      UpdateButtons();
   }
}

////////////////////////////////////////////////////////////////
// CFolderButton
//
IMPLEMENT_DYNAMIC(CFolderButton, CButton)
BEGIN_MESSAGE_MAP(CFolderButton, CButton)
   ON_WM_LBUTTONDOWN()
   ON_WM_LBUTTONUP()
   ON_WM_LBUTTONDBLCLK()
   ON_WM_TIMER()
END_MESSAGE_MAP()

//////////////////
// Draw scroll button: draw a black triangle.
//
void CFolderButton::DrawItem(LPDRAWITEMSTRUCT lpDis)
{
   DRAWITEMSTRUCT& dis = *lpDis;
   CDC& dc = *CDC::FromHandle(dis.hDC);
   CRect rc;
   GetClientRect(&rc);

   // fill background with 3D face color
   dc.FillSolidRect(&rc,GetSysColor(COLOR_3DFACE));

   // shift southeast if button is pressed (bDown)
   BOOL bDown = dis.itemState & ODS_SELECTED;
   if (bDown) {
      rc += CPoint(1,1);
   }

   // draw line above to match folder tabs
   CPen pen(PS_SOLID,1,
      dis.itemState & ODS_DISABLED ? GetSysColor(COLOR_3DSHADOW) : 
      RGB(0,0,0));
   CPen* pOldPen = dc.SelectObject(&pen);
   dc.MoveTo(rc.TopLeft());
   dc.LineTo(rc.right,rc.top);

   // Draw 3D highlight rect for 3D look
   CRect rc2=rc;
   for (int i=0; i<2; i++) {
      dc.Draw3dRect(&rc2,
         GetSysColor(bDown ? COLOR_3DFACE : COLOR_3DHIGHLIGHT),
         GetSysColor(COLOR_3DSHADOW));
      rc2.right—;
      rc2.bottom—;
   }

   // Draw triangle pointing the right way. Use shadow color if disabled.
   CSize szArrow = CSize(4,7);
   int cyMargin = (rc.Height()-szArrow.cy)/2;
   int cxMargin = (rc.Width()-szArrow.cx)/2;
   int x, incr;
   if (dis.CtlID==FTBNEXT) {
      x = rc.left + cxMargin;
      incr = 1;
   } else {
      x = rc.right - cxMargin - 1;
      incr = -1;
   }
   int y = rc.top + cyMargin;
   int h = 7;
   for (int j=0; j<4; j++) {
      dc.MoveTo(x,y);
      dc.LineTo(x,y+h);
      h-=2;
      x += incr;
      y++;
   }
   dc.SelectObject(pOldPen);
}

//////////////////
// User clicked button.
//
void CFolderButton::OnLButtonDown(UINT nFlags, CPoint pt)
{
   Default();                           // will send WM_COMMAND to parent
   SetTimer(1,500,NULL);                // set timer for continual scroll
   m_nTimerClick = 0;                   // counter for initial delay
}

//////////////////
// User let go of the mouse.
//
void CFolderButton::OnLButtonUp(UINT nFlags, CPoint pt)
{
   KillTimer(1);                        // no more repeat
   Default();
}

//////////////////
// Double-click: treat as another click.
//
void CFolderButton::OnLButtonDblClk(UINT nFlags, CPoint pt)
{
   SendMessage(WM_LBUTTONDOWN,nFlags,MAKELONG(pt.x,pt.y));
}

//////////////////
// Timer click: send another WM_COMMMAND, as if button clicked.
//
void CFolderButton::OnTimer(UINT nIDEvent)
{
   if (IsWindowEnabled()) {
      if (m_nTimerClick++ == 0) {
         KillTimer(1);
         SetTimer(1,150,NULL);
      }
      GetParent()->SendMessage(WM_COMMAND, GetDlgCtrlID());
   } else {
      KillTimer(1);
   }
}
CFolderButton doesn't know anything about folder tabs or how to scroll them. It knows only how to paint and do the mouse action. When the user clicks, Windows sends WM_COMMAND/BN_CLICKED to the parent: in this case, CFolderTabCtrl. CFolderTabCtrl is where the tabs are scrolled. CFolderTabCtrl manages the combined buttons-and-tabs control, the same way a combobox manages its edit control, dropdown button, and listbox.
Adding scroll buttons to the original CFolderTabCtrl requires several modifications. First, you have to create the buttons. Whenever you have a compound control with child windows, the place to instantiate the children is OnCreate.
// in CFolderTabCtrl::OnCreate
if (m_dwFtabStyle & FTS_BUTTONS) {
  CRect rc;
  for (int id=FTBPREV; id<=FTBNEXT; id++) {
    VERIFY(m_wndButton[id-1].Create(
      WS_VISIBLE|WS_CHILD, this, rc, id));
  }
  m_cxButtons = 2*CXBUTTON;
}
FTS_BUTTONS is a new CFolderTabCtrl style to show the buttons. FTBPREV and FTBNEXT are enum codes with values 1 and 2, used as IDs for the buttons.
In Windows, whenever you have kids, you must size them. CFolderTabCtrl::OnSize does the job. Now that the folder tab has buttons, you have to modify CFolderTabCtrl::OnPaint to draw the tabs to the right of the buttons. Instead of editing all the paint code from 1999, you can simply change the viewport:
// x origin = 
//  (width of buttons) - (first tab's x pos);
int xOrigin = m_cxButtons - 
  GetTab(m_iFirstTab)->GetRect().left;
dc.SetViewportOrg(xOrigin,0);
Setting the viewport origin lets you shift the tabs to the right without altering the original code. The translation happens at a low level, inside GDI.
Finally, the last piece of the puzzle is handling the button clicks. These arrive at CFolderTabCtrl as WM_COMMAND/BN_CLICKED. Here's how CFolderTabCtrl handles the Next button:
BEGIN_MESSAGE_MAP(CFolderTabCtrl, CWnd)
  •••
  ON_BN_CLICKED(FTBNEXT,OnNextTab)
END_MESSAGE_MAP()
   
void CFolderTabCtrl::OnNextTab()
{
  if (m_iFirstTab < m_lsTabs.GetCount()-1) {
    m_iFirstTab++;
    Invalidate();
    UpdateButtons();
  }
}
CFolderTabCtrl increments m_iFirstTab and repaints. It also calls a new function UpdateButtons (see Figure 5) to update the enabled state of the buttons. If the first tab is visible (no more tabs to the left), UpdateButtons disables the left button; if the last tab is completely visible (no more tabs to the right), UpdateButtons disables the right button. Lovely. Figure 4 shows the final result—ImgView3 running with scroll buttons and 13 page tabs. The tabs don't actually do anything except consume screen space. The code in Figure 5 shows only the significant changes from the original CFolderTabCtrl. The full program is too long to print, but you can download it as always from the the link at the top of this article. Happy programming!

Send questions and comments for Paul to cppqa@microsoft.com.


Paul DiLasciais a freelance writer, consultant, and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). Paul can be reached at askpd@pobox.com or http://www.dilascia.com.

Page view tracker