Image resource release in c# application - c#

I am doing an image processing project using Windows Forms (c#). You can see the design of my application below.
What does this app do : take the original image, create a copy and modify the copy.
My app is working well but, if I process the same original image another time without closing the app, I get an error due to (I think) the display of the modified image. I think that the display on the bottom right corner uses the resources of the image and, when I try to modify it again, the system considers that the image is already used by another program so it can't be modified.
So my question is : "How can I stop using the modified image if the user clicks on PROCESS again ?"
I tried to use the .Dispose() method but it didn't work.
Code of the c# function linked to the PROCESS button :
private async void button8_Click(object sender, EventArgs e)
{
// start the waiting animation
progressBar1.Visible = true;
progressBar1.Style = ProgressBarStyle.Marquee;
if (csI != csP)
{
MessageBox.Show("The selected profil does not match the selected image. Colorspaces are different.", "WARNING",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
pictureBox2.Image = null;
if (checkBox2.Checked == false)
{
rendered = false;
button8.Enabled = false;
await Task.Run(() => wrapper.DominantColors(trackBar1.Value, rendered));
//wrapper.DominantColors(trackBar1.Value, rendered);
}
else
{
rendering = comboBox1.Text;
string outputImage = wrapper.Link(rendering, bpc);
rendered = true;
button8.Enabled = false;
await Task.Run(() => wrapper.DominantColors(trackBar1.Value, rendered));
//wrapper.DominantColors(trackBar1.Value, rendered);
}
// re-enable things
button8.Enabled = true;
progressBar1.Visible = false;
MessageBox.Show("processing done");
Bitmap bit = new Bitmap(imgDstPath);
float WidthImg = bit.Width;
float HeightImg = bit.Height;
float alpha = WidthImg / pictureBox2.Width;
float beta = HeightImg / pictureBox2.Height;
alpha = Math.Max(alpha, beta);
float newWidthf = WidthImg / alpha;
float newHeightf = HeightImg / alpha;
int newHeight = (int)newHeightf;
int newWidth = (int)newWidthf;
pictureBox2.ClientSize = new Size(newWidth, newHeight);
pictureBox2.Image = bit;
pictureBox2.SizeMode = PictureBoxSizeMode.StretchImage;
}
}
If possible, I'd like to clear the use of the resources when I click on the process button.
Thank you in advance for your help

The basic rule is that all objects you create that implements IDisposable need to be disposed. When writing winforms apps all controls added to a forms are disposed when the form is disposed. But whenever you change things you might need to handle disposal yourself.
For example:
pictureBox2.Image = bit;
If pictureBox2.Image is already set to something you need to ensure that it is disposed.
var oldImage = pictureBox2.Image;
pictureBox2.Image = bit;
oldImage.Dispose();
I'm not sure this is the actual problem you are having, your example code is insufficient to make that determination. To discover this you need to debug your program! Start by examining your exceptions, does it fail when opening a file or some other resource? Where was that resource created? where is it disposed? Perhaps even use a memory debugger to produce a list of all objects that are alive to see if there are any suspicious objects kept around. Will disposal correctly occur if any arbitrary code throws an exception?
It is sometimes useful to check the identity of objects in the debugger, to see if it has been switched out, or see what object your breakpoint was triggered in. You can rightclick an object in the watch panel in visual studio and select "Make ObjectId", this will associate a number with the object that appears at the end of the value.

If anyone in the future wants to know the solution I found, here it is :
At the beginning of the PROCESS function I added those simple lines :
if (pictureBox2.Image != null)
{
pictureBox2.Image.Dispose();
pictureBox2.Image = null;}

Related

"shutdown.exe" destroys application settings

I have a one-window WPF application (Win8.1 / .net4.7), the Window.Closing-Event is unhandled, the Window.Closed-Event is handled as follows:
private void Window_Closed(object sender, EventArgs e)
{
Properties.Settings.Default.WinMainLocationX = this.Left; // ok
Properties.Settings.Default.WinMainLocationY = this.Top; // ok
Properties.Settings.Default.WinMain_size = new Size(this.Width, this.Height); // crucial setting
Properties.Settings.Default.WinMain_state = this.WindowState; // ok
Properties.Settings.Default.Save();
}
I'm closing the app (at this point always in idle state) once a day by a batch file containing C:\WINDOWS\system32\shutdown.exe /s /t 20 and nothing afterwards. By this the computer shuts down properly. The parameters of shutdown.exe can be seen by command line input of shutdown /?.
Problem: Every 7 or 8 days the window size gets corrupted in a way that the application (after having started in the morning) looks like this:
How can I protect my application settings from interference by shutdown.exe?
I think the problem is storing the settings while the application window is minimized. Width and Height of the window will be 0 in this case.
You can use the RestoreBounds property of your window to get its restored size independent of its current state:
Properties.Settings.Default.WinMainLocationX = this.RestoreBounds.Left;
Properties.Settings.Default.WinMainLocationY = this.RestoreBounds.Top;
Properties.Settings.Default.WinMain_size = new Size(this.RestoreBounds.Width, this.RestoreBounds.Height);
Properties.Settings.Default.WinMain_state = this.WindowState;
Some answers to this question show another approach using the WinAPI functions GetWindowPlacement / SetWindowPlacement:
.NET WPF Remember window size between sessions
Adding Environment.Exit(0)has solved the issue. I can imagine the cause of the problem was that the Window.Closed-Handler has been reached twice.
private void Window_Closed(object sender, EventArgs e)
{
Properties.Settings.Default.WinMainLocationX = this.Left; // ok
Properties.Settings.Default.WinMainLocationY = this.Top; // ok
Properties.Settings.Default.WinMain_size = new Size(this.Width, this.Height); // crucial setting
Properties.Settings.Default.WinMain_state = this.WindowState; // ok
Properties.Settings.Default.Save();
Environment.Exit(0);
}

Kill my application while it's using a threaded dll call

I have a structural problem resulting in an System.ObjectDisposedException with a dll.
More specifically it's a µEye Camera driver that is advised to capture data from a camera. I got two events from this camera that are fired asynchronously:
OnFrameEvent
OnSequenceEvent
The latter tells me that my capture is beeing completed and I can continue in saving my images.
This is the code that does the work:
private void onSequenceEvent(object sender, EventArgs e)
{
uEye.Camera Camera = sender as uEye.Camera;
SequenceCount++;
Camera.Acquisition.Stop();
int s32SeqID;
statusRet = Camera.Memory.Sequence.GetLast(out s32SeqID);
Invoke((MethodInvoker)delegate ()
{
lblStatus.Text = "Save Images...";
this.pbCapture.Value = 0;
});
Rectangle src = new Rectangle();
Rectangle dst = new Rectangle();
src.X = AOI_Size.X;
src.Y = AOI_Size.Y;
src.Width = AOI_Size.Width;
src.Height = AOI_Size.Height;
dst.X = 0; dst.Y = 0; dst.Width = AOI_Size.Width; dst.Height = AOI_Size.Height;
Bitmap bitmap_source = new Bitmap(MySensorInfo.MaxSize.Width, MySensorInfo.MaxSize.Height);;
Bitmap bitmap_destination = new Bitmap(dst.Width, dst.Height);;
Graphics g = Graphics.FromImage(bitmap_destination);
for (int i = 1; i < s32SeqID; i++)
{
Camera.Memory.ToBitmap(i, out bitmap_source);
g.DrawImage(bitmap_source, dst, src, GraphicsUnit.Pixel);
bitmap_destination.Save(PathToSave + i.ToString() + ".bmp");
this?.Invoke((MethodInvoker)delegate ()
{
pbOverallProgress.Value++;
pbCapture.Value++;
});
}
bitmap_source.Dispose();
g.Dispose();
this.CloseForm?.Invoke(1);
}
However - as you may expect the Invoke call will cause the ObjectDisposedException as the "Cancel"-Button is beeing pressed:
this?.Invoke((MethodInvoker)delegate ()
{
pbOverallProgress.Value++;
pbCapture.Value++;
});
Here's the code of the Cancel-Button:
private void btn_Exit_Click(object sender, EventArgs e)
{
if (MessageBox.Show("Do you really want to cancel?", "Abort", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
this.CloseForm?.Invoke(0);
}
}
private void UEye_Dialog_Form_CloseForm(int exitCode)
{
this?.Invoke((MethodInvoker)delegate ()
{
if (Camera != null)
{
Camera.EventFrame -= onFrameEvent;
Camera.EventSequence -= onSequenceEvent;
}
Camera?.Acquisition.Stop();
Camera = null;
ReturnCode = exitCode;
this.Close();
});
}
UEye_Dialog_Form_CloseForm(int exitCode) is a event where CloseForm is it's delegate.
I hope this was not too much information :)
This exception only occure if the image is beeing saved, not if I'm waiting for the sequence to be captured.
Sure I could pack the UI Update code within a try-catch-block or check if the forms state is Disposed/Disposing. But for my little programming skills it looks like a structural problem.
Thank you for your help :)
The tricky part is that you're doing multi-threading without synchronization.
Invoke presents one such synchronization point, which is fine. However, as you've found out, it doesn't work after the handle is disposed - this makes perfect sense; Invoke simply sends a window message to the given handle, and when the handle no longer exists there's noöne to deal with the message (not to mention what would Close (which just sends WM_CLOSE etc.) do when the window no longer exists).
Solving this is quite tricky, actually. Even if you check if the form is disposed before trying the Invoke, it might still be disposed between the check and the Invoke itself. locking would allow you to handle the synchronization, but you'd need to lock in DestroyHandle or perhaps an event like Closing - the key is to make sure that whatever signal you send is safely tied to whether Invoke is safe right now. Of course, you don't really want to use Invoke - you need BeginInvoke, otherwise you're guaranteed to have a deadlock when Invoke needs to wait for the UI thread which is currently waiting for the lock to be released. Not good :)
Being prepared for an ObjectDisposedException might be the best solution. But looking through the reference source code, it doesn't look like it's 100% correct either - it's consistent on a single thread, but you're not calling Invoke on the UI thread (obviously).

.NET PictureBox - how to be sure that the resource is released

I have an OpenFileDialog and PictureBox in user control. To understand the problem better I'll explain in few words how this user control works. The user can select an image to be opened for the form. The name of this image is saved in a DataBase and the file for the image is copied in a default location. When there is some image saved in the database it is load in the picturebox when the form with the picturebox control is loaded. If the user select another image and want to save the form with the new image I have a method that takes care to delete the old image file from my default location and that is where the problem occurs.
When I have loaded image and try to save new one, sometimes (very rare in fact) I get an error that The resource is being used by another process.. I can paste the exact error if needed. I think that the problem is caused from the picturebox and the way it deals with images.
Here is my code:
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
try
{
if (MyImage != null)
{
MyImage.Dispose();
}
selectedFile = openFileDialog1.FileName;
selectedFileName = openFileDialog1.SafeFileName;
MyImage = new Bitmap(openFileDialog1.FileName);
pictureBox1.Image = (Image)MyImage;
int imageWidth = pictureBox1.Image.Width;
int picBoxWidth = pictureBox1.Width;
if (imageWidth != 0 && picBoxWidth > imageWidth)
{
pictureBox1.Width = imageWidth;
}
else
{
pictureBox1.Width = defaultPicBoxWidth;
}
}
catch (Exception ex)
{
logger.Error(ex.ToString());
MessageBox.Show("Error loading image!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
and my delete method:
public void DeleteImage(AppConfig imageInfo, string imageName)
{
string imgPath = imageInfo.ConfigValue.ToString();
try
{
File.Delete(imgPath + "\\" + imageName);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
I thought that :
if (MyImage != null)
{
MyImage.Dispose();
}
will deal with this problem but still sometimes it occurs. And because it's not everytime it's even more critical to deal with it because at some point I may decide that I have solved it but in fact to be just lucky for some time.
MyImage = new Bitmap(openFileDialog1.FileName);
pictureBox1.Image = (Image)MyImage;
Yes, that code puts a lock on the file. The lock is produced by a memory mapped file object that GDI+ creates to efficiently map the pixel data of the file into memory without having to allocate space in the paging file. You will not be able to delete the file as long as the image is displayed in the picture box and not disposed, the lock prevents that. You will have to dispose the image and set the Image property back to null before you can delete the file.
You can prevent the file from getting locked by making an in-memory copy of the image:
using (var temp = new Bitmap(openFileDialog1.FileName)) {
pictureBox1.Image = new Bitmap(temp);
}
It is not as efficient of course, to be avoided if the image is large. And do beware that another process may in fact have a similar lock on the file. Nothing you can do about that.
A major difficulty with things like PictureBox is that because a PictureBox has no way of knowing whether it is the only user of an image, it consequently has no way of knowing whether it should dispose of that image when it no longer needs it.
Consequently, whatever code owns a picture box must also take ownership of the image associated therewith. There are three approaches I could suggest for doing so:
Create a control derived from PictureBox which documents itself as assuming ownership of any image given to it. Such a control should probably replace the image property with a SetImageWithOwnership method (with the semantics that once an image is passed to the PictureOwningBox, the box will be expected to "own" it, and will dispose it either when the box is Disposed or when a different image is given to the box).
Attach event handlers to a PictureBox to handle the scenarios where either the box is destroyed or a different image is assigned to it.
Have any code which would cause the PictureBox to be disposed or have a different image loaded, also dispose the Image that had been assigned to it.
While there may be cases where it would be appropriate to call GC.Collect and let the garbage-collector take care of things, such an approach is generally unsound.
try that:
using(Bitmap MyImage = new Bitmap(openFileDialog1.FileName))
{
pictureBox1.Image = (Image)MyImage;
int imageWidth = pictureBox1.Image.Width;
int picBoxWidth = pictureBox1.Width;
if (imageWidth != 0 && picBoxWidth > imageWidth)
{
pictureBox1.Width = imageWidth;
}
else
{
pictureBox1.Width = defaultPicBoxWidth;
}
}
I've had problems like this before, and one way that I've found to make sure that the resource is released, even after Dispose(), which really only marks the object for removal by the garbage collector, is by using GC.Collect(). I'm sure that there is a cleaner way to handle the resource disposal, but the time that it takes the GC.Collect() to run shouldn't hinder your program.

Is there anyway to know when the screen has been updated/refreshed (OpenGL or DirectX maybe?)

I currently have an application I'm writing in c# (using .NET) that requires me to start a timer as soon as a user sees an image on screen up until they respond with a key press.
Now I realise that practically this is very difficult given the monitor input lag and response time, the time the keyboard takes to physically send the message, the OS to process it, etc.
But I'm trying my best to reduce it down to mostly a constant error (the response time results will be used to compare one user to the next so a constant error isn't really an issue). However annoying hurdle is the variable caused by the monitor refresh rate, as I gather when my onPaint message is called and done with, it doesn't mean the image has actually been processed and sent from the graphics buffer?
Unfortunately time restrictions and other commitments would realistically restrict me to continuing this task in c# for windows.
So what I was wondering was if either handling all the drawing in OpenGL or DirectX or better still for me if it is possible to just using either OpenGL or DirectX to create an event when the screen is updated?
Another suggestion given to me previously was regarding V-Sync, if I switch this off is the image sent as soon as it is drawn? as opposed to sending images at a set rate synchronised to the monitor refresh rate?
You must render your graphic in a separate thread in order to:
Use vertical synchronisation to have a precise timing of the effective display of your image.
Get the precise timing of your user input (since user interface is not on the same thread than the render loop.
Initialise Direct3D to enable the VSync during render :
// DirectX example
presentParams.SwapEffect = SwapEffect.Discard;
presentParams.BackBufferCount = 1;
presentParams.PresentationInterval = PresentInterval.One;
device = new Device(...
Perform the render in a separate thread:
Thread renderThread = new Thread(RenderLoop);
renderThread.Start();
shouldDisplayImageEvent = new AutoResetEvent();
Then use the following render loop:
void RenderLoop()
{
while(applicationActive)
{
device.BeginScene();
// Other rendering task
if (shouldDisplayImageEvent.WaitOne(0))
{
// Render image
// ...
userResponseStopwatch = new Stopwatch();
userResponseStopwatch.Start();
}
device.EndScene();
device.Present();
}
}
Then handle the user input :
void OnUserInput(object sender, EventArgs e)
{
if (userResponseStopwatch != null)
{
userResponseStopwatch.Stop();
float userResponseDuration = userResponseStopwatch.ElapsedMillisecond - 1000 / device.DisplayMode.RefreshRate - displayDeviceDelayConstant;
userResponseStopwatch = null;
}
}
You now use the shouldDisplayImageEvent.Set() event trigger to display the image as needed and start the stop watch.
First enable the VSync on your application idle loop :
// DirectX example
presentParams.SwapEffect = SwapEffect.Discard;
presentParams.BackBufferCount = 1;
presentParams.PresentationInterval = PresentInterval.One;
device = new Device(...
Application.Idle += new EventHandler(OnApplicationIdle);
// More on this here : http://blogs.msdn.com/tmiller/archive/2005/05/05/415008.aspx
internal void OnApplicationIdle(object sender, EventArgs e)
{
Msg msg = new Msg();
while (true)
{
if (PeekMessage(out msg, IntPtr.Zero, 0, 0, 0))
break;
}
// Clearing render
// ...
if (displayImage)
{
// Render image
// ...
renderTime = DateTime.now();
}
device.Present();
}
With the vsync enabled, the device.Present function block until the next frame synchronisation, so if you compute the time between renderTime and the user input time and remove the display device delay + 16.67ms you should get your user response delay.

Drag and Drop between Instances of the same Windows Forms Application

I have created a small Windows Forms test application to try out some drag/drop code. The form consists of three PictureBoxes. My intention was to grab a picture from one PictureBox, display it as a custom cursor during the drag operation, then drop it on another PictureBox target.
This works fine from one PictureBox to another as long as they are on the same form.
If I open two instances of the same application and attempt to drag/drop between them, I get the following cryptic error:
This remoting proxy has no channel
sink which means either the server has
no registered server channels that are
listening, or this application has no
suitable client channel to talk to the
server.
For some reason, however, it does work to drag/drop to Wordpad (but not MS Word or Paintbrush).
The three PictureBoxes get their events hooked up like this:
foreach (Control pbx in this.Controls) {
if (pbx is PictureBox) {
pbx.AllowDrop = true;
pbx.MouseDown += new MouseEventHandler(pictureBox_MouseDown);
pbx.GiveFeedback += new GiveFeedbackEventHandler(pictureBox_GiveFeedback);
pbx.DragEnter += new DragEventHandler(pictureBox_DragEnter);
pbx.DragDrop += new DragEventHandler(pictureBox_DragDrop);
}
}
Then there are the four events like this:
void pictureBox_MouseDown(object sender, MouseEventArgs e) {
int width = (sender as PictureBox).Image.Width;
int height = (sender as PictureBox).Image.Height;
Bitmap bmp = new Bitmap(width, height);
Graphics g = Graphics.FromImage(bmp);
g.DrawImage((sender as PictureBox).Image, 0, 0, width, height);
g.Dispose();
cursorCreatedFromControlBitmap = CustomCursors.CreateFormCursor(bmp, transparencyType);
bmp.Dispose();
Cursor.Current = this.cursorCreatedFromControlBitmap;
(sender as PictureBox).DoDragDrop((sender as PictureBox).Image, DragDropEffects.All);
}
void pictureBox_GiveFeedback(object sender, GiveFeedbackEventArgs gfea) {
gfea.UseDefaultCursors = false;
}
void pictureBox_DragEnter(object sender, DragEventArgs dea) {
if ((dea.KeyState & 32) == 32) { // ALT is pressed
dea.Effect = DragDropEffects.Link;
}
else if ((dea.KeyState & 8) == 8) { // CTRL is pressed
dea.Effect = DragDropEffects.Copy;
}
else if ((dea.KeyState & 4) == 4) { // SHIFT is pressed
dea.Effect = DragDropEffects.None;
}
else {
dea.Effect = DragDropEffects.Move;
}
}
void pictureBox_DragDrop(object sender, DragEventArgs dea) {
if (((IDataObject)dea.Data).GetDataPresent(DataFormats.Bitmap))
(sender as PictureBox).Image = (Image)((IDataObject)dea.Data).GetData(DataFormats.Bitmap);
}
Any help would be greatly appreciated!
After much gnashing of teeth and pulling of hair, I was able to come up with a workable solution. It seems there is some undocumented strangeness going on under the covers with .NET and its OLE drag and drop support. It appears to be trying to use .NET remoting when performing drag and drop between .NET applications, but is this documented anywhere? No, I don't think it is.
So the solution I came up with involves a helper class to help marshal the bitmap data between processes. First, here is the class.
[Serializable]
public class BitmapTransfer
{
private byte[] buffer;
private PixelFormat pixelFormat;
private Size size;
private float dpiX;
private float dpiY;
public BitmapTransfer(Bitmap source)
{
this.pixelFormat = source.PixelFormat;
this.size = source.Size;
this.dpiX = source.HorizontalResolution;
this.dpiY = source.VerticalResolution;
BitmapData bitmapData = source.LockBits(
new Rectangle(new Point(0, 0), source.Size),
ImageLockMode.ReadOnly,
source.PixelFormat);
IntPtr ptr = bitmapData.Scan0;
int bufferSize = bitmapData.Stride * bitmapData.Height;
this.buffer = new byte[bufferSize];
System.Runtime.InteropServices.Marshal.Copy(ptr, buffer, 0, bufferSize);
source.UnlockBits(bitmapData);
}
public Bitmap ToBitmap()
{
Bitmap bitmap = new Bitmap(
this.size.Width,
this.size.Height,
this.pixelFormat);
bitmap.SetResolution(this.dpiX, this.dpiY);
BitmapData bitmapData = bitmap.LockBits(
new Rectangle(new Point(0, 0), bitmap.Size),
ImageLockMode.WriteOnly, bitmap.PixelFormat);
IntPtr ptr = bitmapData.Scan0;
int bufferSize = bitmapData.Stride * bitmapData.Height;
System.Runtime.InteropServices.Marshal.Copy(this.buffer, 0, ptr, bufferSize);
bitmap.UnlockBits(bitmapData);
return bitmap;
}
}
To use the class in a manner that will support both .NET and unmanaged recipients of the bitmap, a DataObject class is used for the drag and drop operation as follows.
To start the drag operation:
DataObject dataObject = new DataObject();
dataObject.SetData(typeof(BitmapTransfer),
new BitmapTransfer((sender as PictureBox).Image as Bitmap));
dataObject.SetData(DataFormats.Bitmap,
(sender as PictureBox).Image as Bitmap);
(sender as PictureBox).DoDragDrop(dataObject, DragDropEffects.All);
To complete the operation:
if (dea.Data.GetDataPresent(typeof(BitmapTransfer)))
{
BitmapTransfer bitmapTransfer =
(BitmapTransfer)dea.Data.GetData(typeof(BitmapTransfer));
(sender as PictureBox).Image = bitmapTransfer.ToBitmap();
}
else if(dea.Data.GetDataPresent(DataFormats.Bitmap))
{
Bitmap b = (Bitmap)dea.Data.GetData(DataFormats.Bitmap);
(sender as PictureBox).Image = b;
}
The check for the customer BitmapTransfer is performed first so it takes precedence over the existence of a regular Bitmap in the data object. The BitmapTransfer class could be placed in a shared library for use with multiple applications. It must be marked serializable as shown for drag and drop between applications. I tested it with drag and drop of bitmaps within an application, between applications, and from a .NET application to Wordpad.
Hope this helps you out.
I recently came across this problem, and was using a custom format in the clipboard, making Interop a bit more difficult. Anyway, with a bit of light reflection I was able to get to the original System.Windows.Forms.DataObject, and then call the GetData and get my custom item out of it like normal.
var oleConverterType = Type.GetType("System.Windows.DataObject+OleConverter, PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
var oleConverter = typeof(System.Windows.DataObject).GetField("_innerData", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(e.Data);
var dataObject = (System.Windows.Forms.DataObject)oleConverterType.GetProperty("OleDataObject").GetValue(oleConverter, null);
var item = dataObject.GetData(this.Format);
Following hours and hours of frustration with steam coming out of my ears, I finally arrived at a second solution to this problem. Exactly which solution is the most elegant is probably in the eyes of the beholder. I hope that Michael's and my solutions will both aid frustrated programmers and save them time when they embark on similar quests.
First of all, one thing that did strike me was that Wordpad was able to receive the drag/drop images just out of the box. Thus the packaging of the file was probably not the problem, but there was perhaps something fishy going on at the receiving end.
And fishy there was. It turns out there are seveal types of IDataObjects floating about the .Net framework. As Michael pointed out, OLE drag and drop support attempts to use .Net remoting when interacting between applications. This actually puts a System.Runtime.Remoting.Proxies.__TransparentProxy where the image is supposed to be. Clearly this is not (entirely) correct.
The following article gave me a few pointers in the right direction:
http://blogs.msdn.com/adamroot/archive/2008/02/01/shell-style-drag-and-drop-in-net-wpf-and-winforms.aspx
Windows Forms defaults to System.Windows.Forms.IDataObject. However, since we're dealing with different processes here, I decided to give System.Runtime.InteropServices.ComTypes.IDataObject a shot instead.
In the dragdrop event, the following code solves the problem:
const int CF_BITMAP = 2;
System.Runtime.InteropServices.ComTypes.FORMATETC formatEtc;
System.Runtime.InteropServices.ComTypes.STGMEDIUM stgMedium;
formatEtc = new System.Runtime.InteropServices.ComTypes.FORMATETC();
formatEtc.cfFormat = CF_BITMAP;
formatEtc.dwAspect = System.Runtime.InteropServices.ComTypes.DVASPECT.DVASPECT_CONTENT;
formatEtc.lindex = -1;
formatEtc.tymed = System.Runtime.InteropServices.ComTypes.TYMED.TYMED_GDI;
The two GetData functions only share the same name. One returns an object, the other is defined to return void and instead passes the info into the stgMedium out parameter:
(dea.Data as System.Runtime.InteropServices.ComTypes.IDataObject).GetData(ref formatEtc, out stgMedium);
Bitmap remotingImage = Bitmap.FromHbitmap(stgMedium.unionmember);
(sender as PictureBox).Image = remotingImage;
Finally, to avoid memory leaks, it's probably a good idea to call the OLE function ReleaseStgMedium:
ReleaseStgMedium(ref stgMedium);
That function can be included as follows:
[DllImport("ole32.dll")]
public static extern void ReleaseStgMedium([In, MarshalAs(UnmanagedType.Struct)] ref System.Runtime.InteropServices.ComTypes.STGMEDIUM pmedium);
...and this code seems to work perfectly with drag and drop operations (of bitmaps) between two applications. The code could easily be extended to other valid clipboard formats and probably custom clipboard formats too. Since nothing was done with the packaging part, you can still dragdrop an image to Wordpad, and since it accepts bitmap formats, you can also drag an image from Word into the application.
As a side note, dragging and dropping an image directly from IE does not even raise the DragDrop event. Strange.
Just out of curiousity, in the DragDrop method, have you tried testing whether you can get the bitmap image out of the DragEventArgs at all? Without doing the sender cast? I'm wondering whether the picturebox object isn't serializable, which causes the issue when you try to use the sender in a different app domain...

Categories

Resources