ShellExecuteで開くマイコンピュータのサイズと位置を指定する

解決


とっても初心者  2006-03-24 21:43:32  No: 60998

winxp  .Net  mfc  です。

ShellExecuteで開くマイコンピュータのモニター上のサイズと位置を指定したいのですが、やりかたが分かりません。

MSDNでShellExecuteを検索すると、最後の引数について、

SW_SHOWDEFAULTを選択した場合、

「アプリケーションは、このフラグを指定して ShowWindow 関数を呼び出し、自らのメインウィンドウの初期の表示状態を設定するべきです。」

とあり、できそうなのですが、初心者で、具体的にどうコーディングしたらいいのか、分かりません。

アドバイスをいただけませんでしょうか。
よろしくお願いいたします。


瀬戸っぷ  2006-03-25 09:08:33  No: 60999

CreateProcess()ならば、STARTUPINFO構造体で指定することは可能なようです。
(指定に従ってくれるかはまた別の問題ですが)

ShellExecute()では無理ではないかと思いますが……
なんらかの方法でウィンドウハンドルを取得して、MoveWindow()で対処するしかないかと思われます。


Blue  2006-03-25 09:14:42  No: 61000

> CreateProcess()ならば、STARTUPINFO構造体で指定することは可能なようです。
> (指定に従ってくれるかはまた別の問題ですが)
いろいろ試しましたが、Explorerはムリなようです。
ついでに、PROCESS_INFORMATIO構造体から、EnumThreadWindowsでウィンドウハンドルも取得できないようです。
(notepadとかならば、MoveWindowでできたんだけど)


Blue  2006-03-25 09:42:36  No: 61001

参考までに実験したコードを載せておきます。
誤り等ありましたら、ご指摘ください。
# EnumWindowsで該当ProcessIDで探してもダメでした。

// WindowsXp SP2 Pro/VS2005Pro or VC++6.0SP6Enter/Win32 ConsoleApp
#include <windows.h>

struct PROCESS_INFORMATIONEX : public PROCESS_INFORMATION
{
    HWND hWnd;
};

BOOL CALLBACK EnumThreadWndProc( HWND hWnd, LPARAM lParam )
{
    if ( !::GetParent( hWnd ) )
    {
        *( reinterpret_cast< HWND* >( lParam ) ) = hWnd;
        return FALSE;
    }
    return TRUE;
}

BOOL CALLBACK EnumWindowsProc( HWND hWnd, LPARAM lParam )
{
    PROCESS_INFORMATIONEX* pi = reinterpret_cast< PROCESS_INFORMATIONEX* >( lParam );

    DWORD dwProcessId = 0;
    ::GetWindowThreadProcessId( hWnd, &dwProcessId );

    if ( pi->dwProcessId == dwProcessId )
    {
        pi->hWnd = hWnd;
        return FALSE;
    }
    return TRUE;
}

int main()
{
    STARTUPINFO si = { 0 };
    PROCESS_INFORMATIONEX pi;

    si.cb = sizeof( si );
    si.dwX = 0;
    si.dwY = 0;
    si.dwXSize = 400;
    si.dwYSize = 400;
    si.wShowWindow = SW_HIDE;
    si.dwFlags = STARTF_USESHOWWINDOW;// | STARTF_USESIZE | STARTF_USEPOSITION;

    TCHAR szCmd[] = TEXT( "explorer" );
    //TCHAR szCmd[] = TEXT( "notepad" );

    if ( ::CreateProcess( NULL, szCmd, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi ) )
    {
        ::WaitForInputIdle( pi.hProcess, INFINITE );
        //::EnumThreadWindows( pi.dwThreadId, EnumThreadWndProc, reinterpret_cast< LPARAM >( &pi.hWnd ) );
        ::EnumWindows( EnumWindowsProc, reinterpret_cast< LPARAM >( &pi ) );
        if ( ::IsWindow( pi.hWnd ) )
        {
            ::MoveWindow( pi.hWnd, si.dwX, si.dwY, si.dwXSize, si.dwYSize, TRUE );
            ::ShowWindow( pi.hWnd, SW_SHOW );
            ::SetForegroundWindow( pi.hWnd );
        }
        ::WaitForSingleObject( pi.hProcess, INFINITE );
        ::CloseHandle( pi.hThread );
        ::CloseHandle( pi.hProcess );
    }

    return 0;
}


