C++ で Googleニュース検索

(1/1)
C++でGoogleニュース検索
PHP で Google ニュースの見出しを表示(Windows アプリ版)」で作った検索プログラムを C++に移植する。検索結果を画面上で並べ替えたり、CSV ファイルに保存することができる。

(2020 年 9 月 5 日)データ構造を string/wstring に変更.
(2020 年 8 月 29 日)インストーラー追加.iconv, Libxml2 を Boost C++ライブラリで置き換え.

目次

サンプル・プログラム

圧縮ファイルの内容
googlenewswin.msiインストーラ
bin/googlenewswin.exe実行プログラム本体
bin/libcrypto-1_1-x64.dll
bin/libcurl-x64.dll
bin/libssl-1_1-x64.dll
実行時に必要になるDLL
bin/etc/help.chmヘルプ・ファイル
sour/googlenewswin.cppソース・プログラム
sour/resource.hリソース・ヘッダ
sour/resource.rcリソース・ファイル
sour/application.icoアプリケーション・アイコン

Googleニュース検索

Google ニュース検索の結果は RSS ファイルとして取得できる。詳しくは「PHP で Google ニュースの見出しを表示」をご覧いただきたい。

使用ライブラリ

Google ニュース検索にアクセスするために、オープンソースのライブラリ Boost C++ライブラリcURL (カール)  および OpenSSL が必要になる。導入方法等については、「C++ 開発環境の準備」をご覧いただきたい。

リソースの準備

Eclipse を起動し、新規プロジェクト googlenewswin を用意する。
ResEdit を起動し、resource.rc を用意する。

Eclipse に戻り、ソース・プログラム "googlenewswin.cpp" を追加する。
リンカー・フラグを -mwindows -static -lstdc++ -lgcc -lwinpthread -lcurl -lssl -llzma -lz -lws2_32 "C:\pleiades\eclipse\mingw\x86_64-w64-mingw32\bin\libcurl-x64.dll" に設定する。

解説:ヘッダファイル等

0010: // 初期化処理 ======================================================
0011: #include <iostream>
0012: #include <stdio.h>
0013: #include <stdlib.h>
0014: #include <tchar.h>
0015: #include <time.h>
0016: #include <sstream>
0017: #include <string>
0018: #include <winsock2.h>
0019: #include <windows.h>
0020: #include <commctrl.h>
0021: #include <richedit.h>
0022: #include <curl/curl.h>
0023: #include <boost/property_tree/xml_parser.hpp>
0024: #include <boost/format.hpp>
0025: #include "resource.h"
0026: 
0027: using namespace std;
0028: using namespace boost;
0029: using namespace boost::property_tree;
0030: 
0031: #define APPNAME     "Googleニュース検索"    //アプリケーション名
0032: #define APPVERSION "1.2"                    //バージョン
0033: #define APPYEAR     "2020"                    //作成年
0034: #define REFERENCE "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-08-01.shtm"  // 参考サイト
0035: 
0036: //char*バッファサイズ
0037: #define SIZE_BUFF     512
0038: 
0039: //ListViewItemの最大文字長:変更不可
0040: #define MAX_LISTVIEWITEM 259
0041: 
0042: //標準フォント
0043: #define FONT_FACE     "MS UI Gothic"
0044: 
0045: //現在のインターフェイス
0046: static HINSTANCE hInst;
0047: 
0048: //親ウィンドウ
0049: static HWND hParent;
0050: 
0051: //エラー・メッセージ格納用
0052: string ErrorMessage;
0053: 
0054: //ヘルプ・ファイル
0055: #define HELPFILE ".\\etc\\help.chm"
0056: 
0057: //デフォルト保存ファイル名
0058: #define SAVEFILE "googlenewswin.csv"

cURLOpenSSLiconvBoost C++ライブラリから "format.hpp" を利用する関係で、include するヘッダ・ファイルが多くなっている。

その他の定数は、自由に変更できる。

解説:データ構造

0060: //ニュース格納用クラス
0061: #define SIZE_NEWS 500        //格納ニュース数
0062: class _News {
0063: public:
0064:     wstring title = L"";        //見出し
0065:     string link = "";           //記事リンク
0066:     string pubdate = "";        //配信日時
0067:     time_t ti = 0;               //配信日時(time_t型)
0068:     wstring description = L"";  //記事概要
0069:     wstring source = L"";       //メディア
0070: };
0071: unique_ptr<_NewsNews[SIZE_NEWS] = {};
0072: 
0073: //ソート用構造体
0074: struct _stNews {
0075:     wchar_ttitle;              //見出し
0076:     charpubdate;               //配信日時
0077:     time_t ti;                   //配信日時(time_t型)
0078:     charlink;                  //記事リンク
0079:     wchar_tsource;         //メディア
0080: };
0081: vector<_stNewsVnews;

