Visual Studio Community 2013 エディション

遅ればせながら Visual Studio Community 2013 エディションの使用を開始しました。

個人でソフトウェア開発をおこなっている行っている人には大変うれしい公開です。

マイクロソフトの無償で利用できる開発製品は Visual Studio Express エディションがあり、現在 Visual Studio Express 2015 がでていますが、将来的には Community エディションに統合していくようです。

プロフェッショナル エディション相当の機能が使えて、学生、オープンソース開発者はもちろん個人開発者や小規模企業の開発者5名までなら有償アプリの開発を行ってもよいライセンスです。詳細は:Visual Studio Community 2013 の公式サイト

インストールは上記サイトより、

  1. 「Visual Studio Community 2013 ダウンロード」をクリック
  2. 「Visual Studio のダウンロード」ページより「Visual Studio Community & Express」の「Community 2014 with Update 4」をクリック
  3. 「Microsoft Visual Studio Community 2013 with Update 4 – English」の 「Install now」をクリックして、「vs_community.exe」をダウンロードします。
  4. 「Microsoft Visual Studio 2013 Language Pack – 日本語」の「今すぐインストール」をクリックして、「vs_langpack.exe」をダウンロードします。
  5. vs_community.exe を実行します。
  6. vs_langpack.exe を実行します。

なによりもうれしいのは VC++ コンパイラだけでなくて、MFC ライブラリやコード分析機能が使えることです。昔の VC++ と MFC ライブラリで開発したコードを、コード分析して安全なコードに書き直したり、マネージ拡張して機能強化したりと楽しみ 🙂

手始めに Visual Studio 2005 で作成したプロジェクトを Visual Studio Community 2013 で読み込んで再ビルドしてみます。結果、後日報告します。

VC++/MFC タイムゾーンによる日時変換 (JST->UTC)

VC++/MFC ライブラリで日本時間(JST)を世界標準時間(UTC)へ変換行う方法を調べて試しました。
もっと良い方法があるのかもしれませんが、取り急ぎ。

// 現在の日時を取得
CTime t1;
t1 = CTime::GetCurrentTime();
_tprintf(_T("JST: %sn"), t1.Format(_T("%Y-%m-%d %H:%M:%S")));

// FormatGmt でUTC日時で表示
_tprintf(_T("UTC: %sn"), t1.FormatGmt(_T("%Y-%m-%d %H:%M:%S")));

// タイムゾーン情報の取得.
TIME_ZONE_INFORMATION tzi;
GetTimeZoneInformation(&tzi);
CString stzi(tzi.StandardName); // Unicode -> Shift-JIS.
_tprintf(_T("タイムゾーン: %sn"), (LPCTSTR)stzi);

// CTime -> SYSTEMTIME へ変換
SYSTEMTIME st1 = { 0 };
st1.wYear = t1.GetYear();
st1.wMonth = t1.GetMonth();
st1.wDay = t1.GetDay();
st1.wHour = t1.GetHour();
st1.wMinute = t1.GetMinute();
st1.wSecond = t1.GetSecond();

SYSTEMTIME st2 = { 0 };

// タイムゾーンによる日時変換
TzSpecificLocalTimeToSystemTime(&tzi, &st1, &st2);
// 表示
_tprintf(_T("UTC: %d-%02d-%02d %02d:%02d:%02dn"),
                        st2.wYear, st2.wMonth, st2.wDay,
                        st2.wHour, st2.wMinute, st2.wSecond);

出力結果:

JST: 2013-02-26 12:27:11
UTC: 2013-02-26 03:27:11
タイムゾーン: 東京 (標準時)
UTC: 2013-02-26 03:27:11

Windowsの特別なフォルダ(SHFolders)の列挙

Windows XP, Windows Vista, Windows 7 で動作するアプリを作成する上で、アプリ用のデータを保存するフォルダーとか、個人用のデータを保存するフォルダーなどを Windows 既定の場所を使用したいと思い、そのフォルダーの取得方法を調べました。

