C++ Q&A

Desktop Location, sscanf Equivalents in C#, and More

Paul DiLascia

Code download available at: CQA0304.exe (154 KB)
Browse the Code Online

Q Can you tell me why the following C# form doesn't run where I want it to on my desktop?

public class MyForm : Form
{
  MyForm()
  {
    Text = "Why doesn't this form go where I want it to go?" ;
    DesktopLocation = new System.Drawing.Point (500, 500) ;
  }
  static void Main()
  {
    Run(new MyForm());
  }
}

The title bar text in the C# form is fine, but the DesktopLocation seems to be ignored.

Q Can you tell me why the following C# form doesn't run where I want it to on my desktop?

public class MyForm : Form
{
  MyForm()
  {
    Text = "Why doesn't this form go where I want it to go?" ;
    DesktopLocation = new System.Drawing.Point (500, 500) ;
  }
  static void Main()
  {
    Run(new MyForm());
  }
}

The title bar text in the C# form is fine, but the DesktopLocation seems to be ignored.

Doug Harrington

A You can set DesktopLocation all you want, but the Microsoft® .NET Framework won't pay attention unless you also set StartPosition in the following way:

StartPosition = FormStartPosition.Manual;

A You can set DesktopLocation all you want, but the Microsoft® .NET Framework won't pay attention unless you also set StartPosition in the following way:

StartPosition = FormStartPosition.Manual;

As Mensa members might guess, Form.StartPosition tells the .NET Framework where to start your form. The possible values are CenterParent, CenterScreen, Manual, WindowsDefaultBounds, and WindowsDefaultLocation. Even non-Mensa folks can guess what these mean. By default, StartPosition is WindowsDefaultLocation, the CreateWindow equivalent of CW_USEDEFAULT.

Some of you might wonder, what's the difference between Location and DesktopLocation? Answer: Location is relative to the screen, whereas DesktopLocation is relative to the desktop work area—that is, the screen minus the task bar. That's something to consider if you suspect your user may be crazy enough to dock her task bar on the top of the screen (I hear this is more common in the Southern Hemisphere).

With a top-docked task bar, setting Form.Location = (0,0) results in a partially hidden form, whereas DesktopLocation = (0,0) makes everything first rate. DesktopLocation also works on multiple-monitor systems. (If you aren't programming on a 9 × 12 wall-sized array of plasma screens, you're nobody.) And in case you're wondering where to find DesktopLocation in the Properties window in Visual Studio®—it's not there. You can only set DesktopLocation from code. What a concept!

Q If I want to parse a hex string in C++, I can use sscanf like so:

char hex[] = "0xFA10";
int i;
sscanf( hex, "%x", &i );

But is there a way for me to do this in the .NET Framework? Is there an equivalent to sscanf?

Q If I want to parse a hex string in C++, I can use sscanf like so:

char hex[] = "0xFA10";
int i;
sscanf( hex, "%x", &i );

But is there a way for me to do this in the .NET Framework? Is there an equivalent to sscanf?

Gary Miller

A There's no direct equivalent of sscanf in the .NET Framework; however, you can use Int32.Parse to parse a hex number like so:

Int32 i = Int32.Parse("0FA10", NumberStyles.HexNumber);

A There's no direct equivalent of sscanf in the .NET Framework; however, you can use Int32.Parse to parse a hex number like so:

Int32 i = Int32.Parse("0FA10", NumberStyles.HexNumber);

Note that in the .NET Framework, you must omit the leading "0x". HexNumber is actually a composite style, equivalent to the following flags: AllowLeadingWhite | AllowTrailingWhite | AllowHexSpecifier. In general, the Framework does parsing with—what else—a Parse method. Many classes in the Framework have one. There's Decimal.Parse and Double.Parse for decimals and doubles, IPAddress.Parse for IP addresses, and DateTime.Parse for dates. My favorite is Enum.Parse, which uses reflection to convert the text name of any Enum to its actual enum value. For example, the following code

Console.WriteLine("val={0:x}.", 
   Enum.Parse(typeof(NumberStyles),
   "AllowHexSpecifier"));

yields the line:

val=00000200.