取得した RSS ファイルを PHP の連想配列のようにして使うために、C++スマートポインタ unique_ptr を導入する。
RSS ファイルの必要な部分をクラス _New に収め、このクラスの実体であるオブジェクトへのポインタに unique_p 配列 tr を利用する。RSS の結果数が未知なので、オブジェクトは必要に応じて動的に確保する(つまり、配列の要素数は不定)。

データ要素としては、日本語テキストを格納する要素にはワイド文字列型(wstring 型)を、それ以外には通常の文字列型(string 型)を割り当てた。配信日時は、string 型time_t 型の両方を格納しておく。

また、g++ には Windows の ListView に対応する SetItemData メソッドが無いようである(見つけられなかっただけかもしれない💦)。
このため、ListView に表示した検索結果を並べ替えるのに、独自のソート関数を用意する必要がある。
実装後半になって気づいたことだが、unique_ptr配列はソートがやりにくく、かといって他のデータ構造に変更するには時間がかかりすぎる。仕方なく、ソート用にvector クラスを別途導入することにした。クラス _New のメンバのうち、ソート可能な項目(文字列)へのポインタを vector クラス へコピーすることになる。

解説:世界標準時をローカル時間に変換

0245: /**
0246:  * 世界標準時をローカル時間に変換
0247:  * @param  char* gmt   世界標準時(文字列)  例)Fri, 14 Aug 2020 08:46:35
0248:  * @param  char* local ローカル時間を格納    例)2020-08-14 17:46
0249:  * @return なし
0250: */
0251: time_t gmt2local(chargmtcharlocal) {
0252:     tm t0t1;
0253:     time_t dt;
0254: 
0255:     std::istringstream ss((char *)gmt);
0256:     ss >> std::get_time(&t0, "%a, %d %b %Y %H:%M:%S");
0257:     dt = mktime(&t0) - timezone;
0258:     localtime_s(&t1, &dt);
0259:     strftime(localSIZE_BUFF, "%Y-%m-%d %H:%M", &t1);
0260: 
0261:     return dt;
0262: }

RSS ファイルには、ニュース記事の配信日時が世界標準時(文字列)で記されている。これをローカル時間(実際には日本標準時)を表す文字列に変換する関数が gmt2local である。

解説:文字コード変換

0189: /**
0190:  * テキスト・コード変換:UTF-8→wstring(Windows API使用)
0191:  * @param  string src UTF-8テキスト
0192:  * @return string 変換後テキスト
0193: */
0194: std::wstring __utf8_wstring(std::string src) {
0195:     auto const dest_size = ::MultiByteToWideChar(CP_UTF80Usrc.data(), -1, nullptr0U);
0196:     std::vector<wchar_tdest(dest_sizeL'\0');
0197:     if (::MultiByteToWideChar(CP_UTF80Usrc.data(), -1, dest.data(), dest.size()) == 0) {
0198:         throw std::system_error{static_cast<int>(::GetLastError()), std::system_category()};
0199:     }
0200:     dest.resize(std::char_traits<wchar_t>::length(dest.data()));
0201:     dest.shrink_to_fit();
0202: 
0203:     return std::wstring(dest.begin(), dest.end());
0204: }

RSS ファイルのエンコードは UTF-8 であるが、これを C++で処理しやすいように wstring 型 に変換するユーザー関数が __utf8_wstring である。よく使うので、マクロ _UW に割り当てた。
また、ソースプログラムや Windows API で入力されるテキストはシフト JIS であるため、これらを相互変換できるよう、下表の関数を用意した。
文字コード変換関数群
関数名マクロ名変換元変換先
__sjis_wstring_SWシフトJISwstring
__wstring_sjis_WSwstringシフトJIS
__utf8_wstring_UWUTF-8wstring
__wstring_utf8_WUwstringUTF-8
sjis_utf8_SUシフトJISUTF-8
utf8_sjis_USUTF-8シフトJIS

解説:Googleニュース検索URLを生成する