MSDNによると、CSIDL を使用する方法はWindows Vista 以降は、互換性のために残されていて、今後は、KNOWNFOLDERID を使用するようにとのことです。

CSIDL:http://msdn.microsoft.com/en-us/library/bb762494%28v=vs.85%29.aspx
KNOWNFOLDERID: http://msdn.microsoft.com/en-us/library/dd378457%28v=vs.85%29.aspx

CSIDL の取得

CSIDL の値をすべて配列にいれておいて、ループで実際のフォルダー名を列挙してます。

// SHFolders.cpp : Known Folders の列挙 
//

#include "stdafx.h"
#include <shlobj.h>


int _tmain(int argc, _TCHAR* argv[])
{
    TCHAR szPath[MAX_PATH];
    typedef struct {
        int csidl;
        TCHAR szId[50];
    } SHFOLDER;

    // CSIDL http://msdn.microsoft.com/en-us/library/bb762494%28v=vs.85%29.aspx

    SHFOLDER folders[] = {
        { CSIDL_ADMINTOOLS, _T("CSIDL_ADMINTOOLS") },
        { CSIDL_ALTSTARTUP, _T("CSIDL_ALTSTARTUP") },
        { CSIDL_APPDATA, _T("CSIDL_APPDATA") },
        { CSIDL_BITBUCKET, _T("CSIDL_BITBUCKET") },
        { CSIDL_CDBURN_AREA, _T("CSIDL_CDBURN_AREA") },
        { CSIDL_COMMON_ADMINTOOLS, _T("CSIDL_COMMON_ADMINTOOLS") },
        { CSIDL_COMMON_ALTSTARTUP, _T("CSIDL_COMMON_ALTSTARTUP") },
        { CSIDL_COMMON_APPDATA, _T("CSIDL_COMMON_APPDATA") },
        { CSIDL_COMMON_DESKTOPDIRECTORY, _T("CSIDL_COMMON_DESKTOPDIRECTORY") },
        { CSIDL_COMMON_DOCUMENTS, _T("CSIDL_COMMON_DOCUMENTS") },
        { CSIDL_COMMON_FAVORITES, _T("CSIDL_COMMON_FAVORITES") },
        { CSIDL_COMMON_MUSIC, _T("CSIDL_COMMON_MUSIC") },
        { CSIDL_COMMON_OEM_LINKS, _T("CSIDL_COMMON_OEM_LINKS") },
        { CSIDL_COMMON_PICTURES, _T("CSIDL_COMMON_PICTURES") },
        { CSIDL_COMMON_PROGRAMS, _T("CSIDL_COMMON_PROGRAMS") },
        { CSIDL_COMMON_STARTMENU, _T("CSIDL_COMMON_STARTMENU") },
        { CSIDL_COMMON_STARTUP, _T("CSIDL_COMMON_STARTUP") },
        { CSIDL_COMMON_TEMPLATES, _T("CSIDL_COMMON_TEMPLATES") },
        { CSIDL_COMMON_VIDEO, _T("CSIDL_COMMON_VIDEO") },
        { CSIDL_COMPUTERSNEARME, _T("CSIDL_COMPUTERSNEARME") },
        { CSIDL_CONNECTIONS, _T("CSIDL_CONNECTIONS") },
        { CSIDL_CONTROLS, _T("CSIDL_CONTROLS") },
        { CSIDL_COOKIES, _T("CSIDL_COOKIES") },
        { CSIDL_DESKTOP, _T("CSIDL_DESKTOP") },
        { CSIDL_DESKTOPDIRECTORY, _T("CSIDL_DESKTOPDIRECTORY") },
        { CSIDL_DRIVES, _T("CSIDL_DRIVES") },
        { CSIDL_FAVORITES, _T("CSIDL_FAVORITES") },
        { CSIDL_FONTS, _T("CSIDL_FONTS") },
        { CSIDL_HISTORY, _T("CSIDL_HISTORY") },
        { CSIDL_INTERNET, _T("CSIDL_INTERNET") },
        { CSIDL_INTERNET_CACHE, _T("CSIDL_INTERNET_CACHE") },
        { CSIDL_LOCAL_APPDATA, _T("CSIDL_LOCAL_APPDATA") },
        { CSIDL_MYDOCUMENTS, _T("CSIDL_MYDOCUMENTS") },
        { CSIDL_MYMUSIC, _T("CSIDL_MYMUSIC") },
        { CSIDL_MYPICTURES, _T("CSIDL_MYPICTURES") },
        { CSIDL_MYVIDEO, _T("CSIDL_MYVIDEO") },
        { CSIDL_NETHOOD, _T("CSIDL_NETHOOD") },
        { CSIDL_NETWORK, _T("CSIDL_NETWORK") },
        { CSIDL_PERSONAL, _T("CSIDL_PERSONAL") },
        { CSIDL_PRINTERS, _T("CSIDL_PRINTERS") },
        { CSIDL_PRINTHOOD, _T("CSIDL_PRINTHOOD") },
        { CSIDL_PROFILE, _T("CSIDL_PROFILE") },
        { CSIDL_PROGRAM_FILES, _T("CSIDL_PROGRAM_FILES") },
        { CSIDL_PROGRAM_FILESX86, _T("CSIDL_PROGRAM_FILESX86") },
        { CSIDL_PROGRAM_FILES_COMMON, _T("CSIDL_PROGRAM_FILES_COMMON") },
        { CSIDL_PROGRAM_FILES_COMMONX86, _T("CSIDL_PROGRAM_FILES_COMMONX86") },
        { CSIDL_PROGRAMS, _T("CSIDL_PROGRAMS") },
        { CSIDL_RECENT, _T("CSIDL_RECENT") },
        { CSIDL_RESOURCES, _T("CSIDL_RESOURCES") },
        { CSIDL_RESOURCES_LOCALIZED, _T("CSIDL_RESOURCES_LOCALIZED") },
        { CSIDL_SENDTO, _T("CSIDL_SENDTO") },
        { CSIDL_STARTMENU, _T("CSIDL_STARTMENU") },
        { CSIDL_STARTUP, _T("CSIDL_STARTUP") },
        { CSIDL_SYSTEM, _T("CSIDL_SYSTEM") },
        { CSIDL_SYSTEMX86, _T("CSIDL_SYSTEMX86") },
        { CSIDL_TEMPLATES, _T("CSIDL_TEMPLATES") },
        { CSIDL_WINDOWS, _T("CSIDL_WINDOWS") }
    };

    // フォルダーの列挙
    for (int i = 0; i < _countof(folders); i++)
    {
        if (SUCCEEDED(SHGetFolderPath(NULL, 
                                       folders[i].csidl | CSIDL_FLAG_DONT_VERIFY, 
                                       NULL, 
                                      0, 
                                      szPath))) 
        {
            _tprintf(_T("%s:%sn"), folders[i].szId, szPath);
        }
    }

    return 0;
}

