// Copyright (c) 2006, Gustavo Franco
// Copyright � Decebal Mihailescu 2007
// Email: dmihailescu@hotmail.com
// Email: gustavo_franco@hotmail.com
// All rights reserved.
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE. IT CAN BE DISTRIBUTED FREE OF CHARGE AS LONG AS THIS HEADER
// REMAINS UNCHANGED.
using System;
using System.IO;
using System.Text;
using System.Data;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using System.ComponentModel;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Drawing.Drawing2D;
using Win32Types;
namespace FileDialogExtenders
{
public partial class FileDialogControlBase
{
#region Helper Classes
private class NativeFileDialogWrapper : NativeWindow, IDisposable
{
public const SetWindowPosFlags UFLAGSSIZE =
SetWindowPosFlags.SWP_NOACTIVATE |
SetWindowPosFlags.SWP_NOOWNERZORDER |
SetWindowPosFlags.SWP_NOMOVE;
#region Delegates
#endregion
#region Events
#endregion
#region Variables Declaration
private int _filterIndex;
private FileDialogControlBase _CustomCtrl;
#endregion
#region Constructors
public NativeFileDialogWrapper(FileDialogControlBase fd)
{
_CustomCtrl = fd;
if (_CustomCtrl != null)
fd.MSDialog.Disposed += new EventHandler(NativeDialogWrapper_Disposed);
}
void NativeDialogWrapper_Disposed(object sender, EventArgs e)
{
Dispose();
}
#endregion
#region Methods
public void Dispose()
{
//ReleaseHandle();
if (_CustomCtrl != null && _CustomCtrl.MSDialog != null)
{
_CustomCtrl.MSDialog.Disposed -= new EventHandler(NativeDialogWrapper_Disposed);
_CustomCtrl.MSDialog.Dispose();
if (_CustomCtrl != null)
_CustomCtrl.MSDialog = null;
}
if (_CustomCtrl != null)
{
_CustomCtrl.Dispose();
_CustomCtrl = null;
}
DestroyHandle();
}
#endregion
#region Overrides
protected override void WndProc(ref Message m)
{
switch ((Msg)m.Msg)
{
case Msg.WM_NOTIFY:
OFNOTIFY ofNotify = (OFNOTIFY)Marshal.PtrToStructure(m.LParam, typeof(OFNOTIFY));
switch (ofNotify.hdr.code)
{
case (uint)DialogChangeStatus.CDN_SELCHANGE:
{
StringBuilder filePath = new StringBuilder(256);
NativeMethods.SendMessage(new HandleRef(this, NativeMethods.GetParent(Handle)), (uint)DialogChangeProperties.CDM_GETFILEPATH, (IntPtr)256, filePath);
if (_CustomCtrl != null)
_CustomCtrl.OnFileNameChanged(this, filePath.ToString());
}
break;
case (uint)DialogChangeStatus.CDN_FOLDERCHANGE:
{
StringBuilder folderPath = new StringBuilder(256);
NativeMethods.SendMessage(new HandleRef(this, NativeMethods.GetParent(Handle)), (int)DialogChangeProperties.CDM_GETFOLDERPATH, (IntPtr)256, folderPath);
if (_CustomCtrl != null)
_CustomCtrl.OnFolderNameChanged(this, folderPath.ToString());
}
break;
case (uint)DialogChangeStatus.CDN_TYPECHANGE:
{
OPENFILENAME ofn = (OPENFILENAME)Marshal.PtrToStructure(ofNotify.OpenFileName, typeof(OPENFILENAME));
int i = ofn.nFilterIndex;
if (_CustomCtrl != null && _filterIndex != i)
{
_filterIndex = i;
_CustomCtrl.OnFilterChanged(this as IWin32Window, i);
}
}
break;
case (uint)DialogChangeStatus.CDN_INITDONE:
break;
case (uint)DialogChangeStatus.CDN_SHAREVIOLATION:
break;
case (uint)DialogChangeStatus.CDN_HELP:
break;
case (uint)DialogChangeStatus.CDN_INCLUDEITEM:
break;
case (uint)DialogChangeStatus.CDN_FILEOK://0xfffffda2:
#pragma warning disable 1690, 0414
NativeMethods.SetWindowPos(_CustomCtrl._hFileDialogHandle, IntPtr.Zero,
(int)_CustomCtrl._OpenDialogWindowRect.left,
(int)_CustomCtrl._OpenDialogWindowRect.top,
(int)_CustomCtrl._OpenDialogWindowRect.Width,
(int)_CustomCtrl._OpenDialogWindowRect.Height,
FileDialogControlBase.NativeFileDialogWrapper.UFLAGSSIZE);
break;
#pragma warning restore 1690, 0414
default:
break;
}
break;
case Msg.WM_COMMAND:
switch (NativeMethods.GetDlgCtrlID(m.LParam))//switch (m.WParam & 0x0000ffff)
{
case (int)ControlsId.ButtonOk://OK
break;
case (int)ControlsId.ButtonCancel://Cancel
break;
case (int)ControlsId.ButtonHelp: //0x0000040e://help
break;
}
break;
}
base.WndProc(ref m);
}
#endregion
}
private class DialogWrapper<FDLG> : NativeWindow, IDisposable
where FDLG : FileDialog, new()
{
#region Constants Declaration
private const SetWindowPosFlags UFLAGSSIZEEX =
SetWindowPosFlags.SWP_NOACTIVATE |
SetWindowPosFlags.SWP_NOOWNERZORDER |
SetWindowPosFlags.SWP_NOMOVE |
SetWindowPosFlags.SWP_ASYNCWINDOWPOS |
SetWindowPosFlags.SWP_DEFERERASE;
private const SetWindowPosFlags UFLAGSMOVE =
SetWindowPosFlags.SWP_NOACTIVATE |
SetWindowPosFlags.SWP_NOOWNERZORDER |
SetWindowPosFlags.SWP_NOSIZE;
private const SetWindowPosFlags UFLAGSHIDE =
SetWindowPosFlags.SWP_NOACTIVATE |
SetWindowPosFlags.SWP_NOOWNERZORDER |
SetWindowPosFlags.SWP_NOMOVE |
SetWindowPosFlags.SWP_NOSIZE |
SetWindowPosFlags.SWP_HIDEWINDOW;
private const SetWindowPosFlags UFLAGSZORDER =
SetWindowPosFlags.SWP_NOACTIVATE |
SetWindowPosFlags.SWP_NOMOVE |
SetWindowPosFlags.SWP_NOSIZE;
const uint WS_VISIBLE = 0x10000000;
static readonly IntPtr HWND_MESSAGE = new IntPtr(-3);
static readonly IntPtr NULL = IntPtr.Zero;
#endregion
#region Variables Declaration
IntPtr _hDummyWnd = NULL;
private FileDialogControlBase _CustomControl = null;
private bool _WatchForActivate = false;
private Size mOriginalSize;
private IntPtr _hFileDialogHandle;
private WINDOWINFO _ListViewInfo;
private NativeFileDialogWrapper _BaseDialogNative;
private IntPtr _ComboFolders;
private WINDOWINFO _ComboFoldersInfo;
private IntPtr _hGroupButtons;
private WINDOWINFO _GroupButtonsInfo;
private IntPtr _hComboFileName;
private WINDOWINFO _ComboFileNameInfo;
private IntPtr _hComboExtensions;
private WINDOWINFO _ComboExtensionsInfo;
IntPtr _hOKButton;
WINDOWINFO _OKButtonInfo;
private IntPtr _hCancelButton;
private WINDOWINFO _CancelButtonInfo;
private IntPtr _hHelpButton;
private WINDOWINFO _HelpButtonInfo;
private IntPtr _hToolBarFolders;
private WINDOWINFO _ToolBarFoldersInfo;
private IntPtr _hLabelFileName;
private WINDOWINFO _LabelFileNameInfo;
private IntPtr _hLabelFileType;
private WINDOWINFO _LabelFileTypeInfo;
private IntPtr _hChkReadOnly;
private WINDOWINFO _ChkReadOnlyInfo;
private bool mIsClosing = false;
private bool mInitializated = false;
private RECT _DialogWindowRect = new RECT();
private RECT _DialogClientRect = new RECT();
#endregion
#region Constructors
public DialogWrapper(FileDialogControlBase fileDialogEx)
{
//create the FileDialog & custom control without UI yet
_CustomControl = fileDialogEx;
_CustomControl.MSDialog = new FDLG();
AssignDummyWindow();
_WatchForActivate = true;
}
#endregion
#region Events
#endregion
#region Methods
private void AssignDummyWindow()
{
//_hDummyWnd = Win32.CreateWindowEx(0x00050100, "Message", null, 0x16C80000, -10000, -10000, 0, 0,
//parent.Handle, NULL, NULL, NULL);
_hDummyWnd = NativeMethods.CreateWindowEx(0, "Message", null, WS_VISIBLE, 0, 0, 0, 0,
HWND_MESSAGE, NULL, NULL, NULL);
if (_hDummyWnd == NULL || !NativeMethods.IsWindow(_hDummyWnd))
throw new ApplicationException("Unable to create a dummy window");
AssignHandle(_hDummyWnd);
}
public void Dispose()
{
//ReleaseHandle();
if (_CustomControl != null &&_CustomControl.MSDialog != null)
{
_CustomControl.MSDialog.Disposed -= new EventHandler(DialogWrappper_Disposed);
_CustomControl.MSDialog.Dispose();
_CustomControl.MSDialog = null;
}
if (_CustomControl!= null)
{
_CustomControl.Disposed -= new EventHandler(DialogWrappper_Disposed);
_CustomControl.Dispose();
_CustomControl = null;
}
if (_BaseDialogNative != null)
{
_BaseDialogNative.Dispose();
_BaseDialogNative = null;
}
NativeMethods.DestroyWindow(_hDummyWnd);
DestroyHandle();
}
#endregion
#region Private Methods
private void PopulateWindowsHandlers()
{
NativeMethods.EnumChildWindows(_hFileDialogHandle, new NativeMethods.EnumWindowsCallBack(FileDialogEnumWindowCallBack), 0);
}
private bool FileDialogEnumWindowCallBack(IntPtr hwnd, int lParam)
{
StringBuilder className = new StringBuilder(256);
NativeMethods.GetClassName(hwnd, className, className.Capacity);
int controlID = NativeMethods.GetDlgCtrlID(hwnd);
WINDOWINFO windowInfo;
NativeMethods.GetWindowInfo(hwnd, out windowInfo);
// Dialog Window
if (className.ToString().StartsWith("#32770"))
{
_BaseDialogNative = new NativeFileDialogWrapper(_CustomControl);
_BaseDialogNative.AssignHandle(hwnd);
return true;
}
switch ((ControlsId)controlID)
{
case ControlsId.DefaultView:
_CustomControl._hListViewPtr = hwnd;
NativeMethods.GetWindowInfo(hwnd, out _ListViewInfo);
_CustomControl.UpdateListView();
break;
case ControlsId.ComboFolder:
_ComboFolders = hwnd;
_ComboFoldersInfo = windowInfo;
break;
case ControlsId.ComboFileType:
_hComboExtensions = hwnd;
_ComboExtensionsInfo = windowInfo;
break;
case ControlsId.ComboFileName:
if (className.ToString().ToLower() == "comboboxex32")
{
_hComboFileName = hwnd;
_ComboFileNameInfo = windowInfo;
}
break;
case ControlsId.GroupFolder:
_hGroupButtons = hwnd;
_GroupButtonsInfo = windowInfo;
break;
case ControlsId.LeftToolBar:
_hToolBarFolders = hwnd;
_ToolBarFoldersInfo = windowInfo;
break;
case ControlsId.ButtonOk:
_hOKButton = hwnd;
_OKButtonInfo = windowInfo;
_CustomControl._hOKButton = hwnd;
//Win32Types.NativeMethods.EnableWindow(_hOKButton, false);
break;
case ControlsId.ButtonCancel:
_hCancelButton = hwnd;
_CancelButtonInfo = windowInfo;
break;
case ControlsId.ButtonHelp:
_hHelpButton = hwnd;
_HelpButtonInfo = windowInfo;
break;
case ControlsId.CheckBoxReadOnly:
_hChkReadOnly = hwnd;
_ChkReadOnlyInfo = windowInfo;
break;
case ControlsId.LabelFileName:
_hLabelFileName = hwnd;
_LabelFileNameInfo = windowInfo;
break;
case ControlsId.LabelFileType:
_hLabelFileType = hwnd;
_LabelFileTypeInfo = windowInfo;
break;
}
return true;
}
private void InitControls()
{
mInitializated = true;
// Lets get information about the current open dialog
NativeMethods.GetClientRect(_hFileDialogHandle, ref _DialogClientRect);
NativeMethods.GetWindowRect(_hFileDialogHandle, ref _DialogWindowRect);
// Lets borrow the Handles from the open dialog control
PopulateWindowsHandlers();
switch (_CustomControl.FileDlgStartLocation)
{
case AddonWindowLocation.Right:
// Now we transfer the control to the open dialog
_CustomControl.Location = new Point((int)(_DialogClientRect.Width - _CustomControl.Width), 0);
break;
case AddonWindowLocation.Bottom:
// Now we transfer the control to the open dialog
_CustomControl.Location = new Point(0, (int)(_DialogClientRect.Height - _CustomControl.Height));
break;
case AddonWindowLocation.BottomRight:
// We don't have to do too much in this case, just the default thing
_CustomControl.Location = new Point((int)(_DialogClientRect.Width - _CustomControl.Width), (int)(_DialogClientRect.Height - _CustomControl.Height));
break;
}
// Everything is ready, now lets change the parent
NativeMethods.SetParent(_CustomControl.Handle, _hFileDialogHandle);
// Send the control to the back
NativeMethods.SetWindowPos(_CustomControl.Handle, (IntPtr)ZOrderPos.HWND_BOTTOM, 0, 0, 0, 0, UFLAGSZORDER);
_CustomControl.MSDialog.Disposed += new EventHandler(DialogWrappper_Disposed);
}
void DialogWrappper_Disposed(object sender, EventArgs e)
{
Dispose();
}
#endregion
#region Overrides
protected override void WndProc(ref Message m)
{
switch ((Msg)m.Msg)
{
case Msg.WM_SHOWWINDOW:
mInitializated = true;
InitControls();
break;
case Msg.WM_SIZING:
RECT currentSize = new RECT();
NativeMethods.GetClientRect(_hFileDialogHandle, ref currentSize);
switch (_CustomControl.FileDlgStartLocation)
{
case AddonWindowLocation.Right:
if (currentSize.Height != _CustomControl.Height)
NativeMethods.SetWindowPos(_CustomControl.Handle, (IntPtr)ZOrderPos.HWND_BOTTOM, 0, 0, (int)_CustomControl.Width, (int)currentSize.Height, UFLAGSSIZEEX);
break;
case AddonWindowLocation.Bottom:
if (currentSize.Height != _CustomControl.Height)
NativeMethods.SetWindowPos(_CustomControl.Handle, (IntPtr)ZOrderPos.HWND_BOTTOM, 0, 0, (int)currentSize.Width, (int)_CustomControl.Height, UFLAGSSIZEEX);
break;
case AddonWindowLocation.BottomRight:
if (currentSize.Width != _CustomControl.Width || currentSize.Height != _CustomControl.Height)
NativeMethods.SetWindowPos(_CustomControl.Handle, (IntPtr)ZOrderPos.HWND_BOTTOM, (int)currentSize.Width, (int)currentSize.Height, (int)currentSize.Width, (int)currentSize.Height, UFLAGSSIZEEX);
break;
}
break;
case Msg.WM_WINDOWPOSCHANGING:
if (!mIsClosing)
{
if (!mInitializated)
{
// Resize OpenDialog to make fit our extra form
WINDOWPOS pos = (WINDOWPOS)Marshal.PtrToStructure(m.LParam, typeof(WINDOWPOS));
if (pos.flags != 0 && ((pos.flags & (int)SWP_Flags.SWP_NOSIZE) != (int)SWP_Flags.SWP_NOSIZE))
{
switch (_CustomControl.FileDlgStartLocation)
{
case AddonWindowLocation.Right:
mOriginalSize = new Size(pos.cx, pos.cy);
pos.cx += _CustomControl.Width;
Marshal.StructureToPtr(pos, m.LParam, true);
currentSize = new RECT();
NativeMethods.GetClientRect(_hFileDialogHandle, ref currentSize);
_CustomControl.Height = (int)currentSize.Height;
break;
case AddonWindowLocation.Bottom:
mOriginalSize = new Size(pos.cx, pos.cy);
pos.cy += _CustomControl.Height;
Marshal.StructureToPtr(pos, m.LParam, true);
currentSize = new RECT();
NativeMethods.GetClientRect(_hFileDialogHandle, ref currentSize);
_CustomControl.Width = (int)currentSize.Width;
break;
case AddonWindowLocation.BottomRight:
mOriginalSize = new Size(pos.cx, pos.cy);
pos.cy += _CustomControl.Height;
pos.cx += _CustomControl.Width;
Marshal.StructureToPtr(pos, m.LParam, true);
break;
}
}
}
}
break;
case Msg.WM_IME_NOTIFY:
if (m.WParam == (IntPtr)ImeNotify.IMN_CLOSESTATUSWINDOW)
{
mIsClosing = true;
NativeMethods.SetWindowPos(_hFileDialogHandle, IntPtr.Zero, 0, 0, 0, 0, UFLAGSHIDE);
NativeMethods.GetWindowRect(_hFileDialogHandle, ref _DialogWindowRect);
NativeMethods.SetWindowPos(_hFileDialogHandle, IntPtr.Zero,
(int)(_DialogWindowRect.left),
(int)(_DialogWindowRect.top),
(int)(mOriginalSize.Width),
(int)(mOriginalSize.Height),
FileDialogControlBase.NativeFileDialogWrapper.UFLAGSSIZE);
}
break;
case Msg.WM_PAINT:
break;
case Msg.WM_NCCREATE:
break;
case Msg.WM_CREATE:
break;
case Msg.WM_ACTIVATE:
if (_WatchForActivate && !mIsClosing && m.Msg == (int)Msg.WM_ACTIVATE)//WM_NCACTIVATE works too
{ //Now the Open/Save Dialog is visible and about to enter the modal loop
_WatchForActivate = false;
//Now we save the real dialog window handle
_hFileDialogHandle = m.LParam;
ReleaseHandle();//release the dummy window
AssignHandle(_hFileDialogHandle);//assign the native open file handle to grab the messages
#pragma warning disable 0197, 0414
NativeMethods.GetWindowRect(_hFileDialogHandle, ref _CustomControl._OpenDialogWindowRect);
#pragma warning restore 0197, 0414
_CustomControl._hFileDialogHandle = _hFileDialogHandle;
}
break;
case Msg.WM_COMMAND:
switch(NativeMethods.GetDlgCtrlID(m.LParam))
{
case (int)ControlsId.ButtonOk://OK
break;
case (int)ControlsId.ButtonCancel://Cancel
#pragma warning disable 1690, 0414
NativeMethods.SetWindowPos(_CustomControl._hFileDialogHandle, IntPtr.Zero,
(int)_CustomControl._OpenDialogWindowRect.left,
(int)_CustomControl._OpenDialogWindowRect.top,
(int)_CustomControl._OpenDialogWindowRect.Width,
(int)_CustomControl._OpenDialogWindowRect.Height,
FileDialogControlBase.NativeFileDialogWrapper.UFLAGSSIZE);
#pragma warning restore 1690, 0414
break;
case (int)ControlsId.ButtonHelp://help
break;
case 0:
break;
default:
break;
}//switch(NativeMethods.GetDlgCtrlID(m.LParam)) ends
break;
default:
break;
}//switch ((Msg)m.Msg) ends
base.WndProc(ref m);
}
#endregion
#region Properties
#endregion
}
#endregion
}
#region Enums
public enum AddonWindowLocation
{
BottomRight = 0,
Right = 1,
Bottom = 2
}
internal enum ControlsId :int
{
ButtonOk = 0x1,
ButtonCancel = 0x2,
ButtonHelp = 0x40E,//0x0000040e
GroupFolder = 0x440,
LabelFileType = 0x441,
LabelFileName = 0x442,
LabelLookIn = 0x443,
DefaultView = 0x461,
LeftToolBar = 0x4A0,
ComboFileName = 0x47c,
ComboFileType = 0x470,
ComboFolder = 0x471,
CheckBoxReadOnly = 0x410
}
#endregion
}