0404: /**
0405:  * Googleニュース検索URLを生成する(RSS2.0出力)
0406:  * @param char* query 検索キー
0407:  * @param char* url   URLを格納
0408:  * @param size_t sz   urlのサイズ
0409:  * @return なし
0410: */
0411: void getURL_GoogleNewsSearch(charquerycharurlsize_t sz) {
0412:     const string google = "https://news.google.com/rss/search?hl=ja&gl=JP&ceid=JP:ja&q=";
0413:     static char buff1[SIZE_BUFF + 1], buff2[SIZE_BUFF + 1];
0414: 
0415:     strncpy(buff1sjis_utf8((string)query).c_str(), SIZE_BUFF);
0416: 
0417:     CURL *curl = curl_easy_init();
0418:     strncpy(buff2curl_easy_escape(curl, (const char *)buff1strlen(buff1)), SIZE_BUFF);
0419: 
0420:     string ss = google + (string)buff2;
0421:     strncpy(urlss.c_str(), sz);
0422: 
0423:     curl_easy_cleanup(curl);
0424: }

Google ニュース検索URL を生成する関数が getURL_GoogleNewsSearch である。
前述の文字コード変換関数を使って検索キーワードを UTF-8 に変換した後、cURL ライブラリにある curl_easy_escape 関数を使って URL エスケープする。なお、cURL ライブラリを使い始めるには curl_easy_init 関数を、終了するには curl_easy_cleanup 関数を呼び出す必要がある。

解説:検索結果を配列に格納する

0432: /**
0433:  * 検索結果を配列に格納する
0434:  * @param char* query 検索キーワード
0435:  * @param int   $i    カウンタ
0436:  * @return int 検索結果の件数
0437: */
0438: int searchNews(charquery) {
0439:     //初期化
0440:     for (int i = 0; i < SIZE_NEWSi++) {
0441:         News[i].reset();
0442:         News[i] = NULL;
0443:     }
0444: 
0445:     //cURLによる結果取得
0446:     CURL *curl;
0447:     CURLcode res = (CURLcode)0;
0448:     curl = curl_easy_init();
0449:     string chunk;
0450:     char url[SIZE_BUFF + 1];
0451:     getURL_GoogleNewsSearch(query, (char *)urlSIZE_BUFF);
0452: 
0453:     //cURLによるGoogleニュース検索
0454:     if (curl) {
0455:         curl_easy_setopt(curlCURLOPT_URLurl);
0456:         curl_easy_setopt(curlCURLOPT_SSL_VERIFYPEER, 0);
0457:         curl_easy_setopt(curlCURLOPT_WRITEFUNCTIONcallBackFunk);
0458:         curl_easy_setopt(curlCURLOPT_WRITEDATA, (string*)&chunk);
0459:         res = curl_easy_perform(curl);
0460:         curl_easy_cleanup(curl);
0461:     }
0462:     if (res != CURLE_OK) {
0463:         ErrorMessage = "Google検索エラー";      //cURLエラー
0464:         return (-1);
0465:     }
0466: 
0467:     //XML読み込み
0468:     std::stringstream ss;
0469:     ss << chunk;
0470:     ptree pt;
0471:     xml_parser::read_xml(sspt);
0472: 
0473:     //XML解釈
0474:     ptree tree;
0475:     char buff[SIZE_BUFF + 1];
0476:     int cnt = -1;
0477:     for (auto it : pt.get_child("rss.channel")) {
0478:         //item以外は読み飛ばし
0479:         if (it.first != "item")      continue;
0480:         cnt++;
0481:         News[cnt] = make_unique<_News>();
0482: 
0483:         //見出し
0484:         if (optional<string>title = it.second.get_optional<string>("title")) {
0485:             News[cnt]->title = _UW(title.value());
0486:         }
0487:         //記事リンク
0488:         if (optional<string>link = it.second.get_optional<string>("link")) {
0489:             News[cnt]->link = link.value();
0490:         }
0491:         //配信日時
0492:         if (optional<string>pubdate = it.second.get_optional<string>("pubDate")) {
0493:             News[cnt]->ti = gmt2local((char*)pubdate->c_str(), buff);
0494:             News[cnt]->pubdate = (string)buff;
0495:         }
0496:         //記事概要
0497:         if (optional<string>description = it.second.get_optional<string>("description")) {
0498:             News[cnt]->description = _UW(description.value());
0499:         }
0500:         //メディア
0501:         if (optional<string>source = it.second.get_optional<string>("source")) {
0502:             News[cnt]->source = _UW(source.value());
0503:         }
0504:     }
0505:     chunk.clear();
0506: 
0507:     return cnt;
0508: }