特別なフォルダの列挙

上記プログラムの実行結果は以下のとおりです。(フォルダ名の一部に個人名が使用されているところは編集しました。)

CSIDL_ADMINTOOLS:C:Documents and Settingsスタート メニュープログラム管理ツール
CSIDL_ALTSTARTUP:
CSIDL_APPDATA:C:Documents and SettingsApplication Data
CSIDL_CDBURN_AREA:C:Documents and SettingsLocal SettingsApplication DataMicrosoftCD Burning
CSIDL_COMMON_ADMINTOOLS:C:Documents and SettingsAll Usersスタート メニュープログラム管理ツール
CSIDL_COMMON_ALTSTARTUP:
CSIDL_COMMON_APPDATA:C:Documents and SettingsAll UsersApplication Data
CSIDL_COMMON_DESKTOPDIRECTORY:C:Documents and SettingsAll Usersデスクトップ
CSIDL_COMMON_DOCUMENTS:C:Documents and SettingsAll UsersDocuments
CSIDL_COMMON_FAVORITES:C:Documents and SettingsAll UsersFavorites
CSIDL_COMMON_MUSIC:C:Documents and SettingsAll UsersDocumentsMy Music
CSIDL_COMMON_OEM_LINKS:
CSIDL_COMMON_PICTURES:C:Documents and SettingsAll UsersDocumentsMy Pictures
CSIDL_COMMON_PROGRAMS:C:Documents and SettingsAll Usersスタート メニュープログラム
CSIDL_COMMON_STARTMENU:C:Documents and SettingsAll Usersスタート メニュー
CSIDL_COMMON_STARTUP:C:Documents and SettingsAll Usersスタート メニュープログラムスタートアップ
CSIDL_COMMON_TEMPLATES:C:Documents and SettingsAll UsersTemplates
CSIDL_COMMON_VIDEO:C:Documents and SettingsAll UsersDocumentsMy Videos
CSIDL_COOKIES:C:Documents and SettingsCookies
CSIDL_DESKTOP:C:Documents and Settingsデスクトップ
CSIDL_DESKTOPDIRECTORY:C:Documents and Settingsデスクトップ
CSIDL_FAVORITES:C:Documents and SettingsFavorites
CSIDL_FONTS:C:WINDOWSFonts
CSIDL_HISTORY:C:Documents and SettingsLocal SettingsHistory
CSIDL_INTERNET_CACHE:C:Documents and SettingsLocal SettingsTemporary Internet Files
CSIDL_LOCAL_APPDATA:C:Documents and SettingsLocal SettingsApplication Data
CSIDL_MYMUSIC:C:Documents and SettingsMy DocumentsMy Music
CSIDL_MYPICTURES:C:Documents and SettingsMy DocumentsMy Pictures
CSIDL_MYVIDEO:C:Documents and SettingsMy DocumentsMy Videos
CSIDL_NETHOOD:C:Documents and SettingsNetHood
CSIDL_PERSONAL:C:Documents and SettingsMy Documents
CSIDL_PRINTHOOD:C:Documents and SettingsPrintHood
CSIDL_PROFILE:C:Documents and Settings
CSIDL_PROGRAM_FILES:C:Program Files
CSIDL_PROGRAM_FILES_COMMON:C:Program FilesCommon Files
CSIDL_PROGRAMS:C:Documents and Settingsスタート メニュープログラム
CSIDL_RECENT:C:Documents and SettingsRecent
CSIDL_RESOURCES:C:WINDOWSresources
CSIDL_RESOURCES_LOCALIZED:C:WINDOWSresources411
CSIDL_SENDTO:C:Documents and SettingsSendTo
CSIDL_STARTMENU:C:Documents and Settingsスタート メニュー
CSIDL_STARTUP:C:Documents and Settingsスタート メニュープログラムスタートアップ
CSIDL_SYSTEM:C:WINDOWSsystem32
CSIDL_SYSTEMX86:C:WINDOWSsystem32
CSIDL_TEMPLATES:C:Documents and SettingsTemplates
CSIDL_WINDOWS:C:WINDOWS

