I want to have an custom OpenFileDialog form within my project where I can add/remove buttons and customize whatever I want like a normal form. This is mainly so that it fits into the theme I am using, additionally, I can add custom buttons. Is there any tutorials on how I could construct my own? Are there any pre-existing projects I can use straight out of the download?
I hope that will fit your requirements:
You will need one TreeView and an ImageList
Code
You will need System.Runtime.InteropServices;
And following code to get the associated icon from the path:
[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
public IntPtr hIcon;
public IntPtr iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
};
class Win32
{
public const uint SHGFI_ICON = 0x100;
public const uint SHGFI_LARGEICON = 0x0; // 'Large icon
public const uint SHGFI_SMALLICON = 0x1; // 'Small icon
[DllImport("shell32.dll")]
public static extern IntPtr SHGetFileInfo(string pszPath,
uint dwFileAttributes,
ref SHFILEINFO psfi,
uint cbSizeFileInfo,
uint uFlags);
}
private int GetIconOfFile_Folder(string Path)
{
IntPtr hImgSmall; //the handle to the system image list
IntPtr hImgLarge; //the handle to the system image list
string fName; // 'the file name to get icon from
SHFILEINFO shinfo = new SHFILEINFO();
//Use this to get the small Icon
hImgSmall = Win32.SHGetFileInfo(Path, 0, ref shinfo,
(uint)Marshal.SizeOf(shinfo),
Win32.SHGFI_ICON |
Win32.SHGFI_SMALLICON);
//Use this to get the large Icon
//hImgLarge = SHGetFileInfo(fName, 0,
//ref shinfo, (uint)Marshal.SizeOf(shinfo),
//Win32.SHGFI_ICON | Win32.SHGFI_LARGEICON);
//The icon is returned in the hIcon member of the shinfo
//struct
System.Drawing.Icon myIcon =
System.Drawing.Icon.FromHandle(shinfo.hIcon);
imageList1.Images.Add(myIcon);
return imageList1.Images.Count - 1;
}
Use following Method to Get all your Drives (best place it in your constructor/Form_Load):
private void GetAllDrives()
{
DriveInfo[] drives = DriveInfo.GetDrives();
foreach (var drive in drives)
{
TreeNode rootTreeNode = new TreeNode();
rootTreeNode.Text = drive.Name;
rootTreeNode.Tag = drive.Name;
rootTreeNode.ImageIndex = GetIconOfFile_Folder(drive.Name);
rootTreeNode.SelectedImageIndex = rootTreeNode.ImageIndex;
rootTreeNode.Nodes.Add(" "); //Placeholder to enable expanding (+)
treeView1.Nodes.Add(rootTreeNode);
}
}
Then you will need an EventHandler for the Expand-Event, which will call the method GetFilesAndFolder()
private void treeView1_BeforeExpand(object sender, TreeViewCancelEventArgs e)
{
e.Node.Nodes.Clear();
GetFilesAndFolder(e.Node, (string)e.Node.Tag);
}
private void GetFilesAndFolder(TreeNode tn, string Path)
{
try
{
string[] Directories = Directory.GetDirectories(Path);
string[] Files = Directory.GetFiles(Path);
foreach (string dir in Directories)
{
TreeNode dirTreeNode = new TreeNode();
dirTreeNode.Tag = dir;
dirTreeNode.Text = new DirectoryInfo(dir).Name;
dirTreeNode.ImageIndex = GetIconOfFile_Folder(dir);
dirTreeNode.SelectedImageIndex = dirTreeNode.ImageIndex;
dirTreeNode.Nodes.Add(" ");
tn.Nodes.Add(dirTreeNode);
}
foreach (string file in Files)
{
TreeNode fileTreeNode = new TreeNode();
fileTreeNode.Tag = file;
fileTreeNode.Text = new FileInfo(file).Name;
fileTreeNode.ImageIndex = GetIconOfFile_Folder(file);
fileTreeNode.SelectedImageIndex = fileTreeNode.ImageIndex;
tn.Nodes.Add(fileTreeNode);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, ex.Source, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
Finally I have created an EventHandler for the NodeDoubleClick-Event in the TreeView:
private void treeView1_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
{
if (CheckIfPathIsFile(e.Node.Tag.ToString()) == true) //If the Tag (Path) is a File
{
//Do something with the Path (close this Form + return Path)
}
}
private bool CheckIfPathIsFile(string Path)
{
// get the file attributes for file or directory
FileAttributes attr = File.GetAttributes(Path);
//detect whether its a directory or file
if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
return false;
else
return true;
}
If you used WinForms, chances are that at some point you wanted to extend the OpenFileDialog or SaveFileDialog, but you gave up because there is no easy way to do it,Go with following link to get undestand how to custermize with your own...
HERE
Related
I would like to customize a window 10 style file dialog (open) adding additional control below the file name filed (see the attached image). And update the additional control depending on the selection if I can.
Click here to see image
How I could do it?
- Add my additional controls on the dialog
- Hook callback
I try to hook the file dialog like below code, however, it seems that the classic dialog is only available.
Please help me to figure out this.
public struct OpenFileName
{
public Int32 lStructSize;
public IntPtr hwndOwner;
public IntPtr hInstance;
public IntPtr lpstrFilter;
public IntPtr lpstrCustomFilter;
...
public OfnHookProc lpfnHook;
...
};
[return: MarshalAs(UnmanagedType.SysUInt)]
public delegate IntPtr OfnHookProc(IntPtr hdlg, [MarshalAs(UnmanagedType.U4)] int uiMsg, IntPtr wParam, IntPtr lParam);
public CustomizedDialog(string defaultExtension, string directoryName)
{
// Need two buffers in unmanaged memory to hold the filename
// Note: the multiplication by 2 is to allow for Unicode (16-bit) characters
_fileNameBuffer = Marshal.AllocCoTaskMem(2 * _MAX_PATH);
_fileTitleBuffer = Marshal.AllocCoTaskMem(2 * _MAX_PATH);
_directoryBuffer = Marshal.AllocCoTaskMem(2 * _MAX_PATH);
// Zero these two buffers
byte[] zeroBuffer = new byte[2 * (_MAX_PATH + 1)];
for (int i = 0; i < 2 * (_MAX_PATH + 1); i++) zeroBuffer[i] = 0;
Marshal.Copy(zeroBuffer, 0, _fileNameBuffer, 2 * _MAX_PATH);
Marshal.Copy(zeroBuffer, 0, _fileTitleBuffer, 2 * _MAX_PATH);
Marshal.Copy(zeroBuffer, 0, _directoryBuffer, 2 * _MAX_PATH);
// copy initial directory name into unmanaged memory buffer
byte[] directoryBytes = Encoding.Unicode.GetBytes(directoryName);
Marshal.Copy(directoryBytes, 0, _directoryBuffer, directoryBytes.Length);
// Populate the OPENFILENAME structure
// The flags specified are the minimal set to get the appearance and behaviour we need
_ofn.lStructSize = Marshal.SizeOf(_ofn);
_ofn.lpstrFile = _fileNameBuffer;
_ofn.nMaxFile = _MAX_PATH + 1;
_ofn.lpstrDefExt = Marshal.StringToCoTaskMemUni(defaultExtension);
_ofn.lpstrFileTitle = _fileTitleBuffer;
_ofn.nMaxFileTitle = _MAX_PATH + 1;
_ofn.lpstrInitialDir = _directoryBuffer;
_ofn.lpstrFilter = Marshal.StringToCoTaskMemUni(String.Format(CultureInfo.InvariantCulture, "txt \0*.txt"));
string title = String.Format(CultureInfo.InvariantCulture, "title");
_ofn.lpstrTitle = Marshal.StringToCoTaskMemUni(title);
_ofn.lpfnHook = new OfnHookProc(MyHookProc);
}
public bool Show()
{
User32.GetOpenFileName(ref _ofn);
return true;
}
public IntPtr MyHookProc(IntPtr hWnd, [MarshalAs(UnmanagedType.U4)] int msg, IntPtr wParam, IntPtr lParam)
{
...
}
I found the solution my self.
Please try to use CommonOpenFileDialog or CommonSaveFileDialog in Windows7APICodePack. https://www.nuget.org/packages/Windows7APICodePack-Shell/
refers
https://social.msdn.microsoft.com/Forums/sqlserver/en-US/3b55d433-b1e3-4943-ba28-69c72b613c91/get-folder-name-from-browserdialog-in-wpf-c?forum=wpf
https://github.com/Corillian/Windows-API-Code-Pack-1.1
It is really easy to implement with the package.
Here is an example to add controls.
public static CommonOpenFileDialog OpenFileDialog(string title, List<CommonFileDialogFilter> filters, string initialDirectory = "", bool multiselect = false)
{
var openFilerDialog = new CommonOpenFileDialog();
openFilerDialog.EnsureReadOnly = true;
openFilerDialog.IsFolderPicker = false;
openFilerDialog.AllowNonFileSystemItems = false;
openFilerDialog.Multiselect = multiselect;
openFilerDialog.Title = title;
if (filters != null)
{
foreach (var filter in filters)
{
openFilerDialog.Filters.Add(filter);
}
}
if (!string.IsNullOrEmpty(initialDirectory))
{
openFilerDialog.InitialDirectory = initialDirectory; // Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
}
return openFilerDialog;
}
private void CustomOpenFileDialog_Click(object sender, RoutedEventArgs e)
{
var dialog = FileDialog.OpenFileDialog("Custom OpenFileDialog", new List<CommonFileDialogFilter>() { new CommonFileDialogFilter("stl", "*.stl") });
AddOpenFileDialogCustomControls(dialog);
var dialogResult = dialog.ShowDialog();
}
public static void AddOpenFileDialogCustomControls(CommonFileDialog openDialog)
{
// Add a RadioButtonList
CommonFileDialogRadioButtonList list = new CommonFileDialogRadioButtonList("radioButtonOptions");
list.Items.Add(new CommonFileDialogRadioButtonListItem("Option A"));
list.Items.Add(new CommonFileDialogRadioButtonListItem("Option B"));
list.SelectedIndex = 1;
openDialog.Controls.Add(list);
}
I am working on a console executable that may run in multi monitor environment.
It may be started by double clicking on the Exe file name inside Windows Explorer.
I want to move the console to the same monitor as the Windows Explorer window that started it. Is there any way to do it?
I was able to get parent process and get MainWindowHandle.
But it will not give me the correct window. It will give me the first Explorer window. See code below. SetupConsoleWindow is the main function.
internal static class NativeMethods
{
internal const int SWP_NOSIZE = 0x0001;
[DllImport("kernel32.dll")]
internal static extern bool AllocConsole();
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
internal static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr GetConsoleWindow();
}
public static Process GetParentProcess()
{
var currentProcessName = Process.GetProcessById(Process.GetCurrentProcess().Id).ProcessName;
var processesByName = Process.GetProcessesByName(currentProcessName);
for (var index = 0; index < processesByName.Length; index++)
{
string processIndexdName = index == 0 ? currentProcessName : currentProcessName + "#" + index;
var processId = new PerformanceCounter("Process", "ID Process", processIndexdName);
if ((int)processId.NextValue() == Process.GetCurrentProcess().Id)
break;
}
var parentPerformanceCounter = new PerformanceCounter("Process", "Creating Process ID", currentProcessName);
Process parentProcess = Process.GetProcessById((int)parentPerformanceCounter.NextValue());
return parentProcess;
}
public static void SetupConsoleWindow()
{
NativeMethods.AllocConsole();
var parentProcess = GetParentProcess();
if (parentProcess != null)
{
IntPtr consoleWindowHandle = NativeMethods.GetConsoleWindow();
Screen parentScreen = Screen.FromHandle(parentProcess.MainWindowHandle);
Rectangle monitor = parentScreen.WorkingArea;
NativeMethods.SetWindowPos(consoleWindowHandle, 0, monitor.Left, monitor.Top, 0, 0, NativeMethods.SWP_NOSIZE);
}
}
So what you want to do is launch your program on the same monitor as the Windows Explorer window that started it. Doing this the way you currently intend to may be technically possible, but it would be cumbersome and flaky. It would involve enumerating all the open windows, figuring out which ones are Explorer windows, then for each of those which folder is being viewed and then which file is currently selected. You would then compare the name of that file with your program's name and, if multiple such windows are found, choose the most top-level one. You would have to hope that the file did not somehow get deselected between the double click and your main().
This process is well documented here. For reference, here is what the code to get the currently selected item looks like in C++:
#include <shlobj.h>
#include <exdisp.h>
TCHAR g_szPath[MAX_PATH];
TCHAR g_szItem[MAX_PATH];
void CALLBACK RecalcText(HWND hwnd, UINT, UINT_PTR, DWORD)
{
HWND hwndFind = GetForegroundWindow();
g_szPath[0] = TEXT('\0');
g_szItem[0] = TEXT('\0');
IShellWindows *psw;
if (SUCCEEDED(CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_ALL,
IID_IShellWindows, (void**)&psw))) {
VARIANT v;
V_VT(&v) = VT_I4;
IDispatch *pdisp;
BOOL fFound = FALSE;
for (V_I4(&v) = 0; !fFound && psw->Item(v, &pdisp) == S_OK;
V_I4(&v)++) {
IWebBrowserApp *pwba;
if (SUCCEEDED(pdisp->QueryInterface(IID_IWebBrowserApp, (void**)&pwba))) {
HWND hwndWBA;
if (SUCCEEDED(pwba->get_HWND((LONG_PTR*)&hwndWBA)) &&
hwndWBA == hwndFind) {
fFound = TRUE;
IServiceProvider *psp;
if (SUCCEEDED(pwba->QueryInterface(IID_IServiceProvider, (void**)&psp))) {
IShellBrowser *psb;
if (SUCCEEDED(psp->QueryService(SID_STopLevelBrowser,
IID_IShellBrowser, (void**)&psb))) {
IShellView *psv;
if (SUCCEEDED(psb->QueryActiveShellView(&psv))) {
IFolderView *pfv;
if (SUCCEEDED(psv->QueryInterface(IID_IFolderView,
(void**)&pfv))) {
IPersistFolder2 *ppf2;
if (SUCCEEDED(pfv->GetFolder(IID_IPersistFolder2,
(void**)&ppf2))) {
LPITEMIDLIST pidlFolder;
if (SUCCEEDED(ppf2->GetCurFolder(&pidlFolder))) {
if (!SHGetPathFromIDList(pidlFolder, g_szPath)) {
lstrcpyn(g_szPath, TEXT("<not a directory>"), MAX_PATH);
}
int iFocus;
if (SUCCEEDED(pfv->GetFocusedItem(&iFocus))) {
LPITEMIDLIST pidlItem;
if (SUCCEEDED(pfv->Item(iFocus, &pidlItem))) {
IShellFolder *psf;
if (SUCCEEDED(ppf2->QueryInterface(IID_IShellFolder,
(void**)&psf))) {
STRRET str;
if (SUCCEEDED(psf->GetDisplayNameOf(pidlItem,
SHGDN_INFOLDER,
&str))) {
StrRetToBuf(&str, pidlItem, g_szItem, MAX_PATH);
}
psf->Release();
}
CoTaskMemFree(pidlItem);
}
}
CoTaskMemFree(pidlFolder);
}
ppf2->Release();
}
pfv->Release();
}
psv->Release();
}
psb->Release();
}
psp->Release();
}
}
pwba->Release();
}
pdisp->Release();
}
psw->Release();
}
InvalidateRect(hwnd, NULL, TRUE);
}
However, you may be making this more complicated than it needs to be. Consider this instead:
Get the current mouse coordinates with Cursor.Position (or GetCursorPos)
Get the monitor on which the mouse cursor is present
Move your window to that monitor
I would like to load the icons from multiple files dragged onto a listview and some of the information of those files, but the process is going very slow.
In my test, a list of 381 files dragged (some are not exe so those are skipped in my code), takes over 2 minutes to load the icon from the file, and add them to the listview.
Following is condensed version of my code :
public Form1()
{
InitializeComponent();
listView1.DragEnter += ListView1_DragEnter;
listView1.DragDrop += ListView1_DragDrop;
listView1.LargeImageList = new ImageList() { ImageSize = new Size( 64, 64) };
listView1.SmallImageList = new ImageList();
listView1.AllowDrop = true;
}
private void ListView1_DragDrop(object sender, DragEventArgs e)
{
if(e.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] values = (string[])e.Data.GetData(DataFormats.FileDrop);
Convert.IconExtractor i = new Convert.IconExtractor();
foreach (var v in values)
{
var info = new FileInfo(v);
if(info.Extension.ToLower() == ".exe")
{
ListViewItem item = new ListViewItem();
listView1.LargeImageList.Images.Add(i.Extract(info.FullName, Convert.IconSize.Large));
listView1.SmallImageList.Images.Add(i.Extract(info.FullName, Convert.IconSize.Small));
item.Text = Path.GetFileNameWithoutExtension(info.FullName);
item.ImageIndex = listView1.SmallImageList.Images.Count -1;
item.Tag = info.FullName;
listView1.Items.Add(item);
}
}
listView1.Refresh();
}
}
private void ListView1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.All;
}
}
The method used for extraction is pasted here for convenience :
public enum IconSize
{
Small,
Large
}
public class IconExtractor
{
//----------------------------------------------------------------------------
//
// Description: Extracts the icon associated with any file on your system.
// Author: WidgetMan http://softwidgets.com
//
// Remarks...
//
// Class requires the IconSize enumeration that is implemented in this
// same file. For best results, draw an icon from within a control's Paint
// event via the e.Graphics.DrawIcon method.
//
//----------------------------------------------------------------------------
private const int SHGFI_ICON = 0x100;
private const int SHGFI_SMALLICON = 0x1;
private const int SHGFI_LARGEICON = 0x0;
private struct SHFILEINFO
{
public IntPtr hIcon;
public int iIcon;
public int dwAttributes;
[VBFixedString(260), MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[VBFixedString(80), MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
}
[DllImport("shell32", EntryPoint = "SHGetFileInfoA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int SHGetFileInfo(string pszPath, int dwFileAttributes, ref SHFILEINFO psfi, int ByValcbFileInfo, int uFlags);
public IconExtractor()
{
}
public System.Drawing.Icon Extract(string File, IconSize Size)
{
SHFILEINFO aSHFileInfo = default(SHFILEINFO);
int cbFileInfo = 0;
int uflags = 0;
System.Drawing.Icon Icon = default(System.Drawing.Icon);
switch (Size)
{
case IconSize.Large:
uflags = SHGFI_ICON | SHGFI_LARGEICON;
break;
default:
uflags = SHGFI_ICON | SHGFI_SMALLICON;
break;
}
cbFileInfo = Marshal.SizeOf(aSHFileInfo);
SHGetFileInfo(File, 0, ref aSHFileInfo, cbFileInfo, uflags);
Icon = System.Drawing.Icon.FromHandle(aSHFileInfo.hIcon);
return Icon;
}
public System.Drawing.Icon Extract(string File)
{
return this.Extract(File, IconSize.Small);
}
}
}
What can I do to make this process quick. The concept is to make a quick launcher for multiple applications.
Also of note, while this process is happening, the drag-collection icon is still 'hung' on windows explorer until the drag & drop task has completed fully (looped through all the files).
Here is a rough draft to give a visual of the application:
(yes, i know the icons extracted look like crap as well, but I think that is a separate issue from the slow issue I am having)
My advice is not to load all 381 icons in one step, particularly if the items are not visible. Load them on demand as items are scrolled into view, of if the view is large enough, load them in the background using your threading/task/concurrency technology of choice.
That's what Windows does.
To speed up loading, you may want to use the System Image List which would most likely benefit from caching. Here's some code for getting the large icon. Just change size to be SHGFI_ICON for your use.
public static Icon GetLargeIcon(string FileName, bool jumbo, bool useFileAttributes=false)
{
var shinfo = new SHFILEINFO();
uint flags;
flags = SHGFI_SYSICONINDEX;
if (useFileAttributes)
{
flags |= SHGFI_USEFILEATTRIBUTES;
}
var res = SHGetFileInfo(FileName, FILE_ATTRIBUTE_NORMAL, ref shinfo, (uint) Marshal.SizeOf(shinfo), flags);
if (res == IntPtr.Zero)
{
throw (new FileNotFoundException());
}
var iconIndex = shinfo.iIcon;
// Get the System IImageList object from the Shell:
var iidImageList = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
IImageList iml;
var size = jumbo
? SHIL_JUMBO
: SHIL_EXTRALARGE;
var hres = SHGetImageList(size, iidImageList, out iml);
if (hres != 0)
{
throw (new Exception("Error SHGetImageList"));
}
IntPtr hIcon;
const int ILD_TRANSPARENT = 1;
hres = iml.GetIcon(iconIndex, ILD_TRANSPARENT, out hIcon);
var icon = Icon.FromHandle(hIcon);
icon = icon.Clone() as Icon;
var bm = icon.ToBitmap();
DestroyIcon(hIcon);
return icon;
}
It's a while that I'm trying to figure out what's happening in this piece of code.
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
private static extern IntPtr AddFontMemResourceEx(IntPtr pbFont,
uint cbFont, IntPtr pdv, [System.Runtime.InteropServices.In] ref uint pcFonts);
public static PrivateFontCollection myFontCollection = new PrivateFontCollection();
public static FontFamily RobotoBold = null;
public static FontFamily RobotoThin = null;
public enum Fonts
{
Roboto_Bold = 0,
Roboto_Thin = 1
}
public static void loadFonts()
{
Array fonts = Enum.GetValues(typeof(Fonts));
foreach (Fonts font in fonts)
{
byte[] fontData = (byte[])Resources.ResourceManager.GetObject(font.ToString());
IntPtr fontPtr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(fontData.Length);
System.Runtime.InteropServices.Marshal.Copy(fontData, 0, fontPtr, fontData.Length);
uint dummy = 0;
myFontCollection.AddMemoryFont(fontPtr, fontData.Length);
AddFontMemResourceEx(fontPtr, (uint)fontData.Length, IntPtr.Zero, ref dummy);
System.Runtime.InteropServices.Marshal.FreeCoTaskMem(fontPtr);
}
RobotoBold = Program.myFontCollection.Families[(int)Program.Fonts.Roboto_Bold];
RobotoThin = Program.myFontCollection.Families[(int)Program.Fonts.Roboto_Thin];
}
Actually it reads the .ttf I've embedded as resources, but if I debug, inside the FontFamilys I read this message:
{Name = The name 'name' does not exist in the current context}
I've already tried to figure out what's happening, but can't find my error.
The resource is being read correctly using the ResourceManager, but I think something's wrong while adding the font.
I tried using DeviceIoControl function (Win32 API function) to eject my CDROM drive, it works perfectly when my CDROM drive has no disk, but after inserting a disk in it, the Marshal.GetLastWin32Error() returned 32 (ERROR_SHARING_VIOLATION: The process cannot access the file because it is being used by another process), the driveHandle passed in the DeviceIoControl is created by CreateFile() function.
Could you please help me out? I like this way of manipulating the CD ROM related stuff, I can use winmm.dll to eject my CDROM but I think this way is worth to try.
OK, here is the code:
using System;
using System.ComponentModel;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Runtime.InteropServices;
namespace DVD_ejector
{
public partial class Form1 : Form
{
const int OPENEXISTING = 3;
const int IOCTL_STORAGE_EJECT_MEDIA = 2967560;
const uint GENERICREAD = 0x80000000;
const int INVALID_HANDLE = -1;
public Form1()
{
InitializeComponent();
DriveInfo[] drs = DriveInfo.GetDrives();
List<DriveInfo> cdRoms = new List<DriveInfo>();
foreach (DriveInfo dInfo in drs)
{
if (dInfo.DriveType == DriveType.CDRom)
{
cdRoms.Add(dInfo);
}
}
comboBox1.DataSource = cdRoms;
comboBox1.DisplayMember = "Name";
if (comboBox1.Items.Count > 0) comboBox1.SelectedIndex = 0;
button1.Click += (sender, e) =>
{
Eject(#"\\.\" + ((DriveInfo)comboBox1.SelectedItem).Name[0]+":");
};
}
[DllImport("kernel32", SetLastError=true)]
static extern IntPtr CreateFile(string fileName, uint desiredAccess, uint shareMode, IntPtr attributes,uint creationDisposition, uint flagsAndAttribute, IntPtr fileTemplate);
[DllImport("kernel32")]
static extern int CloseHandle(IntPtr fileHandle);
[DllImport("kernel32")]
static extern bool DeviceIoControl(IntPtr driveHandle, int ctrlCode, IntPtr inBuffer, int inBufferSize, IntPtr outBuffer, int outBufferSize, ref int bytesReturned, IntPtr overlapped);
int bytesReturned;
private void Eject(string cdDrive)
{
IntPtr driveHandle = CreateFile(cdDrive, GENERICREAD, 0, IntPtr.Zero, OPENEXISTING, 0, IntPtr.Zero);
try
{
if((int)driveHandle != INVALID_HANDLE)
DeviceIoControl(driveHandle, IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, ref bytesReturned, IntPtr.Zero);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
CloseHandle(driveHandle);
}
}
}
}
As the error states, the device is being used by something else, but it's failing on the CreateFile call instead of the DeviceIoControl, and your code is not correctly checking for the failure.
The reason you're getting a sharing violation is because you're trying to open the device exclusively which will fail if ANYTHING has tried to open it or a file on it, including anti virus, Explorer, Search indexer, etc.
This updated Eject function fixes the share mode and the error handling and now reports the errors in the correct places.
private void Eject(string cdDrive) {
IntPtr driveHandle = new IntPtr(INVALID_HANDLE);
try {
// Open the device
driveHandle = CreateFile(cdDrive, GENERICREAD, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, IntPtr.Zero, OPENEXISTING, 0, IntPtr.Zero);
if ((int)driveHandle == INVALID_HANDLE) { throw new Win32Exception(); }
// Try and eject
bool ejected = DeviceIoControl(driveHandle, IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, ref bytesReturned, IntPtr.Zero);
if (!ejected) { throw new Win32Exception(); }
} catch (Exception ex) {
MessageBox.Show(ex.Message);
} finally {
if ((int)driveHandle != INVALID_HANDLE) { CloseHandle(driveHandle); }
}
}