Blue  2006-03-25 09:45:56  No: 61002

修正)
pi.hWnd = NULL;

が必要でした。あっても結果は変わりませんけど。


Blue  2006-03-25 10:54:50  No: 61003

> なんらかの方法でウィンドウハンドルを取得して、MoveWindow()で対処するしかないかと思われます。
マイ コンピュータを他に開いていたら使えない方法です。

#include <windows.h>
#include <tchar.h>

BOOL CALLBACK EnumWindowsProc( HWND hWnd, LPARAM lParam )
{
    static TCHAR szTitle[ MAX_PATH ];
    ::GetWindowText( hWnd, szTitle, sizeof( szTitle ) / sizeof( TCHAR ) );
    if ( !_tcscmp( szTitle, TEXT( "マイ コンピュータ" ) ) )
    {
        *( reinterpret_cast< HWND* >( lParam ) ) = hWnd;
        return FALSE;
    }
    return TRUE;
}

int main()
{
    HWND hWnd;
    ::ShellExecute( NULL, TEXT( "explore" ), TEXT( "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" ), NULL, NULL, SW_HIDE );
    ::EnumWindows( EnumWindowsProc, reinterpret_cast< LPARAM >( &hWnd ) );
    if ( ::IsWindow( hWnd ) )
    {
        ::MoveWindow( hWnd, 0, 0, 600, 600, TRUE );
        ::ShowWindow( hWnd, SW_SHOW );
        ::SetForegroundWindow( hWnd );
    }
    return 0;
}


とっても初心者  2006-03-25 17:15:04  No: 61004

ありがとうございます。

初心者でMFCしか分からず、いろいろやってみたのですが、置き換えることができません。
こんなふうにMoveWindowを使えばいい、ということは分かったのですが、肝心のマイコンピュータのハンドルの取り方が分かりません。

ただ、マイコンピュータを2つスクリーン上に表示するのが目的で、そのため画面の住み分けをしたいと考えました。

そもそもVC++でできる、ということと、自分がコーディングできる、ということは全く別ですし、難しいのかなぁ、と思いました。


Blue  2006-03-25 21:24:19  No: 61005

> ただ、マイコンピュータを2つスクリーン上に表示するのが目的で、そのため画面の住み分けをしたいと考えました。
住み分けがよくわかりませんが、べたにかくとこんな感じ?
# マイコンピュータではなくマイコンピュータを表示しているエクスプローラですよね?
#include <windows.h>
#include <tchar.h>

struct MyComputers
{
    HWND hWnd1;
    HWND hWnd2;
};

BOOL CALLBACK EnumWindowsProc( HWND hWnd, LPARAM lParam )
{
    static TCHAR szTitle[ MAX_PATH ];
    ::GetWindowText( hWnd, szTitle, sizeof( szTitle ) / sizeof( TCHAR ) );
    if ( !_tcscmp( szTitle, TEXT( "マイ コンピュータ" ) ) )
    {
        MyComputers* mc = reinterpret_cast< MyComputers* >( lParam );
        if ( mc->hWnd1 )
        {
            mc->hWnd2 = hWnd;
            return FALSE;
        }
        mc->hWnd1 = hWnd;
    }
    return TRUE;
}

int main()
{
    MyComputers mc = { 0 };
    ::ShellExecute( NULL, TEXT( "explore" ), TEXT( "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" ), NULL, NULL, SW_HIDE );
    ::ShellExecute( NULL, TEXT( "explore" ), TEXT( "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" ), NULL, NULL, SW_HIDE );
    ::EnumWindows( EnumWindowsProc, reinterpret_cast< LPARAM >( &mc ) );

    const int screenX = ::GetSystemMetrics( SM_CXSCREEN );
    const int screenY = ::GetSystemMetrics( SM_CYSCREEN );

    if ( ::IsWindow( mc.hWnd1 ) )
    {
        ::MoveWindow( mc.hWnd1, 0, 0, screenX / 2, screenY, TRUE );
        ::ShowWindow( mc.hWnd1, SW_SHOW );
    }
    if ( ::IsWindow( mc.hWnd2 ) )
    {
        ::MoveWindow( mc.hWnd2, screenX / 2, 0, screenX / 2, screenY, TRUE );
        ::ShowWindow( mc.hWnd2, SW_SHOW );
    }
    return 0;
}