以上です。

Windows アプリケーション タスクトレイ表示

Windows アプリケーションのメインウィンドウを表示しないで、タスクトレイ(下記画像)へアイコンとして表示しておいて、マウスでクリックしたら何か表示して、処理をするというのをやってみたいと思います。
(※呼び方がタスクトレイで良いのかわかりませんが、タスクバーの通知領域またはシステムトレイとも言われるようです。)

タスクトレイ アイコン
タスクトレイ アイコン

手順として:
1.Visual Studio で Windows ダイアログ アプリケーションを作ります
2.起動時にアイコンをタスクトレイへ表示します。
3.メインウィンドウを非表示にします。
4.タスクトレイのアイコンがクリックされたらメインウィンドウを表示します。
5.終了時にアイコンをタスクトレイから削除

といった感じで作成します。 なお、1については割愛します。

起動時にアイコンをタスクトレイへ表示

アイコンをタスクトレイへ表示するには、Shell_NotifyIcon API で簡単にできます。パラメータは「追加」、「変更」、「削除」があり、ウィンドウの作成時に追加して、クローズ時に削除すればよいです。

Shell_NotifyIcon を使うために便利な関数を自分のメインウィンドウのクラスに用意してみました。(下記参照)
アイコンなどのリソースはあらかじめ作成しておきます。ここでは、 IDR_TRAYICON というアイコンがある前提です。

