ホーム » MFC (ページ 3)
「MFC」カテゴリーアーカイブ
CWinApp::m_pszAppName
今更の内容ではあるが…
MFC の SDI や MDI で,アプリケーション名は,作成したプロジェクト名?になる.
ダイアログベースで作成した場合は exe 名になってしまう.
困ることがあるのが exe 名を変更した場合のレジストリキー.
CWinApp::m_pszAppName はヘルプにある様に,AFX_IDS_APP_TITLE がなければ exe 名となる.
コードは MFC 6 のものだが,MFC 14.3 でも同様.
CWinApp::SetRegistryKey で m_pszAppName を利用している.
exe 名と異なるものを使用する場合,リソースの AFX_IDS_APP_TITLE を変更するか,なければ追加すれば良い.
…\System32\MicrosoftEdgeCP.exe
…\System32\MicrosoftEdgeCP.exe が存在するのに,stat などでうまく読み取れない.
ファイルが存在しているかどうかをチェックするために,CFileStatus などを利用している.
CFileStatus::GetStatus() で,幾つかのファイルが正しくチェックできない.
コードをデバッガで追いかけていくと,::FindFirstFile で INVALID_HANDLE_VALUE となってしまう.
検索 すると次の様なものがあった.
FindFirstFile関数はx64環境においてシステムファイルが検索できない?
x86 の場合次のものを呼出して切替える必要があるみたい.
Wow64DisableWow64FsRedirection
Wow64RevertWow64FsRedirection
File System Redirector
今回 ::GetFileVersionInfo から始まって … いろいろとあったのでメモ.
MFC OnUpdate… が来ない?
自前の Doc::UpdateCommand で CCmdUI の処理を実装して,イベントが来なかった.
原因は,範囲の指定が間違っていた.
ON_UPDATE_COMMAND_UI_RANGE (ID_Start, ID_End, OnUpdateCmd)
すぐに気づかなかったのでメモ.
ツールバーのコマンドが効かない
ある AP の動作で,ツールバーの一部のコマンドが入らない.
いろいろと確認すると,x64 exe で,リリース版,デバッグ版は関係ない.
x86 exe は問題ない(ちゃんと機能する).
2015/11 の exe でも同様の現象を確認.
メニューの同様のコマンドは問題ない.
ツールバーがちらつくので,UPDATE_COMMAND_UI の実装でうまくない部分があると思われる.
一時的に Disable にしているなど…
調べるのに時間がかかりそうなので,取りあえずメモ.
状態により,次の矛盾したものが呼ばれることがあったため修正.
pCmdUI->Enable(FALSE) ;
pCmdUI->Enable(TRUE) ;
Win.ini [Mail] MAPI=1
Win.ini がなくて MAPI が動作しなかった(Disable だった)と報告を受けた.
4 年位前に書き直した MAPI を使用したコードではチェックしない様にしたが,古いコードもまだ存在する.
それらのコードは MFC のコード docmapi.cpp を参考にしたもの.
VC 2022 のコードを見ると,Win.ini の [MAIL] の MAPI をチェックしている.
最初のコードを書いた 20 年以上前(VC 98)とそれほど変わっていない.
何もインストールしていない環境で Win.ini は存在する?
Win10 Pro 21H2 では次のものが存在している.
何かをアンインストールすると削除されることがあるのか?
引数が正しくありません。
自分で作成した AP のテストをしていて,「引数が正しくありません。」.
20 年位前から変更していないコードのバグだった.
かなりイレギュラーな操作をしない限り,現象は発生しない.
実際の操作としては,データの挿入場所を指定しないで,強制的に「確定」した場合.
またその操作の前にある手順やデータが影響する.
表示されるメッセージは,MFC のバージョンにより異なる.
また,Windows API 内でこのメッセージが表示されることもある みたい.
今回のものは,配列に対して,確保されている領域を超えてアクセスしたことが原因.
アクセスする前に,要素数が満たしているかのチェックをすることで回避できる.
if ( 2 < ary.GetSize() ) { data = ary[1] ; }
これって何とかならないものかといつも思ってしまう.
クラスとして書いた時には,デフォルト値を返すようにしたりしているが…
MDI exe に lnk のドロップで開けない
MDI exe に lnk(ドキュメントへのショートカット)をドロップすると,
—————————
BLAM
—————————
エラーはありませんでした。
—————————
OK
—————————
ショートカット先のドキュメントファイルであれば開ける.
一度 GetOpenFileName などで ダイアログを開くと,その後は問題ない.
デバッガで追いかけると,AfxResolveShortcut でエラーになっている.
対応としては InitInstance の最初に以下を追加.
if (!AfxOleInit()) {
AfxMessageBox(_T("OLE の初期化に失敗しました。")) ;
return FALSE ;
}
今回これを調べたのは,WM_DROPFILES の動作.
InitInstance で次の様にしている場合,CMainFrame::OnDropFiles で処理する.
m_pMainWnd->DragAcceptFiles();
MDI exe では,CView や CChildFrame で処理できそうだが,そのままでは呼ばれることはない.
予めそれぞれで DragAcceptFiles() が必要みたい.
例えば,次の様に CView で呼び出すと WM_DROPFILES が処理できる様になる.
void CBLAMView::OnInitialUpdate()
{
this->DragAcceptFiles();
// ...
}
void CBLAMView::OnDropFiles(HDROP hDropInfo)
{
CView::OnDropFiles(hDropInfo);
}
CMainFrame など以外の実装は Default() を呼出しているだけみたい.
_AFXWIN_INLINE void CWnd::OnDropFiles(HDROP)
{ Default(); }
ビューのウィンドウ以外(グレーの部分)にドロップすると CMainFrame::OnDropFiles が呼ばれる.
C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\atlmfc\src\mfc\winfrm.cpp
void CFrameWnd::OnDropFiles(HDROP hDropInfo)
{
SetActiveWindow(); // activate us first !
UINT nFiles = ::DragQueryFile(hDropInfo, (UINT)-1, NULL, 0);
CWinApp* pApp = AfxGetApp();
ASSERT(pApp != NULL);
for (UINT iFile = 0; iFile < nFiles; iFile++)
{
TCHAR szFileName[_MAX_PATH];
::DragQueryFile(hDropInfo, iFile, szFileName, _MAX_PATH);
pApp->OpenDocumentFile(szFileName);
}
::DragFinish(hDropInfo);
}
CMainFrame で CDocument などを求める.
SDI の場合
CDocument* pActiveDoc = GetActiveDocument() ;
CXxxView* pView = (CXxxView*)GetActiveView() ;
MDI の場合は,GetActiveFrame() で CFrameWnd を求めてから.
CFrameWnd* pActiveFrame = GetActiveFrame() ;
CDocument* pActiveDoc = NULL ;
if (pActiveFrame != NULL) {
pActiveDoc = pActiveFrame->GetActiveDocument() ;
}
CXxxView* pView = NULL ;
if (pActiveFrame != NULL) {
pView = (CXxxView*)pActiveFrame->GetActiveView() ;
}
AfxResolveShortcut
個人的に作成したダイアログベースのツールで,lnk をドロップした時に開けない.
これに対応するには,lnk から doc を求めることで対応できる.
同じ様に作成した SDI exe の場合は,うまく開ける?
デバッガで追いかけると,CDocManager::OpenDocumentFile で AfxResolveShortcut を呼んでいる.
但し,MFC のバージョンにより? ::CoInitialize が呼び出されていないと 1 回だけ.
CFileDialog などを呼出すと ::CoInitialize にあたるものが呼び出されるのでその後はうまくいく.
https://dev.mish.work/wordpress/2021/03/31/win10-21h1-mdi-exe-error/
次の様な関数を用意して,ドロップされたファイルが lnk の時に対応.
tstring LNK_Get_path (HWND hwnd,LPCTSTR lnk_path)
{
tstring doc_path = lnk_path ;
if (::Path_GetExtLow(lnk_path) == _T("lnk")) {
doc_path = ::Get_path_lnk(hwnd,lnk_path) ;
}
return doc_path ;
}
m_atime 2446/05/11 07:38:55
2022/01/17
VC 2019 でビルドした exe でも特に変わらず.
ローカルドライブや Synology NAS では,正しく設定される.
MFC exe で QNAP NAS 上に上書き保存した時にその様になってしまう?
ASUSTOR NAS でも QNAP NAS 同様に正しくない.
デバッガで追いかけると,
CMirrorFile::Close で MFC9EA4BEB9.tmp を ::ReplaceFile の時.
出力されたファイルを CFileStatus で見ると 0x000000037fffffff .
0000 0003 7fff ffff が 2446/05/11 になる.
「ダイアログ バー」の追加
Win10 環境で VC 6 を使えるようにはなったが,「コンポーネントギャラリ」はうまく機能しないみたい.
今回利用しようとしたのは「ダイアログ バー」.ヘルプの内容は次の様なもの.
ダイアログ バー コンポーネント : 挿入結果
ダイアログ バー コンポーネントを挿入すると、プロジェクト コードに次の変更が加えられます。
- プロジェクトのリソース スクリプトにダイアログ テンプレート リソースが追加されます。このダイアログ テンプレートにコントロールを追加するには、Visual C++ のダイアログ エディタを使用できます。
- 指定したフレーム ウィンドウ クラスに、CDialogBar 型のメンバ変数の宣言が追加されます。デフォルトの変数名 m_wndMyDialogBar は変更できます。ダイアログ バーの初期プロパティ (標準では可視、ドッキング可能など) も指定できます。
- フレーム ウィンドウの OnCreate メンバ関数に初期化コードが追加されます。この初期化コードは、CDialogBar::Create を呼び出して、ダイアログ バーのドッキング オプションを設定します。また、ダイアログ バーの表示/非表示に関する初期化もここで行います。
- ダイアログ バーの表示/非表示に関するメニュー項目のサポートが追加されます。
ダイアログ バー コンポーネントを挿入しても、ダイアログ バーの表示/非表示を切り替えるためのメニュー項目はメニュー リソースに追加されません。このメニュー項目はメニュー エディタを使用して手作業で追加する必要があります。コンポーネントは、メニュー項目の追加手順を記述した “TODO” コメントを追加します。このコメントは、ダイアログ バーを追加したフレーム クラスの OnCreate 関数に挿入できます。
- 2 つの関数のハンドラがフレーム ウィンドウ クラスのメッセージ マップに追加されます。
- OnUpdateXXX 関数はメニュー項目の状態を随時更新します。
- 次に、OnBarCheck 関数は、メニュー項目のチェックマークのオン/オフどおりに、ダイアログ バーが表示/非表示になっているかどうかを調べます。
メニュー項目のプロンプト文字列がプロジェクトのストリング テーブル リソースに追加されます。コマンド ハンドラをインプリメントするには、ClassWizard を使用して、次の手順に従います。
- ResourceView で、ダイアログ バーのダイアログ テンプレートを開きます。
- Ctrl キーを押しながら W キーを押して ClassWizard を開きます。[クラスの追加] ダイアログ ボックスが表示されます。
- [クラスを選択] をクリックして、[OK] をクリックします。[クラスの選択] ダイアログ ボックスが表示されます。
- ダイアログ バー コンポーネントを挿入したときに指定したフレーム ウィンドウ クラスの名前をクリックして、[選択] をクリックします。このクラスとダイアログ リソースを関連付けるかどうかを確認するメッセージ ボックスが表示されます。
- [はい] をクリックします。[MFC ClassWizard] ダイアログ ボックスの [メッセージ マップ] タブの [オブジェクト ID] ボックスの一覧に、ダイアログ バーのコントロールの ID が表示されます。
- 通常の方法で、コントロールにコマンド ハンドラを割り当てます。
メモ ActiveX コントロールをダイアログ バーに追加する場合は、プロジェクトが ActiveX コントロール コンテナ機能をサポートしている必要があります。AppWizard でプロジェクトのスケルトンを生成したときに [ActiveX コントロール] オプションを選択していた場合は、ActiveX コントロール コンテナ機能のサポートがプロジェクトに含まれています。サポートがない場合は、[Gallery] の [Visual C++ Components] フォルダから [ActiveX コントロール コンテナ コンポーネント] を選択してプロジェクトに挿入する必要があります。
実際のコードなどの編集は次の様な手順.
ダイアログリソースの追加.
「スタイル」を「チャイルド」に.
「境界線」を「しない」に.
「タイトルバー」のチェックを外す.
CMainFrame に変数を追加.
CDialogBar m_wndDlgBar ;
CMainFrame::OnCreate に追加.
{
if (!m_wndDlgBar.Create(this, IDD_DIALOG_BAR,
CBRS_RIGHT | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_HIDE_INPLACE,
ID_DIALOG_BAR))
{
TRACE0("Failed to create dialog bar m_wndDlgBar\n");
return -1; // fail to create
}
// m_wndDlgBar.EnableDocking(CBRS_ALIGN_RIGHT | CBRS_ALIGN_LEFT);
// EnableDocking(CBRS_ALIGN_ANY);
// DockControlBar(&m_wndDlgBar);
}
Resource.h に ID_DIALOG_BAR を追加.
ダイアログバー上のコントロールに対しての操作は次の様な感じ.
void CMainFrame::OnSize(UINT nType, int cx, int cy)
{
CFrameWnd::OnSize(nType, cx, cy);
if (m_wndDlgBar.GetSafeHwnd() == NULL) { return ; }
CListBox* listBI = (CListBox*)m_wndDlgBar.GetDlgItem(IDC_LIST_BI) ;
FitWindow(&m_wndDlgBar,listBI,3, FALSE, FALSE, FALSE, FALSE) ;
}
再起動マネージャ
何年か前に有効にした「再起動マネージャ」.
Restart Manager in MFC
MFC 再起動マネージャ
その実装がうまくなかった.
編集操作中に「自動保存」が動作すると,意図しない状態になってしまうことがあった.
原因は,実装方法がうまくないだけではあるが,なかなか難しい.
Serialize が呼ばれた時に,一部の情報を更新しているため,例えば選ばれているものに影響を与えてしまう.
実際はデータに応じて選択状態が変わるのだが,それに気づかず操作してしまうことがある.
これとは別にバックアップ機能を持っているので「自動保存」が動作しない様に修正することに.
本当はこれだけでは足りない.
編集操作中に「上書き保存」された時も同様に動作するため,この部分の修正が必要か?
ユーザが意識している操作なので,現状のままとするか?
VS 2022 インストール
VS 2022 があったのでインストール.
共通の C の include などは VC 10 からの設定が引き継がれている(同じ所).
フォールバックの設定を E:\Temp\_Fallbck に.
MFC などランタイムは VC 2015 以降同じもの.
3D ビューアをビルドしたが,その範囲では,特に目立った違いはなさそう.
Current という表現は何なんだろうか?
VC のランタイム vc_redist.x86.exe などは次の所にまとめている.
https://mish.work/joomla/index.php/cpp/ref-vcredist-xxx-exe.html
VC 2015 から 2022 までは,同じもので動作する.
::SetWindowPos(hWnd,…)
wndTopMost で,Z オーダーの最前面に.
{
CRect rect ;
GetWindowRect(&rect) ;
SetWindowPos(&wndTopMost,rect.left,rect.top,0,0,SWP_NOSIZE|SWP_NOACTIVATE) ;
}
幾つかの環境ではこれでうまく動作している.
今日作成したツールの CMSetNmTDlg::OnInitDialog() にこれを追加.
Win10 のホスト環境で最前面に移動しない.
仮想マシンで試すと意図した動作.
ホストの,以前に起動したツールは前面のまま保持された.
が,一度終わらせて再起動したら,最前面にならなくなった.
ちょっとよくわからないが,何かが邪魔しているのか?
PC を再起動すれば直るかもしれないが…
とりあえずメモ.
2021/10/25
explorer.exe の再起動でうまく動作する様になったみたい.
他 AP などからの操作の場合 SetForegroundWindow
2023/07/12
Windows API だと次の様に指定できる.
::SetWindowPos(this->GetSafeHwnd(),HWND_TOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE) ;
2 つ目の引数は次のものが指定可能.
#define HWND_TOP ((HWND)0)
#define HWND_BOTTOM ((HWND)1)
#define HWND_TOPMOST ((HWND)-1)
#define HWND_NOTOPMOST ((HWND)-2)
2024/12/04
MFC では次の方が簡単?
{
SetWindowPos(&wndTopMost,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE) ;
}
CCmdUI::SetText
以前,VC 11 以降で メニューのテキストがうまく更新できない 現象があった.
この時,オーナードローを使っている所は解決した(個人的なコードのバグだった).
が,普通のサブメニューの方はうまくないままとなっていて,それを今回改めて調べてみた.
デバッガで追いかけると,CcmdUI::SetText は呼ばれている.
MFC のコードを見ると,ModifyMenu と SetMenuItemInfo の違いがある.
サブメニューの場合にうまくないのかと思い,単体テスト用のコードを書いてみたが,再現できない.
void CM_textView::OnUpdateMenuItem(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
CString now = CTime::GetCurrentTime().Format(_T("%H:%M:%S")) ;
pCmdUI->SetText(now) ;
}
void CM_textView::OnMenuItem(UINT nID)
{
// TODO: Add your command handler code here
}
何か他の条件があるみたい.
CMenu::TrackPopupMenu で表示するメニューではうまく更新されている.
メニューバーの項目としてコマンドを割り当て,そこから TrackPopupMenu を呼出すことで対応.
コンソール AP で SetRegistryKey …
以前一度やっているが…
https://dev.mish.work/wordpress/2015/01/28/console-ap-reg-read/
class CMy_App : public CWinApp {
public:
void SetRegistryKey_ (LPCTSTR key) { SetRegistryKey(key) ; }
} ;
//CWinApp theApp ;
CMy_App theApp ;
int _tmain (int argc,TCHAR* argv[])
{
_tsetlocale(LC_ALL,_T("")) ;
::reg_argv(argc,argv) ;
{
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0)) {
std::terr << _T("Fatal Error: MFC initialization failed") << std::endl ;
}
theApp.SetRegistryKey_(Profile::GetRegKey_Base()) ; // レジストリを使用する
}
if (argc > 1) {
for (int index=1 ; index<argc ; index++) {
tstring fold = argv[index] ;
::test(fold) ;
}
}
else {
tstring def_path = LPCTSTR(::PC_get_current_page()) ;
tstring fold = def_path ;
while(true) {
fold = ::ask_folder(fold.c_str()) ;
if (fold.empty()) { break ; }
::test(fold) ;
}
}
return 0 ;
}
std::vector::data()
今まで VC 14 リリース版などでは通っていたコード.
VC 8 でビルドした exe で,配列の要素が空の時に終了してしまう.
MapiMessage message ; memset(&message,0,sizeof(message)) ; message.nFileCount = ULONG(fileDescA.size()) ; message.lpFiles = &fileDescA[0] ; message.nRecipCount = ULONG(recipDescA.size()) ; message.lpRecips = &recipDescA[0] ;
&fileDescA[0] で vector の先頭を参照することがうまくないみたい.
次の様に data() を使える環境ならば良さそう.
message.nFileCount = ULONG(fileDescA.size()) ; message.lpFiles = fileDescA.data() ;
使えない場合は,空でないことを確認する必要がある?
if (fileDescA.size() > 0) { message.nFileCount = ULONG(fileDescA.size()) ; message.lpFiles = &fileDescA[0] ; }
MFC ドキュメントの関連付け
スケルトン作成時,ファイルの拡張子を指定しなかったプロジェクトに,後から関連付けのコードを追加する手順.
アプリケーションクラスの InitInstance に EnableShellOpen と RegisterShellFileTypes を追加.
BOOL CXxxxApp::InitInstance() { // ... AddDocTemplate(pDocTemplate); // DDE Execute open を使用可能にします。 EnableShellOpen(); RegisterShellFileTypes(TRUE); // DDE、file open など標準のシェル コマンドのコマンドラインを解析します。 CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); // ... // メイン ウィンドウが初期化されたので、表示と更新を行います。 m_pMainWnd->ShowWindow(SW_SHOW); m_pMainWnd->UpdateWindow(); // ドラッグ/ドロップ オープンを許可します m_pMainWnd->DragAcceptFiles(); return TRUE; }
必要に応じて,DragAccesptFiles .
リソースの String Table IDR_MAINFRAME で,ファイルタイプと拡張子を指定する.
文字列は 7 つに区切られている.CDocTemplate::GetDocString
最近の Windows は,RegisterShellFileTypes では登録できない.
そのため,自前の RegFileType::SetFileType(TRUE) を呼出す.
RegFType.hxx RegFType.cxx
RegisterShellFileTypes とは違い,HKEY_CURRENT_USER\Software\Classes\ に登録している.
スタティック ライブラリで MFC …
「共有 DLL で MFC を使う」にしていたプロジェクトを「スタティック ライブラリで MFC を使用する」に変更.
ビルドして実行すると起動時エラーに.
VC 6 から順にあげてきたプロジェクトで,設定がうまく引き継がれていない.
個々のソースなどは変更した が,それだけでは足りないみたい.
プロジェクトの設定で「リソース」の「プリプロセッサの定義」に _AFXDLL が定義されてしまっている.
「<親またはプロジェクトの既定値から継承>」に変更してうまく動作する様になった.
MFC 起動時のドキュメントの変更
ドキュメントのドロップ時のファイル名の変更 には対応したが,今度は起動時のドキュメントの変更.
InitInstance の所をデバッガで追いかけると,CWinApp::ProcessShellCommand の前後で対応できそう.
次の様に,対象のファイルを変更して起動できることは確認.
CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); { if (cmdInfo.m_nShellCommand == CCommandLineInfo::FileOpen) { cmdInfo.m_strFileName = ::Path_ChangeExt(cmdInfo.m_strFileName,_T("ig3")).c_str() ; } } if (!ProcessShellCommand(cmdInfo)) // ...
SDI exe でファイルのドロップ
SDI exe でファイルをドロップされた時,関連する異なるファイルを開きたくなった.
次の様に,変換済みのデータが存在している時はそれを対象とする.
https://dev.mish.work/wordpress/2021/03/03/win-prevent-2nd/
ドキュメントクラスの OnOpenDocument で変更するのは可能だが「MRU ファイル リスト」に残ってしまう.
15 年位前に,ブラウザからリンクをドロップして動作させるコードを書いた.
最近のブラウザでは,当時の様な操作はできないみたい.
WM_DROPFILES でリンクのテキストが取れたのだったと思う.
この時,何を参考にしてここにたどり着いたかは不明.
それを見ると CMainFrame::OnDropFiles にコードが書いてある.
デフォルトのコードとしては,CFrameWnd::OnDropFiles を呼出している.
::DragQueryFile でファイル名を取って CWinApp::OpenDocumentFile を呼出している.
CMainFrame::OnDropFiles でファイル名を取った後,処理を追加して CWinApp::OpenDocumentFile を呼べば良い.
手順としては (VC)\…\atlmfc\src\mfc\winfrm.cpp の CMainFrame::OnDropFiles をコピーして編集している.
void CMainFrame::OnDropFiles(HDROP hDropInfo) { SetActiveWindow(); // activate us first ! CWinApp* pApp = AfxGetApp(); ASSERT(pApp != NULL); v_tstring files = ::DropFilesTo(hDropInfo) ; if (files.size() > 0) { tstring file = files[0] ; // ここで,必要に応じてファイル名を変更するなどの処理 pApp->OpenDocumentFile(file.c_str()) ; } ::DragFinish(hDropInfo); // CFrameWnd::OnDropFiles(hDropInfo); }
今回は SDI なのでこんな感じ.MDI であればループで回せば良い.