コマンドラインC#を使って、ファイルダイアログのカスタマイズを試してみたいと思います。旧来のリソースファイル(.res)を利用してWinAPI越しにごり押しで。
今更こんな事をやる価値も無いのですが、ちょっと引っ掛かったので気の済む所まで。コードは時々刻々変わります。
手元のWindows2000の環境(comdlg32.dll 5.0.3700.6693)でマイドキュメントを展開すると固まる現象を確認していますので、予めご承知おき願います。
別機WinXP Home(comdlg32.dll 6.0.2800.1106)はOK。
尚以下の内容で間違った記述もありますのでご注意願います。
特にC#のintとWin32APIのintを混同していますので。m(_'_)m
\FrameworkSDK\Samples\Technologies\Interop\PlatformInvoke\WinAPIs\CSそのままのコードでも起こります。
かなり試行錯誤しましたが原因不明、古いバージョンのcomdlg32.dllと.NETが相性が悪いということで。
旧来のリソースファイル(.res)を利用して、
ファイルダイアログの一部に登録する子ダイアログテンプレート用リソースファイルを作成します。
当然ネット用のリソースに関する流儀(ResourceManager?)が存在するのですが、
悲しいかな、ちんぷんかんぷん。
輪を掛けて、ここではリソースコンパイラrc.exeにより手作業で行います。
探せばGUIを使ったダイアログ(リソース)エディタも在りますので、そちらの方が便利です。
モーダルダイアログの作成も参照。
仮にフォルダd:\arx2005\proj\cs03を作成してそこで作業します。
メモ帳等のエディタに次のコードを貼付け、上のプロジェクトフォルダにcs03.rcという名前を付けて保存します。
ダイアログとその中のコントロール群の大きさとキャプション等を記述したものです。
尚貼付後windows.hの前後の空白は除去。
#include < windows.h >
#define IDD_DIALOG1 101
#define IDC_EDIT_FIND 1000
#define IDC_EDIT_REPLACE 1001
#define IDC_STATIC_VIEW 1002
#define IDC_STATIC_STATE 1003
#define IDC_STATIC_FIND 1004
#define IDC_STATIC_REPLACE 1005
#define IDC_BUTTON_FIND 1007
#define IDC_BUTTON_REPLACE 1008
#define IDC_LIST_FOUND 1009
#define IDC_TAB 1010
IDD_DIALOG1 DIALOGEX 0, 0, 372, 152
STYLE WS_CHILD | WS_CLIPSIBLINGS
EXSTYLE WS_EX_TRANSPARENT
FONT 9, "MS Pゴシック"
BEGIN
PUSHBUTTON "&Find",IDC_BUTTON_FIND,325,30,31,14
PUSHBUTTON "&Replace",IDC_BUTTON_REPLACE,325,50,30,14
LTEXT "",IDC_STATIC_VIEW,10,30,130,85,SS_SUNKEN
EDITTEXT IDC_EDIT_FIND,195,30,125,12,ES_AUTOHSCROLL
LTEXT "Find &Pattern",IDC_STATIC_FIND,145,32,45,10
EDITTEXT IDC_EDIT_REPLACE,195,50,125,12,ES_AUTOHSCROLL
LTEXT "Replace &String",IDC_STATIC_REPLACE,146,52,46,10
LTEXT "",IDC_STATIC_STATE,10,120,130,20
LISTBOX IDC_LIST_FOUND,150,70,210,75,LBS_SORT | LBS_MULTIPLESEL |
LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_HSCROLL |
WS_TABSTOP
CONTROL "Tab",IDC_TAB,"SysTabControl32",0x0,5,0,360,150
END
コマンドプロンプを起動し、コマンドラインに次の3行を実行します。
コンパイルに成功するとプロジェクトフォルダにコンパイル済みリソースファイルcs03.resが作成されます。
尚後述の修正後のバッチファイルから[R]esourceを選んでコンパイルも可。
d:
cd \arx2005\proj\cs03
rc /l 0x411 /fo cs03.res cs03.rc
\FrameworkSDK\Samples\Technologies\Interop\PlatformInvoke\WinAPIs\CS
フォルダにファイルダイアログをWinAPI風に表示するサンプルプログラムがあります。
この中のOpenFileDlg.csを土台にしてコードを追加、実験します。
要点はOPENFILENAME構造体の中のInstanceメンバにモジュールのハンドルを登録することと、
lpTemplateNameメンバに自家製ダイアログのIDを登録することです。
先に作成したフォルダd:\arx2005\proj\cs03で作業します。
エディタに次のコードを貼付け、プロジェクトフォルダにcs03.csという名前を付けて保存します。
using System;
using System.Windows.Forms;
using System.Reflection;
using System.Runtime.InteropServices;
namespace cs03
{
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Auto)]
public class OPENFILENAME
{ public uint lStructSize=0;
public IntPtr hwndOwner=IntPtr.Zero;
public IntPtr hInstance=IntPtr.Zero;
public string lpstrFilter=null;
public string lpstrCustomFilter=null;
public uint nMaxCustFilter=0;
public uint nFilterIndex=0;
public string lpstrFile=null;
public uint nMaxFile=0;
public string lpstrFileTitle=null;
public uint nMaxFileTitle=0;
public string lpstrInitialDir=null;
public string lpstrTitle=null;
public uint Flags=0;
public ushort nFileOffset=0;
public ushort nFileExtension=0;
public string lpstrDefExt=null;
public uint lCustData=0;
public IntPtr lpfnHook=IntPtr.Zero;
public IntPtr lpTemplateName;
//#if(_WIN32_WINNT >= 0x0500)
public IntPtr pvReserved=IntPtr.Zero;
public uint dwReserved=0;
public uint FlagsEx=0;
}
public class testOpenFileDlg
{ [DllImport("Comdlg32.dll",CharSet=CharSet.Auto)]
public static extern bool GetOpenFileName([In,Out]OPENFILENAME ofn);
public static void Main()
{ const int IDD_DIALOG1=101;
OPENFILENAME ofn=new OPENFILENAME();
ofn.lStructSize=(uint)Marshal.SizeOf(ofn);
ofn.hInstance=Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]);
ofn.lpstrFilter="Drawing files\0*.DWG\0DXF files\0*.dxf\0";
ofn.lpstrFile=new String(new char[260]);
ofn.nMaxFile=(uint)ofn.lpstrFile.Length;
ofn.lpstrFileTitle=new String(new char[64]);
ofn.nMaxFileTitle=(uint)ofn.lpstrFileTitle.Length;
ofn.lpstrInitialDir="C:\\";
ofn.lpstrTitle="Choose one of DWG files";
ofn.Flags=0x4 | 0x40 | 0x80000 | 0x800000; //ゆくゆく整理
ofn.lpTemplateName=new IntPtr(IDD_DIALOG1);
GetOpenFileName(ofn);
}
}
}
旧来のリソース(.res)やWinAPIを使う関係で
前回作成したバッチファイルd:\arx2005\proj\cpl.batを次の様に修正します。
@echo off
rem 次の3行はインストールフォルダに合わせて要書替
d:
cd \arx2005\proj\%1
start d:\program\hidemaru\hidemaru.exe %1.cs
:DO
choice /C:CQ Resource,Compile,Exec,Quit
if errorlevel 4 goto QUIT
if errorlevel 3 goto EXECUTE
if errorlevel 2 goto COMPILE
if errorlevel 1 goto RCOMPILE
goto DO
:RCOMPILE
rc /l 0x411 /fo %1.res %1.rc
if errorlevel 1 goto ERR
goto DO
:COMPILE
rem 次のacdbmgd.dll,acmgd.dllのパスd:\ACAD2005はインストールフォルダに合わせて要書替
rem csc /nologo /target:library /reference:acdbmgd.dll,acmgd.dll /lib:d:\ACAD2005 %1.cs
csc /nologo /win32res:%1.res %1.cs
if errorlevel 1 goto ERR
goto DO
:EXECUTE
%1
goto DO
:ERR
echo =========エラー
goto DO
:QUIT
バッチファイルcpl.batのショートカットを右クリック、プロパティから
リンク先をd:\arx2005\proj\cpl.bat 03csと引数に加えて記述、OK後ダブルクリックで起動。
コマンドプロンプトが起動し、Resource,Compile,Exec,Quit[R,C,E,Q]?と聞いてきますので[C]を選択します。
コンパイルに成功すれば[Q]を押してバッチプログラムを一旦終了します。
大抵コンパイルは一発で通りませんので、常に03.csを開いたままでソース修正上書き、コンパイルを成功するまで繰り返すことになります。
コンパイルが正常に終了するとcs03.exeがプロジェクトフォルダに新たに作成されていると思います。
コマンドラインのResource,Compile,Exec,Quit[R,C,E,Q]?から[E]を選択してcs03.exeを実行します。
プレースバーの付いたお馴染みのダイアログの下方に、先程の自家製のダイアログが含まれて表示されればめでたし。
これ以上は何も起こりませんので、キャンセルかシステムメニューの閉じるを。
ファイルダイアログにドッキングした自家製ダイアログの各コントロールの応答関数のアウトラインを作成します。
要点は親ダイアログから呼ばれるフック関数を書き、OPENFILENAME構造体の中のlpfnHookメンバにこの関数のアドレスを登録することです。
実際はWinAPI関数の引数の受け渡しにかなりのテクニックと試行錯誤が必要です。
文字列の扱いが事の他難しそうです。
Stringの代わりにをStringBuilderを使いたかったのですが、これは上手く行きませんでした。
先に作成したフォルダd:\arx2005\proj\cs03で作業します。
cs03.csを次のコードに一式変更します。
using System;
using System.Text; //for StringBuilder
using System.Windows.Forms; //for MessageBox
using System.Reflection; //for Assembly
using System.Runtime.InteropServices;
namespace cs03
{
public class custFileDlg
{
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Auto)]
public class OPENFILENAME
{ public uint lStructSize=0;
public IntPtr hwndOwner=IntPtr.Zero;
public IntPtr hInstance=IntPtr.Zero;
public string lpstrFilter=null;
public string lpstrCustomFilter=null;
public uint nMaxCustFilter=0;
public uint nFilterIndex=0;
public string lpstrFile=null;
public uint nMaxFile=0;
public string lpstrFileTitle=null;
public uint nMaxFileTitle=0;
public string lpstrInitialDir=null;
public string lpstrTitle=null;
public uint Flags=0;
public ushort nFileOffset=0;
public ushort nFileExtension=0;
public string lpstrDefExt=null;
public uint lCustData=0;
public OFNHookProc lpfnHook;
public IntPtr lpTemplateName;
//#if(_WIN32_WINNT >= 0x0500)
public IntPtr pvReserved=IntPtr.Zero;
public uint dwReserved=0;
public uint FlagsEx=0;
}
public const uint OFN_ALLOWMULTISELECT=0x200;
public const uint OFN_CREATEPROMPT=0x2000;
public const uint OFN_ENABLEHOOK=0x20;
public const uint OFN_ENABLETEMPLATE=0x40;
public const uint OFN_ENABLETEMPLATEHANDLE=0x80;
public const uint OFN_ENABLEINCLUDENOTIFY=0x00400000;
public const uint OFN_ENABLESIZING=0x00800000;
public const uint OFN_EXPLORER=0x80000;
public const uint OFN_EXTENSIONDIFFERENT=0x400;
public const uint OFN_FILEMUSTEXIST=0x1000;
public const uint OFN_HIDEREADONLY=0x4;
public const uint OFN_LONGNAMES=0x200000;
public const uint OFN_NOCHANGEDIR=0x8;
public const uint OFN_NODEREFERENCELINKS=0x100000;
public const uint OFN_NOLONGNAMES=0x40000;
public const uint OFN_NONETWORKBUTTON=0x20000;
public const uint OFN_NOREADONLYRETURN=0x8000;
public const uint OFN_NOTESTFILECREATE=0x10000;
public const uint OFN_NOVALIDATE=0x100;
public const uint OFN_DONTADDTORECENT=0x02000000;
public const uint OFN_OVERWRITEPROMPT=0x2;
public const uint OFN_PATHMUSTEXIST=0x800;
public const uint OFN_READONLY=0x1;
public const uint OFN_SHAREAWARE=0x4000;
public const uint OFN_SHAREFALLTHROUGH=2;
public const uint OFN_SHARENOWARN=1;
public const uint OFN_SHAREWARN=0;
public const uint OFN_SHOWHELP=0x10;
public const uint CDN_FIRST=unchecked(0U-601U);
public const uint CDN_INITDONE=CDN_FIRST;
public const uint CDN_SELCHANGE=CDN_FIRST-0x0001;
public const uint CDN_FOLDERCHANGE=CDN_FIRST-0x0002;
public const uint CDN_SHAREVIOLATION=CDN_FIRST-0x0003;
public const uint CDN_HELP=CDN_FIRST-0x0004;
public const uint CDN_FILEOK=CDN_FIRST-0x0005;
public const uint CDN_TYPECHANGE=CDN_FIRST-0x0006;
public const uint CDN_INCLUDEITEM=CDN_FIRST-0x0007;
public const int WM_USER=0x0400;
public const int CDM_FIRST=WM_USER+100;
public const int CDM_GETSPEC=CDM_FIRST+0x0000;
public const int CDM_GETFILEPATH=CDM_FIRST+0x0001;
public const int CDM_GETFOLDERPATH=CDM_FIRST+0x0002;
public const int CDM_GETFOLDERIDLIST=CDM_FIRST+0x0003;
public const int CDM_SETCONTROLTEXT=CDM_FIRST+0x0004;
public const int CDM_HIDECONTROL=CDM_FIRST+0x0005;
public const int CDM_SETDEFEXT=CDM_FIRST+0x0006;
[StructLayout(LayoutKind.Sequential)]
public struct NMHDR
{ public IntPtr hwndFrom;
public uint idFrom;
public uint code;
}
public const int WM_COMMAND=0x0111;
public const int WM_SETFOCUS=0x0007;
public const int WM_INITDIALOG=0x0110;
public const int WM_LBUTTONDOWN=0x0201;
public const int WM_RBUTTONDOWN=0x0204;
public const int WM_MOVE=0x0003;
public const int WM_NOTIFY=0x004e;
public const int LB_RESETCONTENT=0x184;
public const int LB_ADDSTRING=0x180;
public const int LB_GETSEL=0x187;
public const int LB_SETHORIZONTALEXTENT=0x193;
public const int LB_GETCOUNT=0x18b;
public const int LB_SETCURSEL=0x186;
public const int LB_GETCURSEL=0x188;
public const int LB_GETTEXT=0x189;
public const int LB_GETTEXTLEN=0x18a;
public const int LB_FINDSTRING=0x18f;
public const int LB_ERR=-1;
public const int IDOK=1;
public const int IDCANCEL=2;
public const int IDC_EDIT_FIND=1000;
public const int IDC_EDIT_REPLACE=1001;
public const int IDC_STATIC_VIEW=1002;
public const int IDC_STATIC_STATE=1003;
public const int IDC_STATIC_FIND=1004;
public const int IDC_STATIC_REPLACE=1005;
public const int IDC_BUTTON_FIND=1007;
public const int IDC_BUTTON_REPLACE=1008;
public const int IDC_LIST_FOUND=1009;
public const int IDC_TAB=1010;
[DllImport( "Comdlg32.dll",CharSet=CharSet.Auto)]public static extern bool GetOpenFileName([ In, Out ] OPENFILENAME ofn );
[DllImport("User32")]public static extern IntPtr GetParent(IntPtr hWnd);
[DllImport("User32",CharSet=CharSet.Auto)]public static extern uint SetDlgItemText(IntPtr hDlg,int nIDDlgItem,string lpString);
[DllImport("User32",CharSet=CharSet.Auto)]public static extern uint GetDlgItemText(IntPtr hDlg,int nIDDlgItem,[MarshalAs(UnmanagedType.LPTStr)]string lpString,int nMaxCount);
[DllImport("User32",CharSet=CharSet.Auto)]public static extern uint GetDlgItemText(IntPtr hDlg,int nIDDlgItem,[MarshalAs(UnmanagedType.LPTStr)]StringBuilder lpString,int nMaxCount);
[DllImport("User32",CharSet=CharSet.Auto)]public static extern IntPtr GetDlgItem(IntPtr hDlg,int nIDDlgItem);
[DllImport("User32",CharSet=CharSet.Auto)]public static extern int SendMessage(IntPtr hWnd,uint Msg,uint wParam,uint lParam);
[DllImport("User32",CharSet=CharSet.Auto)]public static extern int SendMessage(IntPtr hWnd,uint Msg,uint wParam,[MarshalAs(UnmanagedType.LPTStr)]string lParam);
[DllImport("User32",CharSet=CharSet.Auto)]public static extern int SendMessage(IntPtr hWnd,uint Msg,uint wParam,[MarshalAs(UnmanagedType.LPTStr)]StringBuilder lParam);
public const int DWL_MSGRESULT=0;
[DllImport("User32",CharSet=CharSet.Auto)]public static extern int SetWindowLong(IntPtr hWnd,int nIndex,int dwNewLong);
[DllImport("User32",CharSet=CharSet.Auto)]public static extern int GetWindowLong(IntPtr hWnd,int nIndex);
public custFileDlg(){}
public delegate uint OFNHookProc(IntPtr hdlg,uint uiMsg,uint wParam,uint lParam);
public const int MAX_PATH=260;
public const int FILES=128;
public static OPENFILENAME ofn=null;
protected virtual uint hookProc(IntPtr hdlg,uint uiMsg,uint wParam,uint lParam)
{ switch(uiMsg)
{ case WM_INITDIALOG:
ofn=(OPENFILENAME)Marshal.PtrToStructure(new IntPtr(lParam),typeof(OPENFILENAME));
MessageBox.Show(ofn.lpstrTitle);
SendMessage(GetDlgItem(hdlg,IDC_LIST_FOUND),LB_RESETCONTENT,0,0);
return 1;
case WM_COMMAND:
switch(wParam)
{ case IDC_BUTTON_FIND:
case IDC_BUTTON_REPLACE:
string findPattern=new string(new char[MAX_PATH]);
string replaceString=new string(new char[MAX_PATH]);
if(0 < GetDlgItemText(hdlg,IDC_EDIT_FIND,findPattern,findPattern.Length))
MessageBox.Show(findPattern);
if(0 < GetDlgItemText(hdlg,IDC_EDIT_FIND,findPattern,findPattern.Length)
&& 0!=GetDlgItemText(hdlg,IDC_EDIT_REPLACE,replaceString,replaceString.Length))
{ char sep='\0'; //string strSep="\",; \t";char[] seps=sepstr.ToCharArray();
MessageBox.Show("Replace "+findPattern.Split(sep)[0]+" by "+replaceString.Split(sep)[0] + "?");
}
return 1;
}
break;
case WM_NOTIFY:
NMHDR hdr=(NMHDR)Marshal.PtrToStructure(new IntPtr(lParam),typeof(NMHDR));
switch((uint)hdr.code)
{ case CDN_INITDONE:
SendMessage(GetParent(hdlg),CDM_HIDECONTROL,IDOK,0);
SetDlgItemText(GetParent(hdlg),IDCANCEL,"&Quit");
SendMessage(GetDlgItem(hdlg,IDC_LIST_FOUND),LB_ADDSTRING,0,"CDN_INITDONE");
return 1;
case CDN_FILEOK:
SetWindowLong(hdlg,DWL_MSGRESULT,-1); //for [ENTER]
return 1;
case CDN_SHAREVIOLATION:
SendMessage(GetDlgItem(hdlg,IDC_LIST_FOUND),LB_ADDSTRING,0,"CDN_SHAREVIOLATION");
break;
case CDN_FOLDERCHANGE:
string path=new string(new char[MAX_PATH]);
SendMessage(GetParent(hdlg),CDM_GETFOLDERPATH,(uint)path.Length,path);
SendMessage(GetDlgItem(hdlg,IDC_LIST_FOUND),LB_ADDSTRING,0,"CDN_FOLDERCHANGE"+path);
return 1;
case CDN_SELCHANGE:
//StringBuilder sb=new StringBuilder(); //too difficult
string file=new string(new char[MAX_PATH*FILES]);
//if(0 < SendMessage(GetParent(hdlg),CDM_GETSPEC,(uint)file.Length,file))
if(0 < SendMessage(GetParent(hdlg),CDM_GETFILEPATH,(uint)file.Length,file))
SetDlgItemText(hdlg,IDC_STATIC_STATE,file);
break;
}
break;
}
return 0;
}
public string strInitialDir="C:\\";
public string strTitle="Choose some files";
string strFilter="";
uint dwFlags=0;
public string Filter
{ get{return strFilter;}
set{strFilter=value;}
}
public uint Flags
{ get{return dwFlags;}
set{dwFlags=value;}
}
public DialogResult ShowDialog()
{ const int IDD_DIALOG1=101;
OPENFILENAME ofn=new OPENFILENAME();
ofn.lStructSize=(uint)Marshal.SizeOf(ofn);
ofn.hInstance=Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]);
ofn.lpstrFilter=strFilter;
ofn.lpstrFile=new String(new char[MAX_PATH*FILES]);
ofn.nMaxFile=(uint)ofn.lpstrFile.Length;
// ofn.lpstrFileTitle=new String(new char[MAX_PATH]);
// ofn.nMaxFileTitle=(uint)ofn.lpstrFileTitle.Length;
ofn.lpstrInitialDir=strInitialDir;
ofn.lpstrTitle=strTitle;
ofn.Flags=dwFlags;
ofn.lpfnHook=new OFNHookProc(hookProc);
ofn.lpTemplateName=new IntPtr(IDD_DIALOG1);
if(GetOpenFileName(ofn))return DialogResult.OK;
else return DialogResult.Cancel;
}
}
public class TestDialog
{
public static void Main()
{
custFileDlg cfd=new custFileDlg();
cfd.Filter="DWG(*.dwg)\0*.dwg\0DXF(*.dxf)\0*.dxf\0";
cfd.Flags=custFileDlg.OFN_DONTADDTORECENT |
custFileDlg.OFN_NONETWORKBUTTON |
custFileDlg.OFN_SHAREFALLTHROUGH |
custFileDlg.OFN_EXPLORER |
custFileDlg.OFN_LONGNAMES |
custFileDlg.OFN_PATHMUSTEXIST |
custFileDlg.OFN_ENABLEHOOK |
custFileDlg.OFN_ENABLETEMPLATE |
custFileDlg.OFN_ENABLESIZING |
custFileDlg.OFN_ALLOWMULTISELECT;
cfd.ShowDialog();
}
}
}
バッチファイルcpl.batのショートカットを右クリック、プロパティから
リンク先をd:\arx2005\proj\cpl.bat 03csと引数に加えて記述、OK後ダブルクリックで起動。
コマンドプロンプトが起動し、Resource,Compile,Exec,Quit[R,C,E,Q]?と聞いてきますので[C]を選択します。
コンパイルに成功すれば[Q]を押してバッチプログラムを一旦終了します。
コンパイルが正常に終了するとcs03.exeがプロジェクトフォルダに新たに作成されていると思います。
コマンドラインのResource,Compile,Exec,Quit[R,C,E,Q]?から[E]を選択してcs03.exeを実行します。
色々ボタンを押してお試しを。