MYWM_TRAYNOTIFY というメッセージID を nid.uCallbackMessage に設定してます。これは WM_USER + 1 で定義したユーザー定義メッセージで、タスクトレイのアイコンがクリックされたらメインウィンドウに通知されてきます。

... (省略) ...

#define MYWM_TRAYNOTIFY        WM_USER + 1

... (省略) ...

//
// TrayNotifyIconMessage
//
// dwMessage:
//        NIM_ADD
//        NIM_MODIFY
//        NIM_DELETE
//
BOOL CMyDialog::TrayNotifyIconMessage(DWORD dwMessage)
{
    CString sTip = _T("ポイントされたら表示するテキストを指定します");
    NOTIFYICONDATA nid;

    nid.cbSize = sizeof(NOTIFYICONDATA);
    nid.hWnd   = GetSafeHwnd();
    nid.uID    = IDR_TRAYICON;        // <-- アイコンIDを指定してます
    nid.uFlags = NIF_MESSAGE | NIF_ICON;
    nid.uCallbackMessage = MYWM_TRAYNOTIFY;    // <-- WM_USER + 1 とかでメッセージ定義
    nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
    nid.hIcon  = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_TRAYICON));
    _tcscpy_s(nid.szTip, _countof(nid.szTip), (LPCTSTR)sTip);
    
    return Shell_NotifyIcon(dwMessage, &nid);
}

起動時にアイコンをタスクトレイへ追加します。ダイアログアプリケーションでは OnInitDialog 内で上記の TrayNotifyIconMessage を NIM_ADD をパラメータで指定するだけです。これで、起動時にタスクトレイにアイコンが表示されます。

BOOL CMyDialog::OnInitDialog()
{
    CDialog::OnInitDialog();

... (省略) ...

    // タスクトレイへアイコンを追加
    TrayNotifyIconMessage(NIM_ADD);    

    return TRUE;  // フォーカスをコントロールに設定した場合を除き、TRUE を返します。
}

メインウィンドウを非表示にする

アプリケーション最小化時にタスクトレイに表示させて、タスクバーには表示されないようにするようなので、それを実現するために、メインウィンドウを最小化時に非表示にします。(もちろん、起動時に非表示にしてしまうこともできますが。。。)

WM_SIZE のメッセージハンドラ OnSize 内でパラメータの nType が SIZE_MINIMIZED だった時に ShowWIndow で非表示にします。

void CMyDialog::OnSize(UINT nType, int cx, int cy)
{
    CDialog::OnSize(nType, cx, cy);

    if (nType == SIZE_MINIMIZED)
    {
        ShowWindow(SW_HIDE);
    }
}

このままだと、二度とメインウィンドウが表示されることがないので、タスクトレイのアイコンがダブルクリックされたらメインウィンドウを再表示するようにします。

タスクトレイのアイコンがクリックされたらメインウィンドウを表示

まずは、最初に定義済みの MYWM_TRAYNOTIFY というユーザー定義メッセージをハンドルする OnTrayNotify 関数を用意します。