ちなみに、具体的にMFCのコードに書き換えるところはないです。
いちいちCWndクラス使う必要性はないです。

> 初心者でMFCしか分からず
結構怖いですな、、、MFCかわからないという状態はありえないと思うけど。


X_File  2006-03-26 00:45:19  No: 61006

IShellBrowserインターフェイスを二つ実装したフォームを
作成するという手もありですが、MFCだとCHtmlViewで・・


とっても初心者  2006-03-26 00:53:46  No: 61007

int main()以下を、OnBnClickedButton1の中にコピペして、
BOOL CALLBACK EnumWindowsProc( HWND hWnd, LPARAM lParam )を手書きして、リビルドしますと、

 'EnumWindows' : 1 番目の引数を 'BOOL (HWND,LPARAM)' から 'WNDENUMPROC' に変換できません。

というエラーになります。

ここから先が分かりません。

あれこれ書き換えてやってみましたが、解決しません。


Blue  2006-03-26 00:57:47  No: 61008

EnumWindowsの第一引数に渡す関数は必ず、

BOOL型の値を返し、第一引数が HWND型、第二引数が  LPARAM型 でないといけません。
どのように、手書きをしているのかわからないのでこれ以上のアドバイスは出来ません。


とっても初心者  2006-03-26 01:25:28  No: 61009

ありがとうございます。

BOOL CALLBACK EnumWindowsProc( HWND hWnd, LPARAM lParam )は、最初ソースファイルにぺたっとコピペして、ヘッダファイルで
        afx_msg BOOL CALLBACK EnumWindowsProc( HWND hWnd, LPARAM lParam )
とやってみました。

そこからあれこれ書き換えたり、移動したりしてみましたが、解決しません。


Blue  2006-03-26 01:34:51  No: 61010

なんで afx_msg つけてんの?

まず、ダイアログのメンバ関数とかにしたいならば、

static BOOL CALLBACK EnumWindowsProc( HWND, LPARAM );

と宣言し、定義のほうは

BOOL CALLBACK CXXXXDlg::EnumWindowsProc( HWND hWnd, LPARAM lParam )
{
////
}

みたいにして、

呼び出す時には

::EnumWindows( CXXXXDlg::EnumWindowsProc, reinterpret_cast< LPARAM >( &mc ) );

というふうにします。


とっても初心者  2006-03-26 03:42:58  No: 61011

たいへんありがとうございました。

ばっちり実現しました。


Blue  2006-03-26 11:25:24  No: 61012

良く考えたら、EnumWindowsを使う必要がないような気がしてきた。
FindWindowExでハンドル取得できそう。

void CXXXXDlg::OnBnClickedButton1()
{
    // TODO: ここにコントロール通知ハンドラ コードを追加します。
    ::ShellExecute( NULL, TEXT( "explore" ), TEXT( "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" ), NULL, NULL, SW_HIDE );
    ::ShellExecute( NULL, TEXT( "explore" ), TEXT( "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" ), NULL, NULL, SW_HIDE );

    HWND hWnd1 = ::FindWindowEx( NULL, NULL, TEXT( "ExploreWClass" ), TEXT( "マイ コンピュータ" ) );
    HWND hWnd2 = ::FindWindowEx( NULL, hWnd1, TEXT( "ExploreWClass" ), TEXT( "マイ コンピュータ" ) );

    const int screenX = ::GetSystemMetrics( SM_CXSCREEN );
    const int screenY = ::GetSystemMetrics( SM_CYSCREEN );

    if ( ::IsWindow( hWnd1 ) )
    {
        ::MoveWindow( hWnd1, 0, 0, screenX / 2, screenY, TRUE );
        ::ShowWindow( hWnd1, SW_SHOW );
        ::SetForegroundWindow( hWnd1 );
    }
    if ( ::IsWindow( hWnd2 ) )
    {
        ::MoveWindow( hWnd2, screenX / 2, 0, screenX / 2, screenY, TRUE );
        ::ShowWindow( hWnd2, SW_SHOW );
        ::SetForegroundWindow( hWnd2 );
    }
}


