I’m up early (after sleeping late) with jetlag, so I thought I may as well put together this post. My brain isn’t fully functional, I suspect, so forgive any errors (but please let me know about them, so I can fix them :-).
I was inspired to implement the code in this post by an internal email thread, wherein Albert Szilvasy recommended implementing a comparable technique to a colleague. Please don’t blame Albert for the implementation details, though, they are all mine.
The idea is simple: you have some kind of modeless PaletteSet, and you want to initiate and manage a drag & drop of some kind of custom content from the palette into AutoCAD. In this case – to get started quickly – I took the code from this previous post, which displayed a palette previewing the contents of the Windows clipboard for eventual inclusion in the Clipboard Manager Plugin of the Month.
In this example we have a bitmap hosted in a palette: when someone clicks on the image, we’ll start our drag operation, and should the drag leave the palette and enter AutoCAD’s drawing window, we’ll respond to the eventual drop by saving the image in the palette as a bitmap to file and firing a command inside AutoCAD with the location of the bitmap. Once again there’s our important rule of thumb when it comes to implementing a modeless UI: rather than manually locking the current document, it’s safer to define a command – which will implicitly lock the current document – and call that from the UI via SendStringToExecute().
The custom command we call – named RINS – will simply add a raster image to the AutoCAD drawing in the current space.
Here’s the C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Windows;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;
using System.IO;
using System;
namespace DragAndDrop
{
public enum Msgs
{
WM_DRAWCLIPBOARD = 0x0308,
WM_CHANGECBCHAIN = 0x030D
}
public class ClipboardView : UserControl
{
[DllImport("user32.dll")]
public static extern IntPtr SetClipboardViewer(
IntPtr hWndNewViewer
);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(
IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam
);
private IntPtr _nxtVwr;
private PictureBox _img;
private PaletteSet _ps;
public System.Drawing.Image Contents
{
get { return _img.Image; }
}
public ClipboardView(PaletteSet ps)
{
_img = new PictureBox();
_img.Anchor =
(AnchorStyles)(AnchorStyles.Top |
AnchorStyles.Bottom |
AnchorStyles.Left |
AnchorStyles.Right);
_img.Location = new Point(0, 0);
_img.Size = this.Size;
_img.SizeMode = PictureBoxSizeMode.StretchImage;
Controls.Add(_img);
// Add our event handler to launch a new drag event
// when someone clicks on the control
_img.MouseDown +=
delegate(object sender, MouseEventArgs e)
{
// Simply initiate the drag & drop in AutoCAD,
// specifying an instance of our custom "drop
// target" class
Autodesk.AutoCAD.ApplicationServices.
Application.DoDragDrop(
_img, this.Contents, DragDropEffects.Copy,
new MyDropTarget()
);
};
_nxtVwr = SetClipboardViewer(this.Handle);
_ps = ps;
}
private void ExtractImage()
{
IDataObject iData;
try
{
iData = Clipboard.GetDataObject();
}
catch (System.Exception ex)
{
MessageBox.Show(ex.ToString());
return;
}
if (iData.GetDataPresent("Bitmap"))
{
object o = iData.GetData("Bitmap");
Bitmap b = o as Bitmap;
if (b != null)
{
_img.Image = b;
if (_ps != null)
{
_ps.Size =
new Size(b.Size.Width / 3, b.Size.Height / 3);
}
}
}
}
protected override void WndProc(ref Message m)
{
switch ((Msgs)m.Msg)
{
case Msgs.WM_DRAWCLIPBOARD:
ExtractImage();
SendMessage(_nxtVwr, m.Msg, m.WParam, m.LParam);
break;
case Msgs.WM_CHANGECBCHAIN:
if (m.WParam == _nxtVwr)
_nxtVwr = m.LParam;
else
SendMessage(_nxtVwr, m.Msg, m.WParam, m.LParam);
break;
default:
base.WndProc(ref m);
break;
}
}
}
// Our custom drop target class
public class MyDropTarget : Autodesk.AutoCAD.Windows.DropTarget
{
public override void OnDrop(DragEventArgs e)
{
Document doc =
Autodesk.AutoCAD.ApplicationServices.Application.
DocumentManager.MdiActiveDocument;
// If we have a valid bitmap, save it and send
// our custom command (RINS)
if (e.Data.GetDataPresent("Bitmap"))
{
object o = e.Data.GetData("Bitmap");
Bitmap b = o as Bitmap;
if (b != null)
{
// We'll save the bitmap as a "temp" file
// (as its unique - a better approach
// would probably to create a unique file
// in the same folder as the drawing, but
// that's left to the reader)
string path = Path.GetTempFileName();
b.Save(path);
// Call our command
string cmd = "_RINS " + path + "\n";
doc.SendStringToExecute(
cmd, false, false, false
);
}
}
}
}
public class Commands
{
private PaletteSet _ps = null;
private ClipboardView _cv = null;
static private Point3d _pt = Point3d.Origin;
[CommandMethod("DRAGDROP")]
public void DragAndDropClipboardRaster()
{
if (_ps == null)
{
_ps = new PaletteSet(
"DRAGDROP",
new Guid("5C8FC28C-45ED-4796-BD40-28D235B6D7DA")
);
if (_cv == null)
{
_cv = new ClipboardView(_ps);
}
_ps.Text = "Clipboard";
_ps.DockEnabled =
DockSides.Left | DockSides.Right | DockSides.None;
_ps.Size = new System.Drawing.Size(300, 500);
_ps.Add("ClipboardView", _cv);
}
_ps.Visible = true;
}
// Our custom command to insert a raster image
[CommandMethod("RINS")]
public void RasterInsert()
{
Document doc =
Autodesk.AutoCAD.ApplicationServices.Application.
DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
// Pick up the location of the bitmap image
PromptStringOptions pso =
new PromptStringOptions(
"\nPath of raster image: "
);
pso.AllowSpaces = true;
PromptResult pr = ed.GetString(pso);
if (pr.Status != PromptStatus.OK)
return;
if (!File.Exists(pr.StringResult))
{
ed.WriteMessage(
"\nFile does not exist."
);
return;
}
string path = pr.StringResult;
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
// Get or create our raster image dictionary
ObjectId dictId =
RasterImageDef.GetImageDictionary(db);
if (dictId == ObjectId.Null)
dictId = RasterImageDef.CreateImageDictionary(db);
// And open it for write
DBDictionary dict =
(DBDictionary)tr.GetObject(
dictId, OpenMode.ForWrite
);
// Get a unique name for our definition object
string name = RasterImageDef.SuggestName(dict, path);
// Create the definition and add it to the dictionary
RasterImageDef rid = new RasterImageDef();
rid.SourceFileName = path;
rid.Load();
ObjectId ridId = dict.SetAt(name, rid);
tr.AddNewlyCreatedDBObject(rid, true);
// Now we'll add our raster image reference
// to the current space
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
db.CurrentSpaceId,
OpenMode.ForWrite
);
// Create and add the image reference
RasterImage ri = new RasterImage();
ri.ImageDefId = ridId;
ri.SetClipBoundaryToWholeImage();
btr.AppendEntity(ri);
tr.AddNewlyCreatedDBObject(ri, true);
// Of course we commit
tr.Commit();
}
}
}
}
Here’s what happens when we run the DRAGDROP command and copy a block to the clipboard:
As we click on the image to start the drag, we see the “not allowed” cursor:
As we drag onto the drawing canvas, we see the “drag a copy” cursor:
And as we drop we see the RINS command execute and our raster image get inserted at the origin of the drawing with the default size:
Where in this post we’re just passing the location (on disk) of the newly-saved image to the RINS command, in the next post I intend to extend the command to take the location of the drop as being the insertion point of the image and then prompt the user for the second corner. We’ll probably use the raster image jig from this previous post to help with that.
One problem I’m currently having (hopefully due to jetlag-related fogginess) is with translating the screen coordinates provided to the the drop target into a position inside AutoCAD (UCS or WCS, I don’t much care) at which we can create the raster image reference. If anyone knows a simple way to get from screen coordinates to some kind of drawing coordinates, please let me know. I’ve tried a point monitor, but that unfortunately only gets the cursor’s position in the drawing prior to entering the palette, rather than at the drop location.