パラメータの lParam にイベントの種類が入ってきますので、ダブルクリックされたのを判定する WM_LBUTTONDBLCLK と自アプリケーションのアイコンかを判定し、処理を行います。

ここでは、メインウィンドウを表示してフォアグラウンドに持ってきます。

... (省略) ...

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    //}}AFX_MSG_MAP
    ON_WM_SIZE()
    ON_WM_CLOSE()
    ON_MESSAGE(MYWM_TRAYNOTIFY, OnTrayNotify)
END_MESSAGE_MAP()

... (省略) ...

LRESULT CMyDialog::OnTrayNotify(WPARAM wParam, LPARAM lParam)
{
    switch (lParam)
    {
    case WM_LBUTTONDBLCLK: 
        if (wParam == IDR_TRAYICON)
        {
            ShowWindow(SW_NORMAL);
            SetForegroundWindow();
            SetFocus();
        } 
        break;

    default:
        break;
    } 

    return 0;
}

終了時の処理

そして、最後にメインアプリケーションがクローズされるときに、タスクトレイのアイコンを削除します。

void CMyDialog::OnClose()
{
    // タスクトレイからアイコンを削除
    TrayNotifyIconMessage(NIM_DELETE);    

    CDialog::OnClose();
}

以上です。

あとは、必要に応じて、ポップアップメニューを表示したりする処理を OnTrayNotify 内に追加するなど応用できます。

Visual C++/MFCライブラリ CSVカンマ区切りの処理

Visual C++ と MFC ライブラリは長い間使っているけど、他の言語のライブラリにはカンマ区切りの文字列を一発で配列に収めて返してくれる split 関数なるものが普通にあります。あらためて MFC CString にはそれがないことを認識しました。むかし、そのような処理をやったことがあったので、そのコードを抜粋しました。

以下、コード抜粋

    CString str = "111,222,333,444,555,,aaa,bbb,ccc,ddd,eee,fff";
    CArray<CString,CString> arr;

    // カンマ区切りを配列に格納
    arr.RemoveAll();
        
    int iStart = 0;
    int iPos = -1;

    CString restoken;
    iPos = str.Find(',', iStart);

    while(iPos > -1)
    {
        restoken = str.Mid(iStart, iPos - iStart);
        arr.Add(restoken);
        printf("Resulting token: %sn", restoken);

        iStart = iPos + 1;
        iPos = str.Find(',', iStart);

        if (iPos == -1)
        {
            restoken = str.Mid(iStart);
            arr.Add(restoken);
            printf("Resulting token: %sn", restoken);
        }
    }

出力結果

Resulting token: 111
Resulting token: 222
Resulting token: 333
Resulting token: 444
Resulting token: 555
Resulting token:
Resulting token: aaa
Resulting token: bbb
Resulting token: ccc
Resulting token: ddd
Resulting token: eee
Resulting token: fff

CSVファイルによっては、各列データををダブルクォーテーションで囲む場合もあるので、それは対応していません。;-)

また、MSDN CStringT::Tokenize のサンプル トークンの区切り文字をカンマにすると良さそうです。
※ 但し、カンマとカンマに文字が何もない場合(赤字の部分)は、その列は無視して次の列をトークンとして読み込むようです。CSV ファイルの処理で、空き列も処理する必要がある場合は要注意です。

以下、MSDN サンプルを一部変更して

    CAtlString str( "111,222,333,444,555,,aaa,bbb,ccc,ddd,eee,fff" );
    CAtlString resToken;
    int curPos= 0;

    resToken= str.Tokenize(",",curPos);
    while (resToken != "")
    {
        printf("Resulting token: %sn", resToken);
        resToken= str.Tokenize(",",curPos);
    }

出力結果

Resulting token: 111
Resulting token: 222
Resulting token: 333
Resulting token: 444
Resulting token: 555
Resulting token: aaa
Resulting token: bbb
Resulting token: ccc
Resulting token: ddd
Resulting token: eee
Resulting token: fff