Blue  2006-03-26 11:41:20  No: 61013

>     HWND hWnd1 = ::FindWindowEx( NULL, NULL, TEXT( "ExploreWClass" ), TEXT( "マイ コンピュータ" ) );
>     HWND hWnd2 = ::FindWindowEx( NULL, hWnd1, TEXT( "ExploreWClass" ), TEXT( "マイ コンピュータ" ) );

CWnd* pWnd1 = this->FindWindowEx( NULL, NULL, TEXT( "ExploreWClass" ), TEXT( "マイ コンピュータ" ) );
CWnd* pWnd2 = this->FindWindowEx( NULL, pWnd1->GetSafeHwnd(), TEXT( "ExploreWClass" ), TEXT( "マイ コンピュータ" ) );
として扱ったほうがMFCっぽいかな。
ただし、CWnd::FindWindowExがどのバージョンから使えるか不明。
(VC6のときのMFCはない、VS2005のときはあった。.NETはわからん)


とっても初心者  2006-03-26 14:12:03  No: 61014

Blueさん、ありがとうございます。

EnumWindowsなしで動きます。

実は、関連して、質問させてください。

正確に言うと、開きたいのは、マイドキュメントのサブディレクトリで、GetCurrentDirectoryで絶対パスを取得して、そこから開きたいのですが、これがうまくいきません。

不思議なのは、試しにまずマイドキュメントを開いてみようと、
"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}"を
"::{450D8FBA-AD25-11D0-98A8-0800361B1103}"に置き換えただけでも、開かなくなってしまいます。

そのほか、MSDNを見たり、ネットをくぐったりして、引数をいろいろ変えてみましたが、いずれもだめでした。

心苦しいのですが、ご指導頂けませんでしょうか。


Blue  2006-03-26 22:28:32  No: 61015

> "::{450D8FBA-AD25-11D0-98A8-0800361B1103}"に置き換えただけでも、開かなくなってしまいます。
開いていると思いますよ。
ShellExecuteの最後の引数が、SW_SHOWではないからそのように見えるだけ。
(ウィンドウハンドルを見つけれないので、ShowWindowsまでいっていない)


Blue  2006-03-26 23:08:54  No: 61016

ちなみに、マイドキュメント等の特殊フォルダのパスは、SHGetSpecialFolderPathで取得しましょう。

#include <shlobj.h>
#include <shlwapi.h>

#pragma comment( lib, "shlwapi.lib" )



TCHAR dir[ MAX_PATH ];
TCHAR subdir[] = TEXT( "フォルダ1" );
::SHGetSpecialFolderPath( NULL, dir, CSIDL_PERSONAL, FALSE );
::PathAppend( dir, subdir );


とっても初心者  2006-03-27 19:04:28  No: 61017

Blueさん、ありがとうございます。

いろいろ工夫してみて、最終的に下記のようにしました。
char fullpath[]は、絶対パスです。

これで、アプリの立ち上げ直後はFolder1とFolder2が開くのですが、一旦閉じて、再度開こうとするとどちらか1つしか開かなかったり、全く開かなかったりします。
全く別なところで別にShellExecuteを使ってから、もう一度開こうとすると、今度は問題なく開くので、なにかを引きずっていると思うのですが、原因を発見できません。