To see which classes have Parse functions, open your MSDN® disc to the Index, select ".NET Framework" as your docset filter, and then type "Parse".

Q I have a small application that I distribute over the Internet. I've seen programs that display a message when a new version is available and ask the user if he wants to upgrade. How can I implement a version checker like that for my own app?

Q I have a small application that I distribute over the Internet. I've seen programs that display a message when a new version is available and ask the user if he wants to upgrade. How can I implement a version checker like that for my own app?

Shika Kataransky

A I'll answer in just a moment, but first let me use my little soapbox to warn against one of my pet peeves: programs that display annoying messages that require action. In particular, I hate programs that ask whether I want to upgrade. I always answer no and check the "stop bothering me" checkbox (hopefully there is one). There's nothing wrong with informing users that a new version is out, but please do it in a way that doesn't require action—except, of course, to get the new version.

A I'll answer in just a moment, but first let me use my little soapbox to warn against one of my pet peeves: programs that display annoying messages that require action. In particular, I hate programs that ask whether I want to upgrade. I always answer no and check the "stop bothering me" checkbox (hopefully there is one). There's nothing wrong with informing users that a new version is out, but please do it in a way that doesn't require action—except, of course, to get the new version.

Now that I've scared you away, there are many ways to implement the Web-based version checking. The February 2003 issue of MSDN Magazine has an article by Jason Clark that describes a whole new protocol called BITS for just this purpose (see BITS: Write Auto-Updating Apps with .NET and the Background Intelligent Transfer Service API).

On the other hand, if all you really want is something that is quick and simple, you can store your current version number in a text file on your Web site and download it via FTP. Downloading is easy with the Windows® Internet API, also known as WinInet. Just open a connection, open an FTP session, open the file, and read. Here's the sequence in a nutshell:

HINTERNET h = InternetOpen(...);
HINTERNET hftp = InternetConnect(..,INTERNET_SERVICE_FTP,..);
HINTERNET hftpfile = FtpOpenFile(...);
InternetReadFile(...);

As with all things, beauty dwells in the details, so let's dive deeper. I wrote a class called CWebVersion (see Figure 1) that encapsulates all you need for a simple over-the-Web version checker. You can use it in the following manner:

if (CWebVersion::Online()) {
  CWebVersion ver("ftp.mysite.com");
  if (ver.ReadVersion("MyVersionNumber.txt")) {
    DWORD maj = ver.dwVersionMS;
    DWORD min = ver.dwVersionLS;
  }
}

Figure 1 WebVersion

WebVersion.h

////////////////////////////////////////////////////////////////
// MSDN Magazine — April 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compile with Visual Studio .NET on Windows XP. Tab size=3.
//
#pragma once

//////////////////
// This class encapsulates over-the-Web version checking. It expects a
// text version file that contains four numbers separated by commas, the
// same format for FILEVERSION and PRODUCTVERSION in VS_VERSION_INFO.
// ReadVersion reads these numbers into dwVersionMS and dwVersionLS.
//
class CWebVersion {
protected:
   enum { BUFSIZE = 64 };
   LPCTSTR m_lpServer;                  // server name
   DWORD   m_dwError;                   // most recent error code
   TCHAR   m_errInfo[256];              // extended error info
   TCHAR   m_version[BUFSIZ];           // version number as text
   void    SaveErrorInfo();             // helper to save error info

public:
   DWORD dwVersionMS;      // version number: most-sig 32 bits
   DWORD dwVersionLS;      // version number: least-sig 32 bits

   CWebVersion(LPCTSTR server) : m_lpServer(server) { }
   ~CWebVersion() { }

   static  BOOL Online();
   BOOL    ReadVersion(LPCTSTR lpFileName);
   LPCTSTR GetVersionText()       { return m_version; }
   DWORD   GetError()             { return m_dwError; }
   LPCTSTR GetExtendedErrorInfo() { return m_errInfo; }
};

WebVersion.cpp

////////////////////////////////////////////////////////////////
// MSDN Magazine — April 2003
// 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.
//
#include "stdafx.h"
#include "WebVersion.h"
#include "InetHandle.h"