検索結果を _News 配列へ格納する関数が searchNews である。
まず、前述のユーザー関数 getURL_GoogleNewsSearch と cURL ライブラリを使い、RSS ファイルを string 変数 chunk に読み込む。

次に、RSS は XML ファイルの一種であるから、「C++で CPU 情報を取得」で紹介した方法を使って XML解釈を進める。

解説:ニュース記事のソート

0581: /**
0582:  * ソート用変数に代入
0583:  * @param const _stNews& left, right
0584:  * @return 比較結果
0585: */
0586: _stNews __push_stNews(unique_ptr<_News> &ptr) {
0587:     _stNews s;
0588:     s.title      = (wchar_t*)ptr->title.c_str();
0589:     s.source = (wchar_t*)ptr->source.c_str();
0590:     s.pubdate    = (char*)ptr->pubdate.c_str();
0591:     s.ti     = (time_t)ptr->ti;
0592:     s.link       = (char*)ptr->link.c_str();
0593:     return s;
0594: }

0596: /**
0597:  * ニュース記事のソート
0598:  * @param int c  ソート条件
0599:  * @return なし
0600: */
0601: void sortNews(int c) {
0602:     struct {
0603:         int id;
0604:         bool (*fn)(const _stNewsleftconst _stNewsright);
0605: table[] = {
0606:     { SORT_TITLE_ASC,    __cmpTitleAsc },
0607:     { SORT_TITLE_DEC,    __cmpTitleDec },
0608:     { SORT_MEDIA_ASC,    __cmpMediaAsc },
0609:     { SORT_MEDIA_DEC,    __cmpMediaDec },
0610:     { SORT_PUBDATE_ASC,  __cmpPubDateAsc },
0611:     { SORT_PUBDATE_DEC,  __cmpPubDateDec }
0612: };
0613:     int i;
0614:     bool (*fn)(const _stNewsleftconst _stNewsright) = NULL;
0615: 
0616:     //ソート用配列の初期化
0617:     Vnews.clear();
0618:     Vnews.shrink_to_fit();
0619: 
0620:     //ニュース記事をソート用配列へ
0621:     for (i = 0; i < SIZE_NEWSi++) {
0622:         if (News[i] != NULL) {
0623:             Vnews.push_back(__push_stNews(News[i]));
0624:         }
0625:     }
0626: 
0627:     //ソート用関数の選択
0628:     for (i = 0; i < (int)(sizeof(table) / sizeof(*table)); i++) {
0629:         if (c == table[i].id) {
0630:             fn = table[i].fn;
0631:             break;
0632:         }
0633:     }
0634: 
0635:     //ソート実行
0636:     if (fn != NULL) {
0637:         stable_sort(Vnews.begin(), Vnews.end(), fn);
0638:     }
0639: }

前述のように、_News 配列をソートするために複数の関数群を用意した。
unique_ptrvector へコピーするための関数が __push_stNews である。
vector クラス配列をソートするための関数が sortNews である。ソートする項目、昇順/降順の違いによって、__cmp で始まる名前の 6 つの関数を用意した。

解説:検索結果をCSVファイルに保存

0345: /**
0346:  * 検索結果をCSVファイルに保存する
0347:  * @param const char *fname 保存ファイル名
0348:  * @return なし
0349: */
0350: void __saveCSV(const char *fname) {
0351:     ofstream outputfile(fname);
0352: 
0353:     //ラベル
0354:     outputfile << "\"" << "タイトル" << "\",";
0355:     outputfile << "\"" << "メディア" << "\",";
0356:     outputfile << "\"" << "配信日時" << "\",";
0357:     outputfile << "\"" << "記事URL" << "\"" << endl;
0358: 
0359:     //データ本体
0360:     for (int i = 0; i < SIZE_NEWSi++) {
0361:         if (News[i] != NULL) {
0362:             outputfile << "\"" << _WS(News[i]->title) << "\",";
0363:             outputfile << "\"" << _WS(News[i]->source) << "\",";
0364:             outputfile << "\"" << News[i]->pubdate.c_str() << "\",";
0365:             outputfile << "\"" << News[i]->link.c_str() << "\"" << endl;
0366:         }
0367:         if (outputfile.bad()) {
0368:             ErrorMessage = "CSVファイル書き込みエラー";
0369:             break;
0370:         }
0371:     }
0372:     outputfile.close();
0373: }

0375: /**
0376:  * 検索結果をCSVファイルに保存する
0377:  * @param なし
0378:  * @return なし
0379: */
0380: void saveCSV(void) {
0381:     static char fname[MAX_PATH + 1];
0382:     strcpy(fnameSAVEFILE);
0383:     OPENFILENAME of;
0384: 
0385:     //OPENFILENAME構造体のサイズをセット
0386:     memset(&of, 0, sizeof(OPENFILENAME));
0387:     of.lStructSize = sizeof(OPENFILENAME);
0388:     //ダイアログボックスを所有するウィンドウへのハンドル
0389:     of.hwndOwner = hParent;
0390:     of.lpstrFilter = TEXT("CSVファイル(*.csv;*.txt)\0*.csv;*.txt\0すべてのファイル(*.txt)\0*.txt\0\0");
0391:     //ファイル名を格納したバッファのアドレス
0392:     of.lpstrFile = (LPTSTR)fname
0393:     //lpstrFileメンバで指定されるバッファのサイズ
0394:     of.nMaxFile = MAX_PATH;
0395:     of.Flags = OFN_OVERWRITEPROMPT;
0396:     //デフォルトの拡張子を格納したバッファのアドレス
0397:     of.lpstrDefExt = TEXT("csv");
0398:     //コモンダイアログの表示
0399:     GetSaveFileName(&of);
0400:     //ファイル保存
0401:     __saveCSV(fname);
0402: }

_News 配列を CSV ファイルに保存するユーザー関数が saveCSV である。
saveCSV 関数はファイル・ダイアログのコントロールを担当し、実際にファイルへの書き込みを行うのはユーザー関数 __saveCSV である。C++のファイルストリーム機能を使って書き込みを行う。

解説:ニュース一覧作成

0649: /**
0650:  * ニュース一覧のフレーム作成
0651:  * @param HWND hDlg  ウィンドウハンドル
0652:  * @return なし
0653: */
0654: void makeListViewFrame(HWND hWnd) {
0655:     LVCOLUMNA lvcol;
0656:     lvcol.mask = LVCF_TEXT | LVCF_SUBITEM | LVCF_WIDTH | LVCF_FMT;
0657:     lvcol.fmt = LVCFMT_LEFT;
0658: 
0659:     lvcol.cx = 500;
0660:     lvcol.pszText = (char *)"タイトル";
0661:     SendMessage(hWndLVM_INSERTCOLUMNACOL_TITLE, (WPARAM)&lvcol);
0662:     lvcol.cx = 150;
0663:     lvcol.pszText = (char *)"メディア";
0664:     SendMessage(hWndLVM_INSERTCOLUMNACOL_SOURCE, (WPARAM)&lvcol);
0665:     lvcol.cx = 150;
0666:     lvcol.pszText = (char *)"配信日時";
0667:     SendMessage(hWndLVM_INSERTCOLUMNACOL_PUBDATE, (WPARAM)&lvcol);
0668:     lvcol.cx = 0;
0669:     lvcol.pszText = (char *)"URL";
0670:     SendMessage(hWndLVM_INSERTCOLUMNACOL_LINK, (WPARAM)&lvcol);
0671: 
0672:     //罫線表示
0673:     DWORD dwStyle = ListView_GetExtendedListViewStyle(hWnd);
0674:     dwStyle = dwStyle | LVS_EX_GRIDLINES | LVS_EX_ONECLICKACTIVATE;
0675:     ListView_SetExtendedListViewStyle(hWnddwStyle);
0676: }

0678: /**
0679:  * ニュース一覧作成
0680:  * @param HWND hDlg  ウィンドウハンドル
0681:  * @return なし
0682: */
0683: void makeListView(HWND hWnd) {
0684:     LVITEMA item;
0685: 
0686:     //初期化
0687:     for (int i = 0; i < SIZE_NEWSi++) {
0688:         item.iItem = i;
0689:         SendMessage(hWndLVM_DELETEITEM, 0,(WPARAM)&item);
0690:     }
0691: 
0692:     //データ行
0693:     for (int i = 0; i < SIZE_NEWSi++) {
0694:         if (News[i] == NULL)  break;
0695:         //タイトル
0696:         item.mask = LVIF_TEXT;
0697:         item.iItem = i;
0698:         item.iSubItem = COL_TITLE;
0699:         item.pszText = (LPSTR)_WS(Vnews[i].title).substr(0, MAX_LISTVIEWITEM).c_str();
0700:         SendMessage(hWndLVM_INSERTITEMA, 0,(WPARAM)&item);
0701:         //メディア
0702:         item.iSubItem = COL_SOURCE;
0703:         item.pszText = (LPSTR)_WS(Vnews[i].source).substr(0, MAX_LISTVIEWITEM).c_str();
0704:         SendMessage(hWndLVM_SETITEMA, 0,(WPARAM)&item);
0705:         //配信日時
0706:         item.iSubItem = COL_PUBDATE;
0707:         item.pszText = (LPSTR)Vnews[i].pubdate;
0708:         SendMessage(hWndLVM_SETITEMA, 0,(WPARAM)&item);
0709:         //リンク
0710:         item.iSubItem = COL_LINK;
0711:         item.pszText = (LPSTR)Vnews[i].link;
0712:         SendMessage(hWndLVM_SETITEMA, 0,(WPARAM)&item);
0713:     }
0714: }

_News 配列の内容は ListView クラスを使って一覧表示する。
ユーザー関数 makeListViewFrame によりフレームを用意し、makeListView により vector クラス 配列の内容を ListView にセットする。
なお、ListViewItem には 259 バイトという上限があるため、これをあらかじめ定数 MAX_LISTVIEWITEM に定義しておき、substr()メソッドを使ってカットしている。

解説:イベントハンドラ:バージョン表示ダイアログ

0281: /**
0282:  * イベントハンドラ:バージョン表示ダイアログ
0283:  * @param HWND hDlg   ウィンドウ・ハンドラ
0284:  * @paramm UINT uMsg
0285:  * @param WPARAM wParam
0286:  * @paramL PARAM lParam
0287:  * @return INT_PTR CALLBACK
0288: */
0289: INT_PTR CALLBACK processHelp(HWND hDlgUINT uMsgWPARAM wParamLPARAM lParam) {
0290:     string help;
0291: 
0292:     switch(uMsg){
0293:         //ダイアログ初期化
0294:         case WM_INITDIALOG:
0295:             CenterWindow(hDlg);
0296:             //メッセージ作成
0297:             help = (boost::format(R"(
0298: %1%  バージョン %2%
0299: 
0300: 本アプリケーションはMIT Licenseです.
0301: 商用を含む無償利用が可能です.自由に改造できます.
0302: 再配布の際は,下記の著作権表記,およびURLと本使用条件を必ず明記してください.
0303: 
0304: Copyright by (c)studio pahoo, %3%
0305: https://www.pahoo.org/
0306: なお,本アプリケーションの利用または改造することによって生じた得失ついては一切関知いたしません.APIの変更・停止によって正常に機能しなくなる場合があります.また,二次利用先の組織・企業・団体の目的・内容・活動については一切関知いたしません.
0307: 
0308: )
")
0309: APPNAME
0310: APPVERSION
0311: APPYEAR
0312: ).str();
0313:             setStrEditBox(hDlgIDC_TEXT_HELPhelp);
0314:             break;
0315: 
0316:         //ボタン押下
0317:         case WM_COMMAND:
0318:              switch (LOWORD(wParam)) {
0319:                 //実行
0320:                 case IDC_BUTTON_OK:
0321:                     EndDialog(hDlg, 0);
0322:                     break;
0323:                 default:
0324:                     return 1;
0325:             }
0326:             break;
0327:         //プログラム終了
0328:         case WM_CLOSE:
0329:             EndDialog(hDlg, 0);
0330:             break;
0331:     }
0332:     return 0;
0333: }

バージョン情報をモーダル・ダイアログで表示させる。このダイアログに関わるイベント・ハンドラが processHelp である。
表示メッセージは C++の 生文字リテラルを使っているが、ここで変数を利用できるように、Boost C++ライブラリから "format.hpp" を利用した。

解説:イベントハンドラ:メインウィンドウ

0716: /**
0717:  * イベントハンドラ:メインウィンドウ
0718:  * @param HWND hDlg           親ウィンドウ・ハンドラ
0719:  * @paramm UINT uMsg           メッセージ識別子
0720:  * @param WPARAM wParam       メッセージの最初のパラメータ
0721:  * @paramL PARAM lParam       メッセージの2番目のパラメータ
0722:  * @return INT_PTR CALLBACK   TRUE:メッセージ処理完了/FALSE:未完了
0723: */
0724: INT_PTR CALLBACK processMain(HWND hDlgUINT uMsgWPARAM wParamLPARAM lParam) {
0725:     HICON hIcon;
0726:     char *str;
0727:     string query;
0728:     LVITEM item;
0729:     LV_HITTESTINFO lvinfo;
0730:     NM_LISTVIEW *pNMLV;
0731:     TCHAR buff[SIZE_BUFF + 1];
0732:     static int subsort[100];
0733: 
0734:     switch(uMsg){
0735:         //ダイアログ初期化
0736:         case WM_INITDIALOG:
0737:             hParent = hDlg;
0738:             hIcon = (HICON)LoadImage(hInstMAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
0739:             SendMessage(hParentWM_SETICONICON_SMALL, (LPARAM)hIcon);
0740:             makeListViewFrame(GetDlgItem(hDlgIDC_LISTVIEW_NEWS));
0741:             break;
0742: 
0743:         //ボタン押下
0744:         case WM_COMMAND:
0745:              switch (LOWORD(wParam)) {
0746:                 //実行
0747:                 case IDM_EXEC:
0748:                 case IDC_BUTTON_SEARCH:
0749:                     ErrorMessage = "";
0750:                     //カーソルを砂時計に
0751:                     SetCursor(LoadCursor(NULLIDC_WAIT));
0752:                     query = getStrEditBox(hDlgIDC_EDIT_QUERY);
0753:                     //Googleニュース検索
0754:                     searchNews((char *)query.c_str());
0755:                     sortNews(SORT_NONE);
0756:                     //一覧表示
0757:                     makeListView(GetDlgItem(hDlgIDC_LISTVIEW_NEWS));
0758:                     setStrEditBox(hDlgIDC_TEXT_ERRORErrorMessage);
0759:                     break;
0760:                 //保存
0761:                 case IDC_BUTTON_SAVE:
0762:                 case IDM_SAVE:
0763:                     saveCSV();
0764:                     break;
0765:                 //ヘルプ
0766:                 case IDM_HELP:
0767:                     ShellExecute(hParent_T("open"), _T(HELPFILE), NULLNULLSW_RESTORE);
0768:                     break;
0769:                 //バージョン表示
0770:                 case IDM_VERSION:
0771:                     createHelp(hParentprocessHelp);
0772:                     break;
0773:                 //解説サイト
0774:                 case IDM_PAHOO:
0775:                     ShellExecute(NULL_T("open"), _T(REFERENCE), NULLNULLSW_RESTORE);
0776:                     break;
0777:                 //コピー
0778:                 case IDM_COPY:
0779:                     setClipboardData(getStrEditBox(hParentIDC_EDIT_QUERY));
0780:                     break;
0781:                 //貼り付け
0782:                 case IDM_PASTE:
0783:                     str = getClipboardData();
0784:                     if (str != NULL) {
0785:                         setStrEditBox(hParentIDC_EDIT_QUERYstr);
0786:                     }
0787:                     break;
0788:                 //切り取り
0789:                 case IDM_DELETE:
0790:                     setStrEditBox(hParentIDC_EDIT_QUERY, "");
0791:                     break;
0792:                 //プログラム終了
0793:                 case IDM_QUIT:
0794:                     EndDialog(hParent, 0);
0795:                     return 0;
0796:                 default:
0797:                     return 1;
0798:             }
0799:             break;
0800: 
0801:         //通知
0802:         case WM_NOTIFY:
0803:             switch(((LPNMHDR)lParam)->idFrom) {
0804:                 case IDC_LISTVIEW_NEWS:
0805:                     switch (((LPNMLISTVIEW)lParam)->hdr.code) {
0806:                         //一覧のラベルがクリック
0807:                         case LVN_COLUMNCLICK:
0808:                             pNMLV = (NM_LISTVIEW *)lParam;
0809:                             if (subsort[pNMLV->iSubItem] == SORT_ASC) {
0810:                                 subsort[pNMLV->iSubItem] = SORT_DEC;
0811:                             } else {
0812:                                 subsort[pNMLV->iSubItem] = SORT_ASC;
0813:                             }
0814:                             //ニュース記事の並べ替え
0815:                             sortNews(pNMLV->iSubItem * 2 + subsort[pNMLV->iSubItem]);
0816:                             //一覧表示
0817:                             makeListView(GetDlgItem(hDlgIDC_LISTVIEW_NEWS));
0818:                             break;
0819:                         //ニュース一覧の1行を選択
0820:                         case LVN_ITEMCHANGED:
0821:                         //ニュース一覧の1行をダブルクリック
0822:                         //case NM_DBLCLK:
0823:                             GetCursorPos((LPPOINT)&lvinfo.pt);
0824:                             ScreenToClient(((LPNMLISTVIEW)lParam)->hdr.hwndFrom, &lvinfo.pt);
0825:                             ListView_HitTest(((LPNMLISTVIEW)lParam)->hdr.hwndFrom, &lvinfo);
0826:                             if ((lvinfo.flags & LVHT_ONITEM) != 0) {
0827:                                 item.mask = TVIF_HANDLE | TVIF_TEXT;
0828:                                 item.iItem = lvinfo.iItem;
0829:                                 item.iSubItem = COL_LINK;
0830:                                 item.pszText = buff;
0831:                                 item.cchTextMax = SIZE_BUFF;
0832:                                 ListView_GetItem(((LPNMLISTVIEW)lParam)->hdr.hwndFrom, &item);
0833:                                 //ブラウザ起動
0834:                                 ShellExecute(hParent_T("open"), _T(buff), NULLNULLSW_RESTORE);
0835:                             }
0836:                             break;
0837:                         default:
0838:                             break;
0839:                     }
0840:                     break;
0841:                 default:
0842:                     break;
0843:             }
0844:             break;
0845: 
0846:         //プログラム終了
0847:         case WM_CLOSE:
0848:             EndDialog(hParent, 0);
0849:             return 0;
0850:     }
0851:     return 0;
0852: }

メインウィンドウに関わるイベント・ハンドラが processMain である。いくつかのボタンとメニューは共通の機能を担っている。
ダイアログ初期化(WM_INITDIALOG)の際に makeListViewFrame を使ってニュース一覧のフレームを用意する。

検索実行イベント発生時(IDC_BUTTON_SEARCH または IDM_EXEC)、Google ニュース検索にアクセスして応答が返ってくるまで少し時間がかかるので、まずカーソルを砂時計にする。続けて searchNews を呼び出し、Google ニュース検索を行い、結果を _News 配列に代入する。
sortNews(SORT_NONE)_News 配列の内容を vector クラス配列へコピーするだけで、ソートは行わない。

ListViewクラスのラベルをクリックしたときは WM_NOTIFY イベントが発生、コードとして LVN_COLUMNCLICK が渡る。ここから、クリックされたラベル(列)の番号 pNMLV->iSubItem を取り出す。
現在の列の状態が昇順(SORT_ASC)か降順(SORT_DEC)かは、列番号を要素に持つ配列 subsort に入れておき、その値によってソート関数 sortNews を呼び出し、一覧表示 makeListView を実行する。

記事業をシングルクリックすると、LVN_ITEMCHANGED が渡される。その行にあるリンク URL(列番号 COL_LINK)を文字列 buff にコピーしてやり、ShellExecute 関数を使ってブラウザに渡す。

解説:Windowsメインプログラム

0855: /**
0856:  * Windowsメインプログラム
0857:  * @param HINSTANCE hInstance           インスタンスハンドル
0858:  * @paramm HINSTANCE hPrevInstance       未使用(常にNULL):Win16時代の名残
0859:  * @param LPSTR lpCmdLine               コマンドライン引数
0860:  * @paramL int nShowCmd               ウィンドウの表示方法
0861:  * @return int リターンコード
0862: */
0863: int WINAPI WinMain(HINSTANCE hInstanceHINSTANCE hPrevInstanceLPSTR lpCmdLineint nShowCmd) {
0864:     LoadLibrary("RICHED20.DLL");
0865:     hInst = hInstance;
0866:     DialogBox(hInstanceMAKEINTRESOURCE(IDD_MAIN), NULL, (DLGPROC)processMain);
0867:     return 0;
0868: }

検索キーワードを入力するエディットボックスは、コントロールキーが使えるように RichEdit にしてある。プログラムの冒頭で "RICHED20.DLL" を読み込んでやる必要がある。

解説:ヘルプファイルの作成

.chm 形式のヘルプファイルは、doc2htmlhelp(s7taka 氏)を利用した。Microsoft Word と Microsoft HTML Help Workshop が必要。

参考サイト

(この項おわり)
header