ここまできたのでなんとかすっきり成功させたく、心苦しいのですが、ご指導頂けませんでしょうか。

    char mych1[1000];
    strcpy(mych1,fullpath); strcat(mych1,"\\Folder1");
    ::ShellExecute( HWND_DESKTOP, "open", "explorer.exe", mych1, NULL, SW_HIDE );
    HWND hWnd1 = ::FindWindowEx( NULL, NULL, NULL, "Folder1" );
    strcpy(mych1,fullpath); strcat(mych1,"\\Folder2");
    ::ShellExecute( HWND_DESKTOP, "open", "explorer.exe", mych1, NULL, SW_HIDE );
    HWND hWnd2 = ::FindWindowEx( NULL, NULL, NULL, "Folder2" );
    const int screenX = ::GetSystemMetrics( SM_CXSCREEN );
    const int screenY = ::GetSystemMetrics( SM_CYSCREEN );
    if ( ::IsWindow( hWnd1 ) )
    {
        ::MoveWindow( hWnd1, 0, 0, screenX / 2, screenY, TRUE );
        ::ShowWindow( hWnd1, SW_SHOW );
        ::SetForegroundWindow( hWnd1 );
    }
    if ( ::IsWindow( hWnd2 ) )
    {
        ::MoveWindow( hWnd2, screenX / 2, 0, screenX / 2, screenY, TRUE );
        ::ShowWindow( hWnd2, SW_SHOW );
        ::SetForegroundWindow( hWnd2 );
    }


Blue  2006-03-27 19:21:10  No: 61018

スイマセン。原因はワカリマセン。
ShellExecuteの戻り値や、HWNDの値を確認してみてデバッグ作業をしてみてください。

追記)
FindWindowExで"ExploreWClass"は指定したほうが良いです。
これで、エクスプローラという風に限定できますので。


dairygoods  2006-03-27 20:12:59  No: 61019

ShellExecute から戻った時点で
すでにウィンドウが表示されているという保証はないと思われます。


Blue  2006-03-27 20:32:46  No: 61020

> すでにウィンドウが表示されているという保証はないと思われます。
ということは、ShellExecuteではその確認は不可能そうですね。

ShellExecuteEx + WaitForInputIdle に変更ですかね。


dairygoods  2006-03-27 21:32:06  No: 61021

> ということは、ShellExecuteではその確認は不可能そうですね。

見つかるまでループでもよいかも。
CWnd* wnd;
while ((wnd=FindWindow(NULL,"Folder1"))==NULL)
 Sleep(100);


とっても初心者  2006-03-28 17:05:20  No: 61022

Blueさん、dairygoodsさん、ありがとうございます。

ShellExecuteの最後の引数を SW_HIDE から SW_SHOW に置き換えたところ、だいたい毎回フォルダが開くようになりました。
昨晩成功して、今朝やりなおしてみたら開かなったのですが、その後、PCから立ち上げなおしても、開いています。

FindWindowExで引数に"ExploreWClass"を置くと、開かなくなります。

while ((wnd=FindWindow(NULL,"Folder1"))==NULL) は、無限ループになりました。

直近で数十回ボタンクリックして、問題なく開いているのですが、まだなんとなく怪しく、もう少し様子をみてみます。


Blue  2006-03-28 17:41:30  No: 61023

> FindWindowExで引数に"ExploreWClass"を置くと、開かなくなります。
というか、FindWindowExを使う意味がなくなったので、
(以前は同じウィンドウタイトルのウィンドウを2つ取得するので必要であった)
FindWindow("ExploreWClass","Folder1")
としてもだめでしょうか?
ダメならば、VCに付属しているSpy++で、エクスプローラのウィンドウクラスを調べてみてください。

> ShellExecuteEx + WaitForInputIdle に変更ですかね。
の例
#include <shlwapi.h>

#pragma comment( lib, "shlwapi.lib" )