//////////////////
// Check if connected to Internet.
//
BOOL CWebVersion::Online()
{
    DWORD dwState = 0; 
    DWORD dwSize = sizeof(DWORD);
    return InternetQueryOption(NULL,
       INTERNET_OPTION_CONNECTED_STATE, &dwState, &dwSize)
       && (dwState & INTERNET_STATE_CONNECTED);
}

//////////////////
// Read version number as string into buffer
//
BOOL CWebVersion::ReadVersion(LPCTSTR lpFileName)
{
   CInternetHandle hInternet;
   CInternetHandle hFtpSession;
   CInternetHandle hFtpFile;

   m_version[0] = 0;
   m_dwError=0;                         // assume success
   m_errInfo[0]=0;                      // ..

   DWORD nRead=0;
   hInternet = InternetOpen(NULL, INTERNET_OPEN_TYPE_DIRECT, NULL, 
                            NULL, 0);
   if (hInternet!=NULL) {
      hFtpSession = InternetConnect(hInternet, m_lpServer,
         INTERNET_DEFAULT_FTP_PORT, NULL, NULL, INTERNET_SERVICE_FTP, 
         0, NULL);

      if (hFtpSession!=NULL) {
         hFtpFile = FtpOpenFile(hFtpSession, lpFileName,
            GENERIC_READ, FTP_TRANSFER_TYPE_ASCII, NULL);

         if (hFtpFile!=NULL) {
            InternetReadFile(hFtpFile, m_version, BUFSIZE, &nRead);
            if (nRead>0) {
               m_version[nRead] = 0;
               int Mhi,Mlo,mhi,mlo;
               _stscanf(m_version, "%x,%x,%x,%x", &Mhi, &Mlo, &mhi, &mlo);
               dwVersionMS = MAKELONG(Mlo,Mhi);
               dwVersionLS = MAKELONG(mlo,mhi);
               return TRUE;
            }
         }
      }
   }

   // Failed: save error code and extended error info if any.
   m_dwError = GetLastError();
   if (m_dwError==ERROR_INTERNET_EXTENDED_ERROR) {
      DWORD dwErr;
      DWORD len = sizeof(m_errInfo)/sizeof(m_errInfo[0]);
      InternetGetLastResponseInfo(&dwErr, m_errInfo, &len);
   }

   return FALSE;
}

CWebVersion::Online first checks to see if the user is online. I'll let you decide what to do when the user is offline—my recommendation is to do nothing, or display a "Check for latest version" button so users can explicitly initiate the connection. (The WinInet function for this is InternetAttemptConnect.) Whatever you do, don't connect without asking. Many people still use modems in order to surf the Web.

Assuming your application has access to a connection, all you have to do to read the version number is instantiate a CWebVersion object, passing the address of your FTP server, and call CWebVersion::ReadVersion with the name of your version file. ReadVersion expects a one-line text file that looks something like this:

4,3,0,0

That is, four numbers separated by commas, the same format as the FILEVERSION and PRODUCTVERSION fields in your VS_VERSION_INFO resource. ReadVersion reads the numbers into two 32-bit DWORDs, dwVersionMS and dwVersionLS. Windows uses 64-bit version numbers, so you can release 18,446,744,070,000,000,000 versions before cycling. That's 18 quintillion in the U.S., but only 18 trillion in Europe. It would take 8 million programmers 75,000 years to write that many versions, if they released a new one every second. You better hurry up and hone up your typing skills!

Figure 2 shows a test program called GetVersion that uses CWebVersion to read a version number over FTP; Figure 3 shows the program running.

Figure 2 GetVersion.cpp

////////////////////////////////////////////////////////////////
// MSDN Magazine — April 2003
// 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.
//
#include "stdafx.h"
#include "WebVersion.h"

////////////////
// This program shows how to use CWebVersion, a class that reads a
// version number from your Web site using FTP. You can use it to
// test-read your own version number before adding CWebVersion to your
// app. To use it, open a command window and type:
//
// GetVersion <version-filename> <ftp.my.server.name>
//

LPCTSTR GetInetErrorName(DWORD err);

