I know how to print an image using PrintDocument. However, i want to print my Image using the default windows print function. Like when you right click an image and click print, the dialog comes up that allows you to set size choose printer etc. Does anyone know how to achieve this in C#? Do i have to use WINAPI ?
Cheers
Edit:
I'm talking about this print dialog.
You can launch that dialog with the Process class.
private void button1_Click(object sender, EventArgs e)
{
string fileName = #"C:\Development\myImage.tif";//pass in or whatever you need
var p = new Process();
p.StartInfo.FileName = fileName;
p.StartInfo.Verb = "Print";
p.Start();
}
The simple approach with launching a new process using verb "print" is not working on Windows XP at all (it opens Windows Picture and Fax Viewer instead of the Printing Wizard). Also it does not work as intended on Windows 10 (at first run the Default app chooser for images is opened, then the default photo viewer is opened).
The correct approach would be using CLSID_PrintPhotosDropTarget COM object.
My code is in C++ (and ATL) but I hope you could translate it in C#.
I jast pass file names, but AFAIK you can pass picture itself directly without writing it on disk implementing IDataObject interface.
bool DisplaySystemPrintDialogForImage(const std::vector<CString>& files, HWND hwnd) {
static const CLSID CLSID_PrintPhotosDropTarget ={ 0x60fd46de, 0xf830, 0x4894, { 0xa6, 0x28, 0x6f, 0xa8, 0x1b, 0xc0, 0x19, 0x0d } };
CComPtr<IShellFolder> desktop; // namespace root for parsing the path
HRESULT hr = SHGetDesktopFolder(&desktop);
if (!SUCCEEDED(hr)) {
return false;
}
CComPtr<IShellItem> psi;
CComPtr<IDataObject> pDataObject;
std::vector<LPITEMIDLIST> list;
for (const auto& fileName : files) {
PIDLIST_RELATIVE newPIdL;
hr = desktop->ParseDisplayName(hwnd, nullptr, const_cast<LPWSTR>(static_cast<LPCTSTR>(fileName)), nullptr, &newPIdL, nullptr);
if (SUCCEEDED(hr)) {
list.push_back(newPIdL);
}
}
if (!list.empty()) {
hr = desktop->GetUIObjectOf(hwnd, list.size(), const_cast<LPCITEMIDLIST*>(&list[0]), IID_IDataObject, 0, reinterpret_cast<void**>(&pDataObject));
if (SUCCEEDED(hr)) {
// Create the Photo Printing Wizard drop target.
CComPtr<IDropTarget> spDropTarget;
hr = CoCreateInstance(CLSID_PrintPhotosDropTarget, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spDropTarget));
if (SUCCEEDED(hr)) {
// Drop the data object onto the drop target.
POINTL pt = { 0 };
DWORD dwEffect = DROPEFFECT_LINK | DROPEFFECT_MOVE | DROPEFFECT_COPY;
spDropTarget->DragEnter(pDataObject, MK_LBUTTON, pt, &dwEffect);
spDropTarget->Drop(pDataObject, MK_LBUTTON, pt, &dwEffect);
return true;
}
}
}
return false;
}
This works for me:
internal static class ShellHelper
{
[ComImport]
[Guid("00000122-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDropTarget
{
int DragEnter(
[In] System.Runtime.InteropServices.ComTypes.IDataObject pDataObj,
[In] int grfKeyState,
[In] Point pt,
[In, Out] ref int pdwEffect);
int DragOver(
[In] int grfKeyState,
[In] Point pt,
[In, Out] ref int pdwEffect);
int DragLeave();
int Drop(
[In] System.Runtime.InteropServices.ComTypes.IDataObject pDataObj,
[In] int grfKeyState,
[In] Point pt,
[In, Out] ref int pdwEffect);
}
internal static void PrintPhotosWizard(string p_FileName)
{
IDataObject v_DataObject = new DataObject(DataFormats.FileDrop, new string[] { p_FileName });
MemoryStream v_MemoryStream = new MemoryStream(4);
byte[] v_Buffer = new byte[] { (byte)5, 0, 0, 0 };
v_MemoryStream.Write(v_Buffer, 0, v_Buffer.Length);
v_DataObject.SetData("Preferred DropEffect", v_MemoryStream);
Guid CLSID_PrintPhotosDropTarget = new Guid("60fd46de-f830-4894-a628-6fa81bc0190d");
Type v_DropTargetType = Type.GetTypeFromCLSID(CLSID_PrintPhotosDropTarget, true);
IDropTarget v_DropTarget = (IDropTarget)Activator.CreateInstance(v_DropTargetType);
v_DropTarget.Drop((System.Runtime.InteropServices.ComTypes.IDataObject)v_DataObject, 0, new Point(), 0);
}
}
Related
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;
}
Try to use InputSimulator to simulate keyboard inputs. Everything works fine except using sim.Keyboard.ModifiedKeyStroke to simulate the input of ASCII character.
I tried to simulate Alt down + numpad1 + numpad4 + numpad7 + Alt up using the following two different ways:
sim.Keyboard.ModifiedKeyStroke(VirtualKeyCode.LMENU, new[] { VirtualKeyCode.NUMPAD1, VirtualKeyCode.NUMPAD4, VirtualKeyCode.NUMPAD7});
and
sim.Keyboard.KeyDown(VirtualKeyCode.LMENU);
sim.Keyboard.KeyDown(VirtualKeyCode.NUMPAD1);
sim.Keyboard.KeyUp(VirtualKeyCode.NUMPAD1);
sim.Keyboard.KeyDown(VirtualKeyCode.NUMPAD4);
sim.Keyboard.KeyUp(VirtualKeyCode.NUMPAD4);
sim.Keyboard.KeyDown(VirtualKeyCode.NUMPAD7);
sim.Keyboard.KeyUp(VirtualKeyCode.NUMPAD7);
sim.Keyboard.KeyUp(VirtualKeyCode.LMENU);
Neither works. I try to print out key status in console, the real key press and simulated key press both give the same result:
LMenu key down
NumPad1 key down
NumPad1 key up
NumPad4 key down
NumPad4 key up
NumPad7 key down
NumPad7 key up
LMenu key up
I think should be some problems with the library: issue 1. Could anyone help me with this please? Is there any other way to do this?
Update 1
I found "Alt+Tab" is also not working in Win8. I thought this may be the same reason so I try to fix this first. It turns out they are two different problems:
To make "Alt+Tab" working, I need to set uiAccess=true in "app.manifest" and sign the ".exe" file using a test digital signature;
Simulating the input of ASCII characters still not working.
Use keybd_event with MapVirtualKey or if using SendInput add to INPUT's Scan MapVirtualKey.
Here is example with both,
using System;
using System.Threading;
using System.Runtime.InteropServices;
namespace kbd_events_example
{
static class Program
{
#pragma warning disable 649
internal struct INPUT
{
public UInt32 Type;
public KEYBOARDMOUSEHARDWARE Data;
}
[StructLayout(LayoutKind.Explicit)]
//This is KEYBOARD-MOUSE-HARDWARE union INPUT won't work if you remove MOUSE or HARDWARE
internal struct KEYBOARDMOUSEHARDWARE
{
[FieldOffset(0)]
public KEYBDINPUT Keyboard;
[FieldOffset(0)]
public HARDWAREINPUT Hardware;
[FieldOffset(0)]
public MOUSEINPUT Mouse;
}
internal struct KEYBDINPUT
{
public UInt16 Vk;
public UInt16 Scan;
public UInt32 Flags;
public UInt32 Time;
public IntPtr ExtraInfo;
}
internal struct MOUSEINPUT
{
public Int32 X;
public Int32 Y;
public UInt32 MouseData;
public UInt32 Flags;
public UInt32 Time;
public IntPtr ExtraInfo;
}
internal struct HARDWAREINPUT
{
public UInt32 Msg;
public UInt16 ParamL;
public UInt16 ParamH;
}
#pragma warning restore 649
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint extraInfo);
[DllImport("user32.dll", SetLastError = true)]
static extern int MapVirtualKey(uint uCode, uint uMapType);
[DllImport("user32.dll", SetLastError = true)]
static extern UInt32 SendInput(UInt32 numberOfInputs, INPUT[] inputs, Int32 sizeOfInputStructure);
enum VK
{
MENU = 0x12,
NUMPAD0 = 0x60,
NUMPAD1 = 0x61,
NUMPAD2 = 0x62,
NUMPAD3 = 0x63,
NUMPAD4 = 0x64,
NUMPAD5 = 0x65,
NUMPAD6 = 0x66,
NUMPAD7 = 0x67,
NUMPAD8 = 0x68,
NUMPAD9 = 0x69
}
const uint KEYEVENTF_KEYUP = 0x0002;
public const int INPUT_KEYBOARD = 1;
[STAThread]
static void Main()
{
Thread.Sleep(5000); //wait 5 seconds, so you can focus the Notepad
keybd_event((int)VK.MENU, (byte)MapVirtualKey((uint)VK.MENU, 0), 0, 0); //Alt Press
keybd_event((int)VK.NUMPAD1, (byte)MapVirtualKey((uint)VK.NUMPAD1, 0), 0, 0); // N1 Press
keybd_event((int)VK.NUMPAD1, (byte)MapVirtualKey((uint)VK.NUMPAD1, 0), KEYEVENTF_KEYUP, 0); // N1 Release
keybd_event((int)VK.MENU, (byte)MapVirtualKey((uint)VK.MENU, 0), KEYEVENTF_KEYUP, 0); // Alt Release
Thread.Sleep(2000); //wait 2 seconds
INPUT[] inputs = new INPUT[] {
new INPUT {Type = INPUT_KEYBOARD, Data = new KEYBOARDMOUSEHARDWARE { Keyboard = new KEYBDINPUT { Vk = (ushort)VK.MENU, Flags = 0, Scan = (ushort)MapVirtualKey((uint)VK.MENU, 0), ExtraInfo = IntPtr.Zero, Time = 0}}},
new INPUT {Type = INPUT_KEYBOARD, Data = new KEYBOARDMOUSEHARDWARE { Keyboard = new KEYBDINPUT { Vk = (ushort)VK.NUMPAD2, Flags = 0, Scan = (ushort)MapVirtualKey((uint)VK.NUMPAD2, 0), ExtraInfo = IntPtr.Zero, Time = 0}}},
new INPUT {Type = INPUT_KEYBOARD, Data = new KEYBOARDMOUSEHARDWARE { Keyboard = new KEYBDINPUT { Vk = (ushort)VK.NUMPAD2, Flags = 2, Scan = (ushort)MapVirtualKey((uint)VK.NUMPAD2, 0), ExtraInfo = IntPtr.Zero, Time = 0}}},
new INPUT {Type = INPUT_KEYBOARD, Data = new KEYBOARDMOUSEHARDWARE { Keyboard = new KEYBDINPUT { Vk = (ushort)VK.MENU, Flags = 2, Scan = (ushort)MapVirtualKey((uint)VK.MENU, 0), ExtraInfo = IntPtr.Zero, Time = 0}}}
};
SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT)));
}
}
}
As for InputSimulator, you need to write your own class using the SendInput or keybd_event with MapVirtualKey to handle inputs...
Cœur, what version of InputSimulator were you using. Version 1.0.4 has the ModifiedKeyStroke(IEnumerable modifierKeyCodes, IEnumerable keyCodes) method which should allow the following:
sim.Keyboard.ModifiedKeyStroke(new[] { VirtualKeyCode.LMENU }, new[] { VirtualKeyCode.NUMPAD1, VirtualKeyCode.NUMPAD4, VirtualKeyCode.NUMPAD7 });
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;
using System.Threading;
using WindowsInput;
namespace WindowsMacros
{
class Program
{
// import function in your class
[DllImport("User32.dll")]
static extern int SetForegroundWindow(IntPtr point);
static string programnamepath = #"C:\Users\Murzic2\Desktop\Resize.exe — ярлык.lnk";
static string FolderDeafault = #"C:\Users\Murzic2\Desktop\Resize";
static void Main(string[] args)
{
MethodResizezing();
}// End Main
private static void MethodResizezing()
{
if (!Directory.Exists(FolderDeafault))
{
Directory.CreateDirectory(FolderDeafault);
}
Process process = Process.Start(programnamepath);
Process[] processes = Process.GetProcessesByName("Resize"); // Programm Name
Process resize = new Process();
foreach (Process str in processes)
{
if (str.ProcessName.Contains("Resize"))
{
resize = str;
Console.WriteLine(resize.ProcessName);
}
}
if (resize != null)
{
Console.WriteLine("Begin programm into focus ...");
IntPtr h = resize.MainWindowHandle;
SetForegroundWindow(h);
Console.WriteLine("Getting Out Of Programm");
InputSimulator isim = new InputSimulator(); // From Library InputSimulator
if (resize.StartInfo.CreateNoWindow)
{
Console.WriteLine(resize.StartTime);
}
Thread.Sleep(3000);// After need write according process stoped
MouseMove(isim, 222, 83); // Move Mouse to Open Folder Window
isim.Mouse.LeftButtonClick(); // Open Directory Window Window
MouseMove(isim, 297, 269); //
isim.Mouse.LeftButtonClick();
Thread.Sleep(200);
isim.Keyboard.ModifiedKeyStroke(WindowsInput.Native.VirtualKeyCode.CONTROL, WindowsInput.Native.VirtualKeyCode.VK_A);// Select all Key Stroke Ctrl+A
Thread.Sleep(200);
isim.Keyboard.KeyPress(WindowsInput.Native.VirtualKeyCode.RETURN); // Enter Key
MouseMove(isim, 1468, 796);
isim.Mouse.LeftButtonClick();
Thread.Sleep(200);
isim.Keyboard.KeyPress(WindowsInput.Native.VirtualKeyCode.RETURN); // Enter Key
Thread.Sleep(800);
}
Console.ReadKey();
// open this code after finishing ))
foreach (Process prc in processes)
{
if (prc.ProcessName.Contains("Resize"))
{
prc.Kill();
}
}
}
private static void MouseMove(InputSimulator isim,double X, double Y)
{
//X = 222; -- Coordintes on scree`enter code here`n which you need
//Y = 83; -- Coordinates on screen which you need
Thread.Sleep(350);
X = X * 65535 / 1535; // Converting trgarding yours screen
Y = Y * 65535 / 862;// Converting trgarding yours screen
isim.Mouse.MoveMouseTo(Convert.ToDouble(X), Convert.ToDouble(Y)); //
Thread.Sleep(150);
}
}
}
I am trying to make an external map for a computer game.
Therefore I have made a Forms Application with a picture box, that contains my map image.
Now I want to draw little squares onto the map using GDI. I allready got that working using Graphics.DrawRectangle.
Now I want to update the position of the rectangle every 0.2s.
How do I do that?
My current source (i wnt to replace the button with an auto-update):
public partial class Form1 : Form
{
//choords local player
int localX;
int localY;
int running;
const int Basex = 0x05303898;
const int Basey = 0x05303894;
const string Game = "ac_client";
//map drawing
Pen aPen = new Pen(Color.Black);
Graphics localp;
//choords enemy
//permission to read process memory
const int PROCESS_VM_READ = 0x0010; //needed for reading memory
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll")]
public static extern bool ReadProcessMemory(int hProcess,
int lpBaseAddress, byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesRead);
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
if (Process.GetProcessesByName(Game).Length > 0)
{
Process process = Process.GetProcessesByName(Game)[0];
IntPtr procHandle = OpenProcess(PROCESS_VM_READ, false, process.Id);
int bytesRead = 0;
byte[] buffer = new byte[24]; //'Hello World!' takes 12*2 bytes because of Unicode
// 0x0046A3B8 is the address where I found the string, replace it with what you found
ReadProcessMemory((int)procHandle, Basex, buffer, buffer.Length, ref bytesRead);
localX = BitConverter.ToInt32(buffer, 0);
LBlocalx.Text = Convert.ToString(Math.Ceiling(Convert.ToDecimal(localX)));
ReadProcessMemory((int)procHandle, Basey, buffer, buffer.Length, ref bytesRead);
localY = BitConverter.ToInt32(buffer, 0);
LBlocaly.Text = Convert.ToString(Math.Ceiling(Convert.ToDecimal(localY)));
localp = pictureBox1.CreateGraphics();
localp.DrawRectangle(aPen, (Convert.ToInt32(Convert.ToString(Math.Ceiling(Convert.ToDecimal(localX))))/1000), (Convert.ToInt32(Convert.ToString(Math.Ceiling(Convert.ToDecimal(localY))))/1000), 10, 10);
}
else
{
MessageBox.Show("Error! Process not running.");
}
}
How about you store two time variables(DateTime) one that has the time when you started checking for that 2 second difference, another with current time and on the beginning of every iteration you verify if the difference of both times is 2 seconds. Remember, the first variable is the one that has the time when the difference was 2 seconds or when you first started checking for that difference.
You could also use the Timer class and set a timer that on ever 2secs do something.
Timer class reference:
https://msdn.microsoft.com/en-us/library/system.timers.timer%28v=vs.110%29.aspx
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); }
}
}
I'm building a WPF application in which I need to display document previews such as what is achievable with a DocumentViewer and DocumentPaginator. However, converting the report to XPS and loading it into a DocumentViewer has proven to be very slow when the report is large (as a common report I'll need to display is).
This lead me to start thinking that there is probably some way to start showing the first few pages of the report while the rest of the pages are being 'loaded' into the DocumentViewer -- basically loading/showing the pages as they're created.
Does anyone know if something like this is possible? And, if so, how would you suggest I get started trying to make it work? I've spent a few hours looking around online for a solution to display the report faster, but haven't come up with anything.
For the sake of full disclosure, in this case the report I need to display is being created in HTML. I know that I need to convert it to XPS in order to use the DocumentViewer, but I bring this up because if anyone has a fast way of displaying the HTML, please feel free to bring that up too. I can't use a WebBrowser control as I have to have the display in a 'print preview' type of mode. A good algorithm for deciding how to 'paginate' an HTML site would probably lead me to a solution to this problem as well as then I could create a custom control to display it. I'd use a DocumentPaginator, but then the outputted file is XPS and then I'm back to the DocumentViewer issue.
Again, any help is greatly appreciated. Thank you!
Ok, I think I've got something...
Once again I found a better URL to reference. This one wasn't loading for me straight up so I grabbed it from the Google cache: http://webcache.googleusercontent.com/search?q=cache:LgceMCkJBrsJ:joshclose.net/%3Fp%3D247
Define the IViewObject interface as described in each article:
[ComVisible(true), ComImport()]
[GuidAttribute("0000010d-0000-0000-C000-000000000046")]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IViewObject
{
[return: MarshalAs(UnmanagedType.I4)]
[PreserveSig]
int Draw(
[MarshalAs(UnmanagedType.U4)] UInt32 dwDrawAspect,
int lindex,
IntPtr pvAspect,
[In] IntPtr ptd,
IntPtr hdcTargetDev,
IntPtr hdcDraw,
[MarshalAs(UnmanagedType.Struct)] ref Rectangle lprcBounds,
[MarshalAs(UnmanagedType.Struct)] ref Rectangle lprcWBounds,
IntPtr pfnContinue,
[MarshalAs(UnmanagedType.U4)] UInt32 dwContinue);
[PreserveSig]
int GetColorSet([In, MarshalAs(UnmanagedType.U4)] int dwDrawAspect,
int lindex, IntPtr pvAspect, [In] IntPtr ptd,
IntPtr hicTargetDev, [Out] IntPtr ppColorSet);
[PreserveSig]
int Freeze([In, MarshalAs(UnmanagedType.U4)] int dwDrawAspect,
int lindex, IntPtr pvAspect, [Out] IntPtr pdwFreeze);
[PreserveSig]
int Unfreeze([In, MarshalAs(UnmanagedType.U4)] int dwFreeze);
void SetAdvise([In, MarshalAs(UnmanagedType.U4)] int aspects,
[In, MarshalAs(UnmanagedType.U4)] int advf,
[In, MarshalAs(UnmanagedType.Interface)] IAdviseSink pAdvSink);
void GetAdvise([In, Out, MarshalAs(UnmanagedType.LPArray)] int[] paspects,
[In, Out, MarshalAs(UnmanagedType.LPArray)] int[] advf,
[In, Out, MarshalAs(UnmanagedType.LPArray)] IAdviseSink[] pAdvSink);
}
Create an HtmlPaginator class that screenshots the browser's document (as described) but then crops it into pages / frames:
class HtmlPaginator
{
public event EventHandler<PageImageEventArgs> PageReady;
protected virtual void OnPageReady(PageImageEventArgs e)
{
EventHandler<PageImageEventArgs> handler = this.PageReady;
if (handler != null)
handler(this, e);
}
public class PageImageEventArgs : EventArgs
{
public Image PageImage { get; set; }
public int PageNumber { get; set; }
}
public void GeneratePages(string doc)
{
Bitmap htmlImage = RenderHtmlToBitmap(doc);
int pageWidth = 800;
int pageHeight = 600;
int xLoc = 0;
int yLoc = 0;
int pages = 0;
do
{
int remainingHeightOrPageHeight = Math.Min(htmlImage.Height - yLoc, pageHeight);
int remainingWidthOrPageWidth = Math.Min(htmlImage.Width - xLoc, pageWidth);
Rectangle cropFrame = new Rectangle(xLoc, yLoc, remainingWidthOrPageWidth, remainingHeightOrPageHeight);
Bitmap page = htmlImage.Clone(cropFrame, htmlImage.PixelFormat);
pages++;
PageImageEventArgs args = new PageImageEventArgs { PageImage = page, PageNumber = pages };
OnPageReady(args);
yLoc += pageHeight;
if (yLoc > htmlImage.Height)
{
xLoc += pageWidth;
if (xLoc < htmlImage.Width)
{
yLoc = 0;
}
}
}
while (yLoc < htmlImage.Height && xLoc < htmlImage.Width);
}
private static Bitmap RenderHtmlToBitmap(string doc)
{
Bitmap htmlImage = null;
using (var webBrowser = new WebBrowser())
{
webBrowser.ScrollBarsEnabled = false;
webBrowser.ScriptErrorsSuppressed = true;
webBrowser.DocumentText = doc;
while (webBrowser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
webBrowser.Width = webBrowser.Document.Body.ScrollRectangle.Width;
webBrowser.Height = webBrowser.Document.Body.ScrollRectangle.Height;
htmlImage = new Bitmap(webBrowser.Width, webBrowser.Height);
using (Graphics graphics = Graphics.FromImage(htmlImage))
{
var hdc = graphics.GetHdc();
var rect1 = new Rectangle(0, 0, webBrowser.Width, webBrowser.Height);
var rect2 = new Rectangle(0, 0, webBrowser.Width, webBrowser.Height);
var viewObject = (IViewObject)webBrowser.Document.DomDocument;
viewObject.Draw(1, -1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, hdc, ref rect1, ref rect2, IntPtr.Zero, 0);
graphics.ReleaseHdc(hdc);
}
}
return htmlImage;
}
}
Call it like so:
WebBrowser browser = new WebBrowser();
browser.Navigate("http://www.stackoverflow.com");
while (browser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
HtmlPaginator pagr = new HtmlPaginator();
pagr.PageReady += new EventHandler<HtmlPaginator.PageImageEventArgs>(pagr_PageReady);
pagr.GeneratePages(browser.DocumentText);
To test it I implemented a basic form with a button and a picture box and a List collection. I add pages to the collection as they're ready from the HtmlPaginator and use the button to add the next image to the picturebox.
The magic numbers are your desired width and height. I used 800x600 but you probably have different dimensions you want.
The downside here is you're still waiting for the WebBrowser to render the HTML but I really don't see how an alternate solution is going to reduce that time - something has to interpret and draw the HTML in the first place. Write your own web browser I guess. :)
I did try playing with IViewObject.Draw to see if I could just have it render the page frames directly rather than have the cropping loop, but it wasn't working for me.