目次
サンプル・プログラム
googlenewswin.msi | インストーラ |
bin/googlenewswin.exe | 実行プログラム本体 |
bin/libcrypto-1_1-x64.dll | 実行時に必要になるDLL |
bin/etc/help.chm | ヘルプ・ファイル |
sour/googlenewswin.cpp | ソース・プログラム |
sour/resource.h | リソース・ヘッダ |
sour/resource.rc | リソース・ファイル |
sour/application.ico | アプリケーション・アイコン |
sour/makefile | ビルド |
バージョン | 更新日 | 内容 |
---|---|---|
1.5.6 | 2024/11/16 | 使用ライブラリ更新 |
1.5.5 | 2024/07/14 | 使用ライブラリ更新 |
1.5.4 | 2024/03/06 | 使用ライブラリ更新 |
1.5.3 | 2023/10/14 | 使用ライブラリ更新 |
1.5.2 | 2023/06/10 | 使用ライブラリ更新 |
Googleニュース検索
使用ライブラリ
リソースの準備
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" に設定する。
MSYS2 コマンドラインからビルドするのであれば、"makefile" を利用してほしい。
解説:初期値
googlenewswin.cpp
32: #define MAKER "pahoo.org" //作成者
33: #define APPNAME "googlenewswin" //アプリケーション名
34: #define APPNAMEJP "Googleニュース検索" //アプリケーション名(日本語)
35: #define APPVERSION "1.5.6" //バージョン
36: #define APPYEAR "2020-2024" //作成年
37: #define REFERENCE "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-08-01.shtm" // 参考サイト
38:
39: //char*バッファサイズ
40: #define SIZE_BUFF 5120
41:
42: //ListViewItemの最大文字長:変更不可
43: #define MAX_LISTVIEWITEM 259
44:
45: //標準フォント
46: #define FONT_FACE "MS UI Gothic"
47:
48: //Googleニュース検索URL
49: #define URL_GOOGLENEWS "https://news.google.com/rss/search?hl=ja&gl=JP&ceid=JP:ja&q="
50:
51: //現在のインターフェイス
52: static HINSTANCE hInst;
53:
54: //アプリケーション・ウィンドウ
55: HWND hParent;
56:
57: //アプリケーション・ウィンドウ位置
58: unsigned hParent_X, hParent_Y;
59:
60: //エラー・メッセージ格納用
61: string ErrorMessage;
62:
63: //検索キー格納用
64: string Query;
65:
66: //ヘルプ・ファイル
67: #define HELPFILE ".\\etc\\help.chm"
68:
69: //デフォルト保存ファイル名
70: #define SAVEFILE "googlenewswin.csv"
解説:データ構造
googlenewswin.cpp
75: //ニュース格納用クラス
76: #define SIZE_NEWS 500 //格納ニュース数
77: class _News {
78: public:
79: wstring title = L""; //見出し
80: string link = ""; //記事リンク
81: string pubdate = ""; //配信日時
82: time_t ti = 0; //配信日時(time_t型)
83: wstring description = L""; //記事概要
84: wstring source = L""; //メディア
85: };
86: unique_ptr<_News> News[SIZE_NEWS] = {};
87:
88: //ソート用構造体
89: struct _stNews {
90: wchar_t* title; //見出し
91: char* pubdate; //配信日時
92: time_t ti; //配信日時(time_t型)
93: char* link; //記事リンク
94: wchar_t* source; //メディア
95: };
96: vector<_stNews> Vnews;
RSSファイルの必要な部分をクラス _New に収め、このクラスの実体であるオブジェクトへのポインタに unique_p 配列tr を利用する。RSSの結果数が未知なので、オブジェクトは必要に応じて動的に確保する(つまり、配列の要素数は不定)。
データ要素としては、日本語テキストを格納する要素にはワイド文字列型(wstring型)を、それ以外には通常の文字列型(string型)を割り当てた。配信日時は、string型と time_t型の両方を格納しておく。
また、g++ にはWindowsの ListView に対応する SetItemData メソッドが無いようである(見つけられなかっただけかもしれない💦)。
このため、ListView に表示した検索結果を並べ替えるのに、独自のソート関数を用意する必要がある。
実装後半になって気づいたことだが、unique_ptr配列はソートがやりにくく、かといって他のデータ構造に変更するには時間がかかりすぎる。仕方なく、ソート用にvectorクラスを別途導入することにした。クラス _New のメンバのうち、ソート可能な項目(文字列)へのポインタを vectorクラス へコピーすることになる。
解説:世界標準時をローカル時間に変換
googlenewswin.cpp
333: /**
334: * 世界標準時をローカル時間に変換
335: * @param char* gmt 世界標準時(文字列) 例)Fri, 14 Aug 2020 08:46:35
336: * @param char* local ローカル時間を格納 例)2020-08-14 17:46
337: * @return なし
338: */
339: time_t gmt2local(char* gmt, char* local) {
340: tm t0, t1;
341: time_t dt;
342:
343: std::istringstream ss((char *)gmt);
344: ss >> std::get_time(&t0, "%a, %d %b %Y %H:%M:%S");
345: dt = mktime(&t0) - timezone;
346: localtime_s(&t1, &dt);
347: strftime(local, SIZE_BUFF, "%Y-%m-%d %H:%M", &t1);
348:
349: return dt;
350: }
解説:文字コード変換
googlenewswin.cpp
277: /**
278: * テキスト・コード変換:UTF-8→wstring(Windows API使用)
279: * @param string src UTF-8テキスト
280: * @return string 変換後テキスト
281: */
282: wstring __utf8_wstring(std::string src) {
283: auto const dest_size = ::MultiByteToWideChar(CP_UTF8, 0U, src.data(), -1, nullptr, 0U);
284: std::vector<wchar_t> dest(dest_size, L'\0');
285: if (::MultiByteToWideChar(CP_UTF8, 0U, src.data(), -1, dest.data(), dest.size()) == 0) {
286: throw std::system_error{static_cast<int>(::GetLastError()), std::system_category()};
287: }
288: dest.resize(std::char_traits<wchar_t>::length(dest.data()));
289: dest.shrink_to_fit();
290:
291: return std::wstring(dest.begin(), dest.end());
292: }
また、ソースプログラムやWindows APIで入力されるテキストはシフトJISであるため、これらを相互変換できるよう、下表の関数を用意した。
関数名 | マクロ名 | 変換元 | 変換先 |
---|---|---|---|
__sjis_wstring | _SW | シフトJIS | wstring |
__wstring_sjis | _WS | wstring | シフトJIS |
__utf8_wstring | _UW | UTF-8 | wstring |
__wstring_utf8 | _WU | wstring | UTF-8 |
sjis_utf8 | _SU | シフトJIS | UTF-8 |
utf8_sjis | _US | UTF-8 | シフトJIS |
解説:Googleニュース検索URLを生成する
googlenewswin.cpp
622: /**
623: * Googleニュース検索URLを生成する(RSS2.0出力)
624: * @param char* query 検索キー
625: * @param char* url URLを格納
626: * @param size_t sz urlのサイズ
627: * @return なし
628: */
629: void getURL_GoogleNewsSearch(char* query, char* url, size_t sz) {
630: const string google = URL_GOOGLENEWS;
631:
632: static char buff1[SIZE_BUFF + 1], buff2[SIZE_BUFF + 1];
633:
634: strncpy(buff1, sjis_utf8((string)query).c_str(), SIZE_BUFF);
635:
636: CURL *curl = curl_easy_init();
637: strncpy(buff2, curl_easy_escape(curl, (const char *)buff1, strlen(buff1)), SIZE_BUFF);
638:
639: string ss = google + (string)buff2;
640: strncpy(url, ss.c_str(), sz);
641:
642: curl_easy_cleanup(curl);
643: }
前述の文字コード変換関数を使って検索キーワードをUTF-8に変換した後、cURLライブラリにある curl_easy_escape 関数を使ってURLエスケープする。なお、cURLライブラリを使い始めるには curl_easy_init 関数を、終了するには curl_easy_cleanup 関数を呼び出す必要がある。
解説:検索結果を配列に格納する
googlenewswin.cpp
651: /**
652: * 検索結果を配列に格納する
653: * @param char* query 検索キーワード
654: * @param int $i カウンタ
655: * @return int 検索結果の件数
656: */
657: int searchNews(char* query) {
658: //初期化
659: for (int i = 0; i < SIZE_NEWS; i++) {
660: News[i].reset();
661: News[i] = NULL;
662: }
663:
664: //cURLによる結果取得
665: CURL *curl;
666: CURLcode res = (CURLcode)0;
667: curl = curl_easy_init();
668: string chunk;
669: char url[SIZE_BUFF + 1];
670: getURL_GoogleNewsSearch(query, (char *)url, SIZE_BUFF);
671:
672: //cURLによるGoogleニュース検索
673: if (curl) {
674: curl_easy_setopt(curl, CURLOPT_URL, url);
675: curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
676: curl_easy_setopt(curl, CURLOPT_USERAGENT, UserAgent.c_str());
677: curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callBackFunk);
678: curl_easy_setopt(curl, CURLOPT_WRITEDATA, (string*)&chunk);
679: res = curl_easy_perform(curl);
680: curl_easy_cleanup(curl);
681: }
682: if (res != CURLE_OK) {
683: ErrorMessage = "Google検索エラー"; //cURLエラー
684: return (-1);
685: }
686:
687: //XML読み込み
688: std::stringstream ss;
689: ss << chunk;
690: ptree pt;
691: xml_parser::read_xml(ss, pt);
692:
693: //XML解釈
694: ptree tree;
695: char buff[SIZE_BUFF + 1];
696: wstring title2;
697: int cnt = -1;
698: for (auto it : pt.get_child("rss.channel")) {
699: //item以外は読み飛ばし
700: if (it.first != "item") continue;
701: cnt++;
702: News[cnt] = make_unique<_News>();
703:
704: //見出し
705: if (optional<string>title = it.second.get_optional<string>("title")) {
706: News[cnt]->title = _UW(title.value());
707: }
708: //記事リンク
709: if (optional<string>link = it.second.get_optional<string>("link")) {
710: News[cnt]->link = link.value();
711: }
712: //配信日時
713: if (optional<string>pubdate = it.second.get_optional<string>("pubDate")) {
714: News[cnt]->ti = gmt2local((char*)pubdate->c_str(), buff);
715: News[cnt]->pubdate = (string)buff;
716: }
717: //記事概要
718: if (optional<string>description = it.second.get_optional<string>("description")) {
719: News[cnt]->description = _UW(description.value());
720: }
721: //メディア
722: if (optional<string>source = it.second.get_optional<string>("source")) {
723: News[cnt]->source = _UW(source.value());
724: }
725: }
726: chunk.clear();
727:
728: return cnt;
729: }
まず、前述のユーザー関数 getURL_GoogleNewsSearch とcURLライブラリを使い、RSSファイルをstring変数 chunk に読み込む。
次に、RSSはXMLファイルの一種であるから、「C++でCPU情報を取得」で紹介した方法を使ってXML解釈を進める。
解説:ニュース記事のソート
googlenewswin.cpp
802: /**
803: * ソート用変数に代入
804: * @param const _stNews& left, right
805: * @return 比較結果
806: */
807: _stNews __push_stNews(unique_ptr<_News> &ptr) {
808: _stNews s;
809: s.title = (wchar_t*)ptr->title.c_str();
810: s.source = (wchar_t*)ptr->source.c_str();
811: s.pubdate = (char*)ptr->pubdate.c_str();
812: s.ti = (time_t)ptr->ti;
813: s.link = (char*)ptr->link.c_str();
814: return s;
815: }
googlenewswin.cpp
817: /**
818: * ニュース記事のソート
819: * @param int c ソート条件
820: * @return なし
821: */
822: void sortNews(int c) {
823: struct {
824: int id;
825: bool (*fn)(const _stNews& left, const _stNews& right);
826: } table[] = {
827: { SORT_TITLE_ASC, __cmpTitleAsc },
828: { SORT_TITLE_DEC, __cmpTitleDec },
829: { SORT_MEDIA_ASC, __cmpMediaAsc },
830: { SORT_MEDIA_DEC, __cmpMediaDec },
831: { SORT_PUBDATE_ASC, __cmpPubDateAsc },
832: { SORT_PUBDATE_DEC, __cmpPubDateDec }
833: };
834: int i;
835: bool (*fn)(const _stNews& left, const _stNews& right) = NULL;
836:
837: //ソート用配列の初期化
838: Vnews.clear();
839: Vnews.shrink_to_fit();
840:
841: //ニュース記事をソート用配列へ
842: for (i = 0; i < SIZE_NEWS; i++) {
843: if (News[i] != NULL) {
844: Vnews.push_back(__push_stNews(News[i]));
845: }
846: }
847:
848: //ソート用関数の選択
849: for (i = 0; i < (int)(sizeof(table) / sizeof(*table)); i++) {
850: if (c == table[i].id) {
851: fn = table[i].fn;
852: break;
853: }
854: }
855:
856: //ソート実行
857: if (fn != NULL) {
858: stable_sort(Vnews.begin(), Vnews.end(), fn);
859: }
860: }
unique_ptr を vector へコピーするための関数が __push_stNews である。
vectorクラス配列をソートするための関数が sortNews である。ソートする項目、昇順/降順の違いによって、__cmpで始まる名前の6つの関数を用意した。
解説:検索結果をCSVファイルに保存
googlenewswin.cpp
562: /**
563: * 検索結果をCSVファイルに保存する
564: * @param const char *fname 保存ファイル名
565: * @return なし
566: */
567: void __saveCSV(const char *fname) {
568: ofstream outputfile(fname);
569:
570: //ラベル
571: outputfile << "\"" << "タイトル" << "\",";
572: outputfile << "\"" << "メディア" << "\",";
573: outputfile << "\"" << "配信日時" << "\",";
574: outputfile << "\"" << "記事URL" << "\"" << endl;
575:
576: //データ本体
577: for (int i = 0; i < SIZE_NEWS; i++) {
578: if (News[i] != NULL) {
579: outputfile << "\"" << _WS(News[i]->title) << "\",";
580: outputfile << "\"" << _WS(News[i]->source) << "\",";
581: outputfile << "\"" << News[i]->pubdate.c_str() << "\",";
582: outputfile << "\"" << News[i]->link.c_str() << "\"" << endl;
583: }
584: if (outputfile.bad()) {
585: ErrorMessage = "CSVファイル書き込みエラー";
586: break;
587: }
588: }
589: outputfile.close();
590: }
googlenewswin.cpp
592: /**
593: * 検索結果をCSVファイルに保存する
594: * @param なし
595: * @return なし
596: */
597: void saveCSV(void) {
598: static char fname[MAX_PATH + 1];
599: strcpy(fname, SAVEFILE);
600: OPENFILENAME of;
601:
602: //OPENFILENAME構造体のサイズをセット
603: memset(&of, 0, sizeof(OPENFILENAME));
604: of.lStructSize = sizeof(OPENFILENAME);
605: //ダイアログボックスを所有するウィンドウへのハンドル
606: of.hwndOwner = hParent;
607: of.lpstrFilter = TEXT("CSVファイル(*.csv;*.txt)\0*.csv;*.txt\0すべてのファイル(*.txt)\0*.txt\0\0");
608: //ファイル名を格納したバッファのアドレス
609: of.lpstrFile = (LPTSTR)fname;
610: //lpstrFileメンバで指定されるバッファのサイズ
611: of.nMaxFile = MAX_PATH;
612: of.Flags = OFN_OVERWRITEPROMPT;
613: //デフォルトの拡張子を格納したバッファのアドレス
614: of.lpstrDefExt = TEXT("csv");
615: //コモンダイアログの表示
616: GetSaveFileName(&of);
617: //ファイル保存
618: __saveCSV(fname);
619: }
解説:ニュース一覧作成
googlenewswin.cpp
870: /**
871: * ニュース一覧のフレーム作成
872: * @param HWND hDlg ウィンドウハンドル
873: * @return なし
874: */
875: void makeListViewFrame(HWND hWnd) {
876: LVCOLUMNA lvcol;
877: lvcol.mask = LVCF_TEXT | LVCF_SUBITEM | LVCF_WIDTH | LVCF_FMT;
878: lvcol.fmt = LVCFMT_LEFT;
879:
880: lvcol.cx = 500;
881: lvcol.pszText = (char *)"タイトル";
882: SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_TITLE, (WPARAM)&lvcol);
883: lvcol.cx = 150;
884: lvcol.pszText = (char *)"メディア";
885: SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_SOURCE, (WPARAM)&lvcol);
886: lvcol.cx = 150;
887: lvcol.pszText = (char *)"配信日時";
888: SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_PUBDATE, (WPARAM)&lvcol);
889: lvcol.cx = 0;
890: lvcol.pszText = (char *)"URL";
891: SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_LINK, (WPARAM)&lvcol);
892:
893: //罫線表示
894: DWORD dwStyle = ListView_GetExtendedListViewStyle(hWnd);
895: dwStyle = dwStyle | LVS_EX_GRIDLINES | LVS_EX_ONECLICKACTIVATE;
896: ListView_SetExtendedListViewStyle(hWnd, dwStyle);
897: }
googlenewswin.cpp
899: /**
900: * ニュース一覧作成
901: * @param HWND hDlg ウィンドウハンドル
902: * @return なし
903: */
904: void makeListView(HWND hWnd) {
905: LVITEMA item;
906:
907: //初期化
908: for (int i = 0; i < SIZE_NEWS; i++) {
909: item.iItem = i;
910: SendMessage(hWnd, LVM_DELETEITEM, 0,(WPARAM)&item);
911: }
912:
913: //データ行
914: for (int i = 0; i < SIZE_NEWS; i++) {
915: if (News[i] == NULL) break;
916: //タイトル
917: item.mask = LVIF_TEXT;
918: item.iItem = i;
919: item.iSubItem = COL_TITLE;
920: item.pszText = (LPSTR)_WS(Vnews[i].title).substr(0, MAX_LISTVIEWITEM).c_str();
921: SendMessage(hWnd, LVM_INSERTITEMA, 0,(WPARAM)&item);
922: //メディア
923: item.iSubItem = COL_SOURCE;
924: item.pszText = (LPSTR)_WS(Vnews[i].source).substr(0, MAX_LISTVIEWITEM).c_str();
925: SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
926: //配信日時
927: item.iSubItem = COL_PUBDATE;
928: item.pszText = (LPSTR)Vnews[i].pubdate;
929: SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
930: //リンク
931: item.iSubItem = COL_LINK;
932: item.pszText = (LPSTR)Vnews[i].link;
933: SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
934: }
935: }
ユーザー関数 makeListViewFrame によりフレームを用意し、makeListView により vectorクラス 配列の内容をListViewにセットする。
なお、ListViewItemには259バイトという上限があるため、これをあらかじめ定数 MAX_LISTVIEWITEM に定義しておき、substr()メソッドを使ってカットしている。
解説:イベントハンドラ:バージョン表示ダイアログ
googlenewswin.cpp
515: /**
516: * イベントハンドラ:バージョン表示ダイアログ
517: * @param HWND hDlg ウィンドウ・ハンドラ
518: * @paramm UINT uMsg メッセージ識別子
519: * @param WPARAM wParam メッセージの最初のパラメータ
520: * @paramL PARAM lParam メッセージの2番目のパラメータ
521: * @return INT_PTR CALLBACK TRUE:メッセージ処理完了/FALSE:未完了
522: */
523: INT_PTR CALLBACK processHelp(HWND hDlg, UINT uMsg,
524: WPARAM wParam, LPARAM lParam) {
525: switch (uMsg) {
526: //ダイアログ初期化
527: case WM_INITDIALOG:
528: CenterWindow(hDlg);
529: setStrEditBox(hDlg, IDC_TEXT_HELP, Version);
530: break;
531:
532: //ボタン押下
533: case WM_COMMAND:
534: switch (LOWORD(wParam)) {
535: //実行
536: case IDC_BUTTON_OK:
537: EndDialog(hDlg, 0);
538: break;
539: default:
540: return 1;
541: }
542: break;
543: //プログラム終了
544: case WM_CLOSE:
545: EndDialog(hDlg, 0);
546: break;
547: }
548: return 0;
549: }
表示メッセージはC++の 生文字リテラルを使っているが、ここで変数を利用できるように、Boost C++ライブラリから "format.hpp" を利用した。
解説:ウィンドウ位置・検索キーの読込・保存
normalizetextwin.cpp
325: /**
326: * AppDataのパスを取得
327: * @param char* appname アプリケーション名
328: * @return string パス
329: */
330: string getMyPath(const char* appname) {
331: static TCHAR myPath[MAX_PATH] = "";
332:
333: if (strlen(myPath) == 0) {
334: if (SHGetSpecialFolderPath(NULL, myPath, CSIDL_APPDATA, 0)) {
335: TCHAR *ptmp = _tcsrchr(myPath, _T('\\'));
336: if (ptmp != NULL) {
337: ptmp = _tcsinc(ptmp);
338: *ptmp = _T('\0');
339: }
340: strcat(myPath, _T("Roaming"));
341: CreateDirectory((LPCTSTR)myPath, NULL);
342: strcat(myPath, _T("\\pahoo.org"));
343: CreateDirectory((LPCTSTR)myPath, NULL);
344: strcat(myPath, _T("\\"));
345: strcat(myPath, _T(appname));
346: CreateDirectory((LPCTSTR)myPath, NULL);
347: strcat(myPath, _T("\\"));
348: } else {
349: }
350: }
351: return (string)myPath;
352: }
normalizetextwin.cpp
423: /**
424: * オプションの保存
425: * @param string option オプション
426: * @return なし
427: */
428: void saveOption(string option) {
429: #ifndef CMDAPP
430: //アプリケーション・ウィンドウの位置取得
431: WINDOWINFO windowInfo;
432: windowInfo.cbSize = sizeof(WINDOWINFO);
433: GetWindowInfo(hParent, &windowInfo);
434: hParent_X = (unsigned)windowInfo.rcWindow.left;
435: hParent_Y = (unsigned)windowInfo.rcWindow.top;
436: if (hParent_X >= (unsigned)windowInfo.rcWindow.right) {
437: hParent_X = 0;
438: }
439: if (hParent_Y >= (unsigned)windowInfo.rcWindow.bottom) {
440: hParent_Y = 0;
441: }
442: #endif
443:
444: //XMLファイルへ書き込む
445: ptree pt;
446: ptree& child1 = pt.add("parameter.param", option);
447: child1.add("<xmlattr>.type", "option");
448: ptree& child2 = pt.add("parameter.param", (string)to_string(hParent_X));
449: child2.add("<xmlattr>.type", "wx");
450: ptree& child3 = pt.add("parameter.param", (string)to_string(hParent_Y));
451: child3.add("<xmlattr>.type", "wy");
452:
453: // clog << "option=" << option << endl;
454:
455: const int indent = 4;
456: write_xml(getMyPath(APPNAME) + APPNAME + ".xml", pt, std::locale(),
457: xml_writer_make_settings<std::string>(' ', indent));
458: }
normalizetextwin.cpp
366: /**
367: * オプションの読み込み
368: * @param なし
369: * @return string オプション
370: */
371: string loadOption(void) {
372: ptree pt;
373:
374: //初期値設定
375: string option = initOption();
376:
377: //XMLファイル読み込み
378: try {
379: xml_parser::read_xml(getMyPath(APPNAME) + APPNAME + ".xml", pt);
380:
381: //XML解釈
382: try {
383: //形式チェック
384: if (optional<string>str = pt.get_optional<string>("parameter")) {
385: } else {
386: return OPTION_INIT;
387: }
388: //パラメータ読み込み
389: for (auto it : pt.get_child("parameter")) {
390: string type= it.second.get_optional<string>("<xmlattr>.type").value();
391: if (type == "option") {
392: option =(string)it.second.data();
393: } else if (type == "wx") {
394: hParent_X = (unsigned)stoi(it.second.data());
395: } else if (type == "wy") {
396: hParent_Y = (unsigned)stoi(it.second.data());
397: }
398: }
399: //解釈失敗したら初期値設定
400: } catch (xml_parser_error& e) {
401: return initOption();
402: }
403: //読み込み失敗したら初期値設定
404: } catch (xml_parser_error& e) {
405: return initOption();
406: }
407:
408: //アプリケーション・ウィンドウの位置(デスクトップ範囲外なら原点移動)
409: HWND hDesktop = GetDesktopWindow();
410: WINDOWINFO windowInfo;
411: windowInfo.cbSize = sizeof(WINDOWINFO);
412: GetWindowInfo(hDesktop, &windowInfo);
413: if (hParent_X >= (unsigned)windowInfo.rcWindow.right) {
414: hParent_X = 0;
415: }
416: if (hParent_Y >= (unsigned)windowInfo.rcWindow.bottom) {
417: hParent_Y = 0;
418: }
419:
420: return option;
421: }
解説:イベントハンドラ:メインウィンドウ
googlenewswin.cpp
938: /**
939: * イベントハンドラ:メインウィンドウ
940: * @param HWND hDlg 親ウィンドウ・ハンドラ
941: * @paramm UINT uMsg メッセージ識別子
942: * @param WPARAM wParam メッセージの最初のパラメータ
943: * @paramL PARAM lParam メッセージの2番目のパラメータ
944: * @return INT_PTR CALLBACK TRUE:メッセージ処理完了/FALSE:未完了
945: */
946: INT_PTR CALLBACK processMain(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
947: HICON hIcon;
948: char *str;
949: LVITEM item;
950: LV_HITTESTINFO lvinfo;
951: NM_LISTVIEW *pNMLV;
952: TCHAR buff[SIZE_BUFF + 1];
953: static int subsort[100];
954:
955: switch(uMsg){
956: //ダイアログ初期化
957: case WM_INITDIALOG:
958: hParent = hDlg;
959: hIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
960: SendMessage(hParent, WM_SETICON, ICON_SMALL, (LPARAM) hIcon);
961: ErrorMessage = "";
962: //オプション読み込み
963: loadParameter();
964: setStrEditBox(hDlg, IDC_EDIT_QUERY, Query);
965: //アプリケーション・ウィンドウ移動
966: SetWindowPos(hParent, NULL, hParent_X, hParent_Y, 0, 0, (SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER));
967: makeListViewFrame(GetDlgItem(hDlg, IDC_LISTVIEW_NEWS));
968: break;
969:
970: //ボタン押下
971: case WM_COMMAND:
972: switch (LOWORD(wParam)) {
973: //実行
974: case IDM_EXEC:
975: case IDC_BUTTON_SEARCH:
976: ErrorMessage = "";
977: //カーソルを砂時計に
978: SetCursor(LoadCursor(NULL, IDC_WAIT));
979: Query = getStrEditBox(hDlg, IDC_EDIT_QUERY);
980: //Googleニュース検索
981: searchNews((char *)Query.c_str());
982: sortNews(SORT_NONE);
983: //一覧表示
984: makeListView(GetDlgItem(hDlg, IDC_LISTVIEW_NEWS));
985: setStrEditBox(hDlg, IDC_TEXT_ERROR, ErrorMessage);
986: break;
987: //保存
988: case IDC_BUTTON_SAVE:
989: case IDM_SAVE:
990: saveCSV();
991: break;
992: //設定クリア+アプリ終了
993: case IDM_CLEAR_PARAMETER:
994: delParameter();
995: EndDialog(hParent, 0);
996: return 0;
997: break;
998: //ヘルプ
999: case IDM_HELP:
1000: ShellExecute(hParent, _T("open"), _T(HELPFILE), NULL, NULL, SW_RESTORE);
1001: break;
1002: //バージョン表示
1003: case IDM_VERSION:
1004: createHelp(hParent, processHelp);
1005: break;
1006: //解説サイト
1007: case IDM_PAHOO:
1008: ShellExecute(NULL, _T("open"), _T(REFERENCE), NULL, NULL, SW_RESTORE);
1009: break;
1010: //コピー
1011: case IDM_COPY:
1012: setClipboardData(getStrEditBox(hParent, IDC_EDIT_QUERY));
1013: break;
1014: //貼り付け
1015: case IDM_PASTE:
1016: str = getClipboardData();
1017: if (str != NULL) {
1018: setStrEditBox(hParent, IDC_EDIT_QUERY, str);
1019: }
1020: break;
1021: //切り取り
1022: case IDM_DELETE:
1023: setStrEditBox(hParent, IDC_EDIT_QUERY, "");
1024: break;
1025: //プログラム終了
1026: case IDM_QUIT:
1027: //オプション保存
1028: saveParameter();
1029: EndDialog(hParent, 0);
1030: return 0;
1031: default:
1032: return 1;
1033: }
1034: break;
1035:
1036: //通知
1037: case WM_NOTIFY:
1038: switch(((LPNMHDR)lParam)->idFrom) {
1039: case IDC_LISTVIEW_NEWS:
1040: switch (((LPNMLISTVIEW)lParam)->hdr.code) {
1041: //一覧のラベルがクリック
1042: case LVN_COLUMNCLICK:
1043: pNMLV = (NM_LISTVIEW *)lParam;
1044: if (subsort[pNMLV->iSubItem] == SORT_ASC) {
1045: subsort[pNMLV->iSubItem] = SORT_DEC;
1046: } else {
1047: subsort[pNMLV->iSubItem] = SORT_ASC;
1048: }
1049: //ニュース記事の並べ替え
1050: sortNews(pNMLV->iSubItem * 2 + subsort[pNMLV->iSubItem]);
1051: //一覧表示
1052: makeListView(GetDlgItem(hDlg, IDC_LISTVIEW_NEWS));
1053: break;
1054: //ニュース一覧の1行を選択
1055: case LVN_ITEMCHANGED:
1056: //ニュース一覧の1行をダブルクリック
1057: //case NM_DBLCLK:
1058: GetCursorPos((LPPOINT)&lvinfo.pt);
1059: ScreenToClient(((LPNMLISTVIEW)lParam)->hdr.hwndFrom, &lvinfo.pt);
1060: ListView_HitTest(((LPNMLISTVIEW)lParam)->hdr.hwndFrom, &lvinfo);
1061: if ((lvinfo.flags & LVHT_ONITEM) != 0) {
1062: item.mask = TVIF_HANDLE | TVIF_TEXT;
1063: item.iItem = lvinfo.iItem;
1064: item.iSubItem = COL_LINK;
1065: item.pszText = buff;
1066: item.cchTextMax = SIZE_BUFF;
1067: ListView_GetItem(((LPNMLISTVIEW)lParam)->hdr.hwndFrom, &item);
1068: //ブラウザ起動
1069: ShellExecute(hParent, _T("open"), _T(buff), NULL, NULL, SW_RESTORE);
1070: }
1071: break;
1072: default:
1073: break;
1074: }
1075: break;
1076: default:
1077: break;
1078: }
1079: break;
1080:
1081: //プログラム終了
1082: case WM_CLOSE:
1083: //オプション保存
1084: saveParameter();
1085: EndDialog(hParent, 0);
1086: return 0;
1087: }
1088: return 0;
1089: }
ダイアログ初期化(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メインプログラム
googlenewswin.cpp
1092: /**
1093: * Windowsメインプログラム
1094: * @param HINSTANCE hInstance インスタンスハンドル
1095: * @paramm HINSTANCE hPrevInstance 未使用(常にNULL):Win16時代の名残
1096: * @param LPSTR lpCmdLine コマンドライン引数
1097: * @paramL int nShowCmd ウィンドウの表示方法
1098: * @return int リターンコード
1099: */
1100: int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
1101: LoadLibrary("RICHED20.DLL");
1102:
1103: //UserAgent生成
1104: static OSVERSIONINFOEX os;
1105: GetVersion2(&os);
1106: UserAgent = (string)"Mozilla/5.0 (" + APPNAME + "/"
1107: + APPVERSION + "/" + MAKER
1108: + ", Windows NT " + to_string(os.dwMajorVersion) + "."
1109: + to_string(os.dwMinorVersion) + ")";
1110:
1111: hInst = hInstance;
1112: DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, (DLGPROC)processMain);
1113: return 0;
1114: }
解説:ヘルプファイルの作成
参考サイト
- PHPでGoogleニュースの見出しを表示:ぱふぅ家のホームページ
- cURL
- OpenSSL
- リストビュー:インコのWindows SDK
- doc2htmlhelp:s7taka氏
(2024年11月16日)使用ライブラリ更新
(2024年7月14日)使用ライブラリ更新
(2024年3月6日)使用ライブラリ更新