int _tmain(int argc, _TCHAR* argv[])
{
   if (argc != 3) {
      printf("\nGetVersion 1-1-2003 Paul DiLascia\n");
      printf("Illustrates how to retrieve a program version 
             number from the web.\n");
      printf("usage: GetVersion filename server\n");
      printf(" filename = name of ASCII file containing version 
             number\n");
      printf(" server   = name of FTP server, eg, ftp.mysite.com\n");
      return -1;
   }

   if (CWebVersion::Online()) {
      CWebVersion webver(argv[2]);
      if (webver.ReadVersion(argv[1])) {
         printf("Major version = %08x\n", webver.dwVersionMS);
         printf("Minor version = %08x\n", webver.dwVersionLS);
         printf("Text = %s\n", webver.GetVersionText());
      
      } else {
         DWORD err = webver.GetError();
         printf("%s = %d\n", GetInetErrorName(err), err);
         if (err==ERROR_INTERNET_EXTENDED_ERROR) {
            printf("%s\n", webver.GetExtendedErrorInfo());
         }
      }
   } else {
      printf("Can't get version: not online\n");
   }
   return 0;
}

//////////////////
// Handy helper function to get name of ERROR_INTERNET_XXX code.
// (Debug only)
//
LPCTSTR GetInetErrorName(DWORD err)
{
   static struct {
      DWORD err;
      LPCTSTR name;
   } errors[] = {
      { ERROR_INTERNET_OUT_OF_HANDLES,_T("ERROR_INTERNET_OUT_OF_HANDLES") },
      { ERROR_INTERNET_TIMEOUT,_T("ERROR_INTERNET_TIMEOUT") },
         •••
         // more
      { 0, NULL }
   };

   for (int i=0; errors[i].name; i++) {
      if (err==errors[i].err) {
         return errors[i].name;
      }
   }
   return _T("Unknown error");
}

Figure 3 GetVersion in Action

Figure 3** GetVersion in Action **

How does CWebVersion work? Most of the functions are self-explanatory; however, a few details merit explanation. To see if the system is connected, CWebVersion::Online calls a WinInet function InternetQueryOption to get the INTERNET_OPTION_CONNECTED_STATE. If the state returned has the INTERNET_STATE_CONNECTED flag, you're live. To access the Internet and read the version file, CWebVersion uses another home-grown class, CInternetHandle, which encapsulates HINTERNET (see Figure 4). CInternetHandle's raison d'être is to make error-checking easier. If a WinInet function fails, GetLastError tells why. But CloseInternetHandle clears the error code, so you have to call GetLastError before CloseInternetHandle. You end up with code that looks something like Figure 5. With CInternetHandle, the code is much cleaner: ReadVersion only has to check the error at the end. C++ automatically closes all the CInternetHandles as flow control leaves your function.

Figure 5 yuck.cpp

////////////////////////////////////////////////////////////////
// This is what your code would look like without CInternetHandle.
// You have to call GetLastError in several places because
// CloseInternetHandle clears the error code.
//
DWORD err;
HINTERNET h = InternetOpen(...);
if (h!=NULL) {
   HINTERNET hftp = InternetConnect(..,INTERNET_SERVICE_FTP,..);
   if (hftp!=NULL) {
      HINTERNET hftpfile = FtpOpenFile(...);
      if (hftpfile!=NULL) {
         InternetReadFile(...);
         if (/* success */) {
            // do something
         } else {
            err = GetLastError();
         }
         InternetCloseHandle(hftpfile);
      } else {
         err = GetLastError();
      }
      InternetCloseHandle(hftp);
   } else {
      err = GetLastError();
   }
   InternetCloseHandle(h);
} else {
   err = GetLastError();
}

Figure 4 InternetHandle.h

////////////////////////////////////////////////////////////////
// MSDN Magazine — April 2003
// 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.
//
#pragma once

//////////////////
// This handy class encapsulates an Internet handle so you never have to
// remember to call CloseInternetHandle. More importantly,
// CInternetHandle lets you postpone the call to CloseInternetHandle
// until control leaves the scope of your function, so you can get error
// information when something bad happens before CloseInternetHandle
// clears the error code.
//
class CInternetHandle {
protected:
   HINTERNET m_handle; // underlying handle

public:
   CInternetHandle() : m_handle(NULL) { }
   CInternetHandle(HINTERNET h) : m_handle(h) { }

   ~CInternetHandle() { Close(); }

   // Close handle and set to NULL so I don't close again.
   void Close() {
      if (m_handle) {
         InternetCloseHandle(m_handle);
         m_handle = NULL;
      }
   }

   // Assignment from HINTERNET
   CInternetHandle& operator= (HINTERNET h)
   {
      ASSERT(m_handle==NULL); // only initial assignment, not copy
      m_handle = h;
      return *this;
   }

   // cast to HINTERNET
   operator HINTERNET() {
      return m_handle;
   }
};

As MFC programmers know, MFC already has classes that encapsulate WinInet, so why didn't I use MFC instead of writing my own CInternetHandle? Because it's a waste to load all of MFC when all I need is a few lines of code. Less code is usually better than more. By making CWebVersion independent of MFC, non-MFC apps can use it too—without worrying whether users have the latest DLLs installed. That's a big plus, especially for applications you deliver over the Web.

Figure 7 Get Update Dialog

Figure 7** Get Update Dialog **

To put everything together in a real app, I added over-the-Web version checking to the TraceWin program from last month's column. Figure 6 shows the changes (you can download the full program from the link at the top of this article). TraceWin reads the Web version when it starts, and stores it in a data member m_dwNewVersion (TraceWin only uses 32 bits for its version number). The About dialog compares this version number with the one stored in the program's VS_VERSION_INFO resource, which was stored when the program was compiled. If the Web version is newer, the dialog displays a "Get version XXX" link (see Figure 7). If the Web version is the same or unavailable (because the user is offline), TraceWin hides the link. Please note that TraceWin reads the version number only once, when it first starts—not every time the user invokes About. Reading a 10-byte file over FTP is fast enough to escape notice during startup, but slow enough to delay a dialog. Even with small files, you should be careful when and where you FTP. If you want the fastest possible UI, download the file in a separate thread. Happy programming!

Figure 6 TraceWin.cpp

////////////////////////////////////////////////////////////////
// Changes to TraceWin to add live version checking.
//

#include "stdafx.h"
#include "WebVersion.h"

/////////////////
// Application class
//
class CTraceWinApp : public CWinApp
{
public:
   DWORD m_dwNewVersion;
   •••
} App;

BOOL CTraceWinApp::InitInstance()
{

   ••• // create frame, etc

   // read current version number from Web
   m_dwNewVersion = 0; // assume failure
   if (CWebVersion::Online()) {
      CWebVersion ver(_T("ftp.dilascia.com"));
      if (ver.ReadVersion(_T("TraceWinVer")))
         m_dwNewVersion = ver.dwVersionMS;
   }

   return TRUE;
}

//////////////////
// Custom about dialog uses CStaticLink for hyperlinks.
//
class CAboutDialog : public CDialog {
protected:
   •••
   CStaticLink m_wndLink3; // add hyperlink to download update..
   CStatic m_wndVersion;   // ..and static control to display my version
};

BOOL CAboutDialog::OnInitDialog()
{
   •••
   // subclass static controls
   m_wndLink3.SubclassDlgItem(IDC_URLUPDATE,this);
   m_wndVersion.SubclassDlgItem(IDC_VERSION,this);

   // Read my own version info and set static text
   CMyVersionInfo vi;
   CString s;
   s.Format(_T("TraceWin Version %d.%02d by Paul DiLascia"),
      HIWORD(vi.dwProductVersionMS),LOWORD(vi.dwProductVersionMS));
   m_wndVersion.SetWindowText(s);

   // compare my version to current version on Web site
   if (App.m_dwNewVersion && vi.dwProductVersionMS < App.m_dwNewVersion) {
      // current version is newer: display link
      s.Format(_T("Get version %d.%02d"),
         HIWORD(App.m_dwNewVersion),LOWORD(App.m_dwNewVersion));
      m_wndLink3.SetWindowText(s);

   } else {
      // current version not available: hide link
      m_wndLink3.ShowWindow(SW_HIDE);
   }
   return TRUE;
}

//////////////////
// Handle Help | About : run the About dialog
//
void CTraceWinApp::OnAppAbout()
{
   static CAboutDialog dlg;
   dlg.DoModal();
}

Send your 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 https://www.dilascia.com.