I have a problem that I couldn't be able to fix since one week, I hope someone could have ever experienced this.
I'm using SharpDX with a windows form project, basically there is a form with a picturebox and some panel over it.
The typical Swapchain.Present(1,PresentFlags.None) works good when I zoom or translate the image. However I get this strange situation where when I minimize my screen and reopen it back, all the swapchain content is hidden like if it has been erased. However, I know it hasn't and this is probably a race condition between GDI refresh and SharpDX.
So, I tried to overrides WndProc(Message m) in every control and handle it's paint and eraseBackground event. This is not working. When I debug, windows message are always different from time to time so it is quite hard to figure out what could be wrong.
If anyone has ever experience this behavior while mixing SharpDX(or directX) on Windows form, I would really like an answer.
Here is how I handle the resize event of my SharpDX DeviceContext
Public Overrides Sub Resize(Width As Integer, Height As Integer)
If m_SwapChain IsNot Nothing Then
If m_BackBuffer IsNot Nothing Then
m_BackBuffer.Dispose()
End If
If m_2DDeviceContext IsNot Nothing Then
m_2DDeviceContext.Dispose()
End If
If m_2DTarget IsNot Nothing Then
m_2DTarget.Dispose()
End If
m_SwapChain.ResizeBuffers(2, Width, Height, Format.B8G8R8A8_UNorm, SwapChainFlags.GdiCompatible)
m_BackBuffer = m_SwapChain.GetBackBuffer(Of Surface)(0)
m_2DDeviceContext = New SharpDX.Direct2D1.DeviceContext(m_2DDevice, SharpDX.Direct2D1.DeviceContextOptions.EnableMultithreadedOptimizations)
m_2DTarget = New SharpDX.Direct2D1.Bitmap(m_2DDeviceContext, m_BackBuffer, m_Properties)
m_2DDeviceContext.AntialiasMode = AntialiasMode.PerPrimitive
m_2DDeviceContext.Target = m_2DTarget
CType(m_Context, GPUDrawingContext).DeviceContext = m_2DDeviceContext
End If
End Sub
EDIT :
After trying many stuff, I realize it was happening right when I call base.Wndproc(m) where m = WM_PAINT.
I can't ignore the paint message because if I do, my control is still invalidated and it will add a new WM_PAINT to the event queue and send me into an infinite loop. I really cannot do base.Wndproc(m) because this is where my swapchain gets hide.
Is their any way to handle(ignore) this event message without having it back in the loop because SharpDX doesn't require this function since it's a paint layer over the control.
When you just want to resize the swapchain you don't have to recreate the DeviceContext. Maybe here is the first cause of your problem, because the swapchain was created with a different DeviceContext than the backbuffer.
For me this is working in C#:
first add eventhandlers to the resize events of the control. No need to override the WM_PAINT:
form.ResizeBegin += (o, e) => {
formHeight = ((Form)o).Height;
formWidth = ((Form)o).Width;
};
form.ResizeBegin += (o, e) => {
isResizing = true;
};
form.ResizeEnd += (o, e) => {
isResizing = false;
HandleResize(o, e);
};
form.SizeChanged += HandleResize;
with this I can save the old size of the control for comparison. And I'm just resizing the swapchain after the resize is finished and not for every event (like when resizing a window). Also I will only resize if the control is not minimized:
private void HandleResize(object sender, System.EventArgs e) {
Form f = (Form)sender;
if ((f.ClientSize.Width != formWidth || f.ClientSize.Height != formHeight)
&& !isResizing
&& !(f.WindowState == FormWindowState.Minimized)) {
formWidth = f.ClientSize.Width;
formHeight = f.ClientSize.Height;
DoResize(formWidth, formHeight);
}
}
Finally DoResize is following (renderTarget is my custom class):
private void DoResize(int width, int height) {
renderTarget.Dispose();
swapChain.ResizeBuffers(1, width, height, Format.R8G8B8A8_UNorm, SwapChainFlags.AllowModeSwitch);
using (var resource = Resource.FromSwapChain<Texture2D>(swapChain, 0)) {
//recreate the rendertarget with the new backbuffer
renderTarget.Resize(width, height, resource);
}
}
Related
Not sure if the title makes sense.
I have a form that has several panels that each has a form associated with them.
I call this form the "mainForm", and when I move/drag it across my screen it lags a lot, the mouse also feels extremely unresponsive.
When you first open it and it doesn't lag at all, however after having some controls on screen it lag becomes noticeable and when I have over 100 controls it becomes very laggy. IF I don't move it, the program runs fine and everything in itself is good, it's only when I try to move it.
I'm not entirely sure how this works, but when you move it, does it recalculate something on the form and the controls and are on it? If so is it possible to disable that when trying to move and reenabling it when we stopped moving?
Changing to WPF at this point is not possible due to time constraints.
Hopefully, someone has had an issue like this before.
Any tips are extremely appreciated.
Thank you for your time guys!
I ended up solving it. The issue was happening even with the code that Morten Bork shared. It was simply too many controls (I had about 500 controls in the test I did) and the form was going crazy slow when moved.
The user Jimi input on the matter was key, after looking around in the events I tried the following and it solved my issue. So hopefully someone that needs a solution for something similar can use my solution.
In my "mainForm" when it moves I clear the main panel I have in "ResizeBegin" and add the sub panels back to the main panel in "ResizeEnd" and it works flawlessly so far. Since I also saved the current sub panel being displayed I can make that one display first.
I once created this in winforms:
public class FormExt : Form
{
/// <summary>
/// Required designer variable.
/// </summary>
private IContainer components;
private readonly TableLayoutPanel tlp = new TableLayoutPanel();
public FormExt(IFormExtension formExtension)
{
FormExtension = formExtension;
InitializeComponent();
}
public FormExt()
{
InitializeComponent();
}
public Panel LowerLeft { get; set; }
public BorderedPanel LowerRight { get; set; }
public Panel UpperLeft { get; set; }
public IFormExtension FormExtension { get; set; }
public virtual void FormExtended_Closing(object sender, CancelEventArgs e)
{
if (FormExtension.Parent != null)
{
FormExtension.Parent.Refresh();
FormExtension.Parent.Show();
}
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && components != null) components.Dispose();
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
//TableLayoutPanel
tlp.Location = new Point(0, 0);
tlp.Dock = DockStyle.Fill;
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 100F));
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize, 500F));
tlp.RowStyles.Add(new RowStyle(SizeType.Absolute, 50F));
tlp.RowStyles.Add(new RowStyle(SizeType.AutoSize, 550F));
tlp.CellBorderStyle = TableLayoutPanelCellBorderStyle.None;
tlp.BorderStyle = BorderStyle.None;
tlp.Margin = new Padding(0);
tlp.Padding = new Padding(0);
//Upper background color
Color myRgbColor = new Color();
#if DEBUG
myRgbColor = Color.FromArgb(255, 0, 0);
#else
myRgbColor = Color.FromArgb(10, 19, 39);
#endif
//LowerLeft
LowerLeft = new Panel();
LowerLeft.BackColor = Color.White;
LowerLeft.Dock = DockStyle.Fill;
//UpperLeft
UpperLeft = new Panel();
UpperLeft.BackColor = myRgbColor;
UpperLeft.BackgroundImageLayout = ImageLayout.None;
UpperLeft.Dock = DockStyle.Fill;
//LowerRight
LowerRight = new BorderedPanel();
LowerRight.Size = new Size(600, 550);
LowerRight.BackColor = Color.White;
LowerRight.Dock = DockStyle.Fill;
components = new System.ComponentModel.Container();
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(800, 450);
FormClosing += FormExtended_Closing;
Text = "";
this.Controls.Add(tlp);
tlp.SetColumnSpan(UpperLeft, 2);
LowerLeft.Margin = new Padding(0);
LowerLeft.Padding = new Padding(0);
tlp.Controls.Add(LowerLeft, 0, 1);
UpperLeft.Margin = new Padding(0);
UpperLeft.Padding = new Padding(0);
tlp.Controls.Add(UpperLeft, 0, 0);
LowerRight.Margin = new Padding(0);
LowerRight.Padding = new Padding(0);
tlp.Controls.Add(LowerRight, 1, 1);
}
#endregion
}
To be a template for a layout.
And then I would add buttons on the left lower, content in lowerright, and upper left and right for a logo and some possible page mechanics like filter lists etc.
The nice concept is you have on 1 active form at a time. And while the otherforms by still exist in memory, they remain inactive, don't receive events unless you deliberately pass them. As far as I know. I at least, have never had the issue you describe using this component. But I never loaded "hundreds of controls" at the same time, on the same form. That is just ... to much responsibility for a single form to have.
The formExtension interface:
public interface IFormExtension
{
public Form Parent { get; set; }
}
This is just one way of creating multiple forms, that each have their own "table-layout" you can either overload this particular layout -> (Or the one you have made)
Or you can simple redefine a new layout placed in a TableLayoutPanel, and then your form should work as intended.
I had the same problem, and I found a workaround but it doesn't make sense.
I overrode the WndProc to see what messages were being sent during the form movement that might be slowing it down. 5 messages cycle through while moving the form:
3: WM_MOVE
534: WM_MOVING
70: WM_WINDOWPOSCHANGING
36: WM_GETMINMAXINFO
71: WM_WINDOWPOSCHANGED
At first, the only Windows message that I suspected could require heavy processing with a lot of controls is WM_GETMINMAXINFO, because it might be evaluating the size of controls, and would take longer for more controls.
Surprisingly, the override and addition of a Debug.WriteLine statement was enough to remove the lag altogether.
protected override void WndProc(ref Message m)
{
Debug.WriteLine($"msg: {message.Msg}, wparam:{message.WParam}, lparam:{message.LParam}, lparam:{message.HWnd}");
base.WndProc(ref m);
}
The presence of the Debug.WriteLine statement seems to change the behavior of the method such that the form moves smoothly with no lag. If I compile in release mode or comment out the Debug statement, then the form lags again. It doesn't make much sense.
UPDATE/SOLUTION: New info. This lag occurs only when dragging the form with my Rival 650 Wireless mouse. It does not lag when I drag the window with the mouse pad built into the laptop. Perhaps the mouse driver is doing something goofy or it's producing mouse move events too rapidly, and somehow the addition of a debug statement slows things down enough to disrupt the lag.
If I lower the polling rate of the mouse from 1000hz to 500hz there is still lag, but if I lower it to 250hz then the window dragging lag disappears altogether. So this seems to be caused by the high polling/event rate of the mouse and the way Windows handles the events.
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;}
I'm developing a Windows Forms application in C# with an embedded WebBrowser control to "dummy-proof" (i.e. disable context menus, back button, free navigation, etc.) access to a third-party web application.
Right now I'm trying to add the Zoom feature to my custom browser. I have the keyboard combos working for it (CTRL + and CTRL - make the correct OLE calls to the underlying ActiveX WebBrowser object), but among the other frustrating things about WebBrowser I've had to deal with, I can't seem to figure out how to capture CTRL-Mouse wheel to simulate the Zoom function like IE does. I've looked everywhere to find a solution to this but to no avail.
To try to figure it out, I created an empty form with just the WebBrowser control in it, and found the following:
CTRL-MouseWheel always fires the MouseWheel event when the parent form has focus and the mouse cursor is hovering over the top of the window (e.g. over the title of the application), or when the mouse cursor is hovering over the WebBrowser control when it does not appear to have focus even though the parent form has focus.
CTRL-MouseWheel never fires the MouseWheel event when the mouse cursor is hovering over the WebBrowser control and WebBrowser has focus, and there seems to be no effect.
Using the mouse wheel without CTRL scrolls the window contents of WebBrowser but does not fire the MouseWheel event until the vertical scroll bar has fully reached either the top or the bottom.
Intercepting the Message for WM_MOUSEWHEEL by overriding WndProc and DefWndProc both for a sample class inherited from WebBrowser and for the parent form applies only for the above conditions (with wParam properly denoting MK_CONTROL).
The PreviewKeyDown event fires when CTRL is pressed, as expected, but still does nothing in unison with the mouse wheel.
So I guess the Message is being processed below the parent form and the managed control level and does not bubble up to where I can intercept or even handle it. Is there a way to do this, or some other way to simulate zooming in and out using CTRL-MouseWheel?
Thanks for reading!
First cast the WebBrowser.Document.DomDocument to the right interface in the mshtml namespace, like mshtml.HTMLDocumentEvents2_Event, then you can handle (and cancel) mousewheel events. I'm not sure, but I think you need to wire up the event handler anytime the document is changed, so I do it on the WebBrowser.DocumentCompleted event. I'm also not sure if you need to release any COM objects.
This was frustrating enough that I got it to work and stopped caring...
Here is at least one document explaining the basics: How to handle document events in a Visual C# .NET application
For your specific case, just conditionally squash the onmousewheel event, based on whether or not the CTRL key is pressed.
private void webBrowser_DocumentCompleted(object sender,
WebBrowserDocumentCompletedEventArgs e)
{
if (webBrowser.Url.ToString() == "about:blank")
return;
var docEvents = (mshtml.HTMLDocumentEvents2_Event)webBrowser.Document.DomDocument;
docEvents.onmousewheel -= docEvents_onmousewheel; //may not be necessary?
docEvents.onmousewheel += docEvents_onmousewheel;
}
bool docEvents_onmousewheel(mshtml.IHTMLEventObj pEvtObj)
{
if (pEvtObj.ctrlKey)
{
pEvtObj.cancelBubble = true; //not sure what this does really
pEvtObj.returnValue = false; //this cancels the event
return false; //not sure what this does really
}
else
return true; //again not sure what this does
}
Now if you need to know the Wheel Delta (scroll amount), you'll want to cast the events object to yet another interface.
bool docEvents_onmousewheel(mshtml.IHTMLEventObj pEvtObj)
{
var wheelEventObj = (mshtml.IHTMLEventObj4)pEvtObj;
var delta = wheelEventObj.wheelDelta;
[...]
}
Perhaps using SetWindowsHookEx to look for these events may work for you. This is what I've used to get scroll wheel events on top of the ActiveX MapPoint control.
Be aware there are some quirks with this on Windows 7 that may require some tinkering. For more details do a search for SetWindowsHookEx on Windows 7 on the MSDN forums.
To solve this problem you have to listen for and handle these messages:
OLECMDID_GETZOOMRANGE
OLECMDID_OPTICAL_GETZOOMRANGE
OLECMDID_OPTICAL_ZOOM
OLECMDID_ZOOM
They're dispatched by Internet Explorer. See the remarks on MSDN.
This is the code I used to disable ctrl+shift: You need to change the behavior of WndProc in the deepest control "Internet Explorer_Server",
Do this after your web browser is ready:
IntPtr wbHandle = Win32.FindWindowEx(this.wbMain.Handle, IntPtr.Zero, "Shell Embedding", String.Empty);
wbHandle = Win32.FindWindowEx(wbHandle, IntPtr.Zero, "Shell DocObject View", String.Empty);
wbHandle = Win32.FindWindowEx(wbHandle, IntPtr.Zero, "Internet Explorer_Server", String.Empty);
WbInternal wb = new WbInternal(wbHandle);
class WbInternal : NativeWindow
{
public WbInternal(IntPtr handle)
{
this.AssignHandle(handle);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_MOUSEWHEEL)
{
if (((int)m.WParam & 0x00FF) == MK_SHIFT)
{
return;
}
}
base.WndProc(ref m);
}
}
You can find more message about WM_MOUSEWHEEL from MSDN.
I find this from MSDN. But I forgot the link, Once find, will append it here.
I couldn't get any of these to work reliably so after some (frustrating) experimentation, I came up with a derivative of the answer posted by TCC. My webbrowser control is hosted in a usercontrol. The main differences are I use a class-level variable for the HTMLDocumentEvents2_Event so I can unsubscribe successfully, and I set the mshtml.IHTMLEventObj pEvtObj.Returnvalue to true.. seems to work well now..
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_wbData = (WebBrowser)FindElement("DataBrowser");
_horzScroll = (ScrollBar)FindElement("HorizontalScroll");
_vertScroll = (ScrollBar)FindElement("VerticalScroll");
_wbData.LoadCompleted += new System.Windows.Navigation.LoadCompletedEventHandler(OnLoadCompleted);
_horzScroll.Scroll += new ScrollEventHandler(OnHorizontalScroll);
_vertScroll.Scroll += new ScrollEventHandler(OnVerticalScroll);
LoadDefault();
EnableSoundEffects(SoundEffects);
}
private void OnHorizontalScroll(object sender, ScrollEventArgs e)
{
// _wbData.Handle
mshtml.HTMLDocument htmlDoc = _wbData.Document as mshtml.HTMLDocument;
_horzPosition = (int)e.NewValue;
if (htmlDoc != null && htmlDoc.body != null)
htmlDoc.parentWindow.scroll(_horzPosition, _vertPosition);
}
private void OnVerticalScroll(object sender, ScrollEventArgs e)
{
mshtml.HTMLDocument htmlDoc = _wbData.Document as mshtml.HTMLDocument;
_vertPosition = (int)e.NewValue;
if (htmlDoc != null && htmlDoc.body != null)
htmlDoc.parentWindow.scroll(_horzPosition, _vertPosition);
}
private void OnLoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
mshtml.HTMLDocument htmlDoc = _wbData.Document as mshtml.HTMLDocument;
if (htmlDoc != null && htmlDoc.body != null)
{
mshtml.IHTMLElement2 body = (mshtml.IHTMLElement2)htmlDoc.body;
_horzScroll.Visibility = Visibility.Collapsed;
if (body.scrollHeight > _wbData.ActualHeight)
_vertScroll.Visibility = Visibility.Visible;
else
_vertScroll.Visibility = Visibility.Collapsed;
_vertScroll.ViewportSize = _wbData.ActualHeight;
_vertScroll.Maximum = body.scrollHeight - (_wbData.ActualHeight - 8);
_eventHelper = (HTMLDocumentEvents2_Event)_wbData.Document;
_eventHelper.onmousewheel -= OnMouseWheel;
_eventHelper.onmousewheel += new HTMLDocumentEvents2_onmousewheelEventHandler(OnMouseWheel);
}
}
private bool OnMouseWheel(mshtml.IHTMLEventObj pEvtObj)
{
mshtml.HTMLDocument htmlDoc = _wbData.Document as mshtml.HTMLDocument;
var wheelEventObj = (mshtml.IHTMLEventObj4)pEvtObj;
var delta = wheelEventObj.wheelDelta;
if (htmlDoc != null && htmlDoc.body != null && wheelEventObj != null)
{
_vertPosition += (int)wheelEventObj.wheelDelta;
htmlDoc.parentWindow.scroll(_horzPosition, _vertPosition);
}
pEvtObj.returnValue = true;
return true;
}
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...
I am trying to make a form move (using the titlebar) from a button click.
I thought this would be simple using SendMessage:
Const WM_LBUTTONDOWN As Integer = &H201
Button1.Capture = False
Cursor.Position = Me.Location + New Size(50, 8)
SendMessage(Me.Handle, WM_LBUTTONDOWN, CType(1, IntPtr), IntPtr.Zero)
However, although this sends the message if the cursor is in the forms client area, it does not seem to send it to the forms titlebar (the form captures the event somehow, despite the cursor being on the titlebar not in the client area).
I have tried the above code in both mousedown and click events on the button, moving the cursor and then pressing on the button1.
Any suggestions?
You would need WM_NCLBUTTONDOWN (and pass HTCAPTION as wParam). I'm still not entirely sure this would accomplish what you're trying to do, though.
Typically, the way to allow the user to move your form when clicking somewhere other than the title bar is to process the WM_NCHITTEST message and return HTCAPTION when the cursor is over the area from which you'd like to initiate moving. However, if this area is occupied by a child control, you also have to process WM_NCHITTEST from the child control and return HTTRANSPARENT.
Incidentally, an easier—if slightly less correct—way to accomplish this is to do as Mehrdad Afshari suggested, and just set the form's Location property. You commented to him that "it needs to move on the mouse move", and that's exactly what you can and should do.
class MyForm : Form{
Point downAt;
MyForm(){
Label lbl = new Label();
lbl.AutoSize = true;
lbl.BackColor = Color.Blue;
lbl.ForeColor = Color.White;
lbl.Location = new Point(50, 50);
lbl.Text = "Drag me to move this form.";
lbl.Parent = this;
lbl.MouseDown += (s, e)=>downAt = e.Location;
lbl.MouseMove += (s, e)=>{if(lbl.Capture) Location += (Size)e.Location - (Size)downAt;};
}
}
The problem with this approach is that it bypasses Windows' code for moving a top-level window. This means that if the user has not selected the "Show window contents while dragging" option in the Display Properties dialog, this will effectively ignore that setting (it won't show a drag outline). There may be other drawbacks that I haven't thought of as well.
On the whole, though, this is a simple, easy way to accomplish this that is a fully .NET solution which doesn't rely on any platform invoke (so it should be portable to Mono on Unix).
Oops. I just realized that I gave you C# example code, but your code seems to be VB.NET. I guess what you would need would be:
Sub New()
Dim lbl As New Label
lbl.AutoSize = True
lbl.BackColor = Color.Blue
lbl.ForeColor = Color.White
lbl.Location = New Point(50, 50)
lbl.Text = "Drag me to move this form."
lbl.Parent = Me
AddHandler lbl.MouseDown, Function(ByVal s As Object, ByVal e As MouseEventArgs)
Me.downAt = e.Location
End Function
AddHandler lbl.MouseMove, Function(ByVal s As Object, ByVal e As MouseEventArgs)
If lbl.Capture Then
Me.Location = Me.Location + DirectCast(e.Location, Size) - DirectCast(Me.downAt, Size)
End If
End Function
End Sub
This may not be the most succinct way to express this in VB.NET. I used Reflector to help me translate it.
The LParam value for the wm_LButtonDown message receives the mouse position in client coordinates. The title bar is in the non-client area, so use the wm_NCLButtonDown message. I've seen that message given as an answer to this question before, but there's a more direct route that I would have expected to work: Send a wm_SysCommand message to the window, and specify sc_Move flag.
Mehrdad is right, no need to do this. The mouse is captured so you can never move it too quickly. Sample code:
Point mLastPos;
private void button1_MouseMove(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Left) {
this.Location = new Point(this.Location.X + e.X - mLastPos.X,
this.Location.Y + e.Y - mLastPos.Y);
}
// NOTE: else is intentional!
else mLastPos = e.Location;
}