void CExplorerTestDlg::OnButton1() 
{
    // TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
    const LPCTSTR dir = TEXT( "C:\\" );
    const LPCTSTR subdir1 = TEXT( "フォルダ1" );
    const LPCTSTR subdir2 = TEXT( "フォルダ2" );

    const int screenX = ::GetSystemMetrics( SM_CXSCREEN );
    const int screenY = ::GetSystemMetrics( SM_CYSCREEN );

    TCHAR path[ MAX_PATH ];
    CWnd *pWnd1, *pWnd2;

    ::PathCombine( path, dir, subdir1 );
    SHELLEXECUTEINFO sei1 = { 0 };
    sei1.cbSize = sizeof( sei1 );
    sei1.nShow  = SW_HIDE;
    sei1.fMask  = SEE_MASK_NOCLOSEPROCESS;
    sei1.lpVerb = TEXT( "explore" );
    sei1.lpFile = path;
    if ( ::ShellExecuteEx( &sei1 ) )
    {
        ::WaitForInputIdle( sei1.hProcess, INFINITE );
        pWnd1 = this->FindWindow( TEXT( "ExploreWClass" ), subdir1 );
    }   
    
    ::PathCombine( path, dir, subdir2 );
    SHELLEXECUTEINFO sei2 = { 0 };
    sei2.cbSize = sizeof( sei2 );
    sei2.nShow  = SW_HIDE;
    sei2.fMask  = SEE_MASK_NOCLOSEPROCESS;
    sei2.lpVerb = TEXT( "explore" );
    sei2.lpFile = path;
    if ( ::ShellExecuteEx( &sei2 ) )
    {
        ::WaitForInputIdle( sei2.hProcess, INFINITE );
        pWnd2 = this->FindWindow( TEXT( "ExploreWClass" ), subdir2 );
    }

    if ( pWnd1 )
    {
        pWnd1->MoveWindow( 0, 0, screenX / 2, screenY );
        pWnd1->ShowWindow( SW_SHOW );
        pWnd1->SetForegroundWindow();
    }
    if ( pWnd2 )
    {
        pWnd2->MoveWindow( screenX / 2, 0, screenX / 2, screenY );
        pWnd2->ShowWindow( SW_SHOW );
        pWnd2->SetForegroundWindow();
    }
}


dairygoods  2006-03-28 18:06:07  No: 61024

エクスプローラのクラス名は、CabinetWClass となる場合もあるようです。


Blue  2006-03-28 18:19:13  No: 61025

> エクスプローラのクラス名は、CabinetWClass となる場合もあるようです。
なるほど。やはりエクスプローラは厄介ですね。

というか、なんかもっといい方法がありそうでならないです。
やっぱりShell32系のCOMを使ったほうがよいのかなぁ、、、
# VBなら、楽チンにできるんだけど。


とっても初心者  2006-03-28 18:48:57  No: 61026

ありがとうございます。
最終的に、下記のようにしました。
Blueさんが最初に提示してくださったコードでいきます。
エクスプローラのクラス名はCabinetWClassでした。

        char mych1[1000];
            const int screenX = ::GetSystemMetrics( SM_CXSCREEN );
            const int screenY = ::GetSystemMetrics( SM_CYSCREEN );

        strcpy(mych1,fullpath); strcat(mych1,"\\Folder1");
        ShellExecute( HWND_DESKTOP, "open", "explorer.exe", mych1, NULL, SW_SHOW );
        strcpy(mych1,fullpath); strcat(mych1,"\\Folder2");
        ShellExecute( HWND_DESKTOP, "open", "explorer.exe", mych1, NULL, SW_SHOW );
        CWnd* pWnd1 = this->FindWindow("CabinetWClass","Folder1");
        CWnd* pWnd2 = this->FindWindow("CabinetWClass","Folder2");
        if (( pWnd1 )&&( pWnd2 )) {
                pWnd1->MoveWindow( 0, 0, screenX / 2, screenY, TRUE );
                pWnd1->ShowWindow( SW_SHOW ); 
                pWnd1->SetForegroundWindow();
                pWnd2->MoveWindow( screenX / 2, 0, screenX / 2, screenY, TRUE  );
                pWnd2->ShowWindow( SW_SHOW ); 
                pWnd2->SetForegroundWindow();
        }


Blue  2006-03-28 19:27:17  No: 61027

> なるほど。やはりエクスプローラは厄介ですね。
ShellExecuteの第2引数が、openかexploreかで、
CabinetWClass または ExploreWClass になるっぽい。(WindowsXP SP2)

通常マウスでクリックして開いたり、Windowsキー+E で開くのは
ExploreWClassでした。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加