Saturday, February 6, 2021

Drag & Drop In AutoCAD (2) - Drag From External Application And Drop OnTo AutoCAD

This is the second of this series on Drag & Drop operation in AutoCAD. See previous ones here:

    Drag & Drop In AutoCAD (1) - The Scenarios

In this post I look into the scenario of dragging from external application and dropping onto a running AutoCAD session.

There are 2 types of external applications user could be dragging something from:

1. Applications that we, as programmers, do not have much control on how the dragging is handled. For example, we cannot control what happens when dragging document content from a Word document into AutoCAD, or we do not have control when the document content is drooped onto AutoCAD editor. The behaviours of drag & drop from both applications (Word and AutoCAD) are designed/built in them and exposed as API for us to intercept into the drag & drop process to manipulate it. So, I'll leave this case out of the discussion here. 

2. Applications we developed, thus, we  have the control at the dragging end of the drag & drop operation. That is, we can choose to use UI components that support drag operation (i.e. having MouseMove event and support DoDragDrop() method) to let drag & drop operation begins at dragging end (our application side). However, at the drop end, AutoCAD runs as a different application, thus, when the drag & drop operation is initialized from our application, we do not have control on how AutoCAD receive the data being dragged and dropped onto it: AutoCAD will do what is was designed to do. There are some dropping behaviours of AutoCAD are known to most AutoCAD users:

  • Insert block
  • Open drawing
  • Insert Text/MText entity
  • Insert OLE object
There could be other behaviours that I am not aware of, but inserting block, opening drawing and inserting MText are the most encountered dropping task when AutoCAD being at the dropping end of drag & drop operation.

Here I built a WinForm desktop application to demonstrate how file name and text string can be dragged from the application, dropped onto AutoCAD and what happens in AutoCAD.

The WinForm application's UI includes a Listbox that can be filled with file names, and a Textbox where user can input text string. There are different options (represented by Radiobuttons) for dragging content in Listbox and/or Textbox that would result in different dropping effect at AutoCAD end.



Here is the code behind the form:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.IO;
 
namespace DragFromExeToAcad
{
    public partial class MainForm : Form
    {
        private readonly string _tempTextFile = "";
 
        public MainForm()
        {
            InitializeComponent();
 
            string folder =
            Path.Combine(Environment.GetFolderPath(
                Environment.SpecialFolder.LocalApplicationData), "Temp");
            _tempTextFile = folder + "\\Dragged_Text.txt";
        }
 
        private void SelectFiles()
        {
            IEnumerable<string> files = null;
            using (var dlg = new OpenFileDialog()
            {
                Title="Select Files",
                Multiselect=true
            })
            {
                if (dlg.ShowDialog()== DialogResult.OK)
                {
                    files = dlg.FileNames;
                }
            }
 
            if (files!=null)
            {
                DragFileListBox.Items.Clear();
                foreach (var f in files)
                {
                    DragFileListBox.Items.Add(f);
                }
            }
 
        }
 
        private void SaveAsTempFile(string textString)
        {
            if (!string.IsNullOrEmpty(_tempTextFile))
            {
                try
                {
                    File.Delete(_tempTextFile);
                }
                catch { }
            }
 
            File.WriteAllText(_tempTextFile, textString);
        }
 
        
 
        private void ExitButton_Click(object sender, EventArgs e)
        {
            this.Close();
        }
 
        private void SelectFileButton_Click(object sender, EventArgs e)
        {
            SelectFiles();
        }
 
        private void DragTextBox_MouseMove(object sender, MouseEventArgs e)
        {
            if (DragTextBox.Text.Trim().Length == 0) return;
            if (e.Button != MouseButtons.Left) return;
 
            string txt = DragTextBox.SelectionLength == 0 ?
                DragTextBox.Text.Trim() : DragTextBox.SelectedText.Trim();
 
            DataObject data;
            if (TextFileRadioButton.Checked)
            {
                // drag the text string as a temporary *.txt file
                SaveAsTempFile(txt);
                data = new DataObject();
                data.SetFileDropList(
                    new System.Collections.Specialized.StringCollection { _tempTextFile });
            }
            else
            {
                data = new DataObject(txt);
            }
            
            DragTextBox.DoDragDrop(data, DragDropEffects.All);
        }
 
        private void DragFileListBox_MouseMove(object sender, MouseEventArgs e)
        {
            if (DragFileListBox.SelectedItems.Count == 0) return;
            if (e.Button != MouseButtons.Left) return;
 
            var data = new DataObject();
 
            var files = new System.Collections.Specialized.StringCollection();
            foreach (var item in DragFileListBox.SelectedItems)
            {
                files.Add(item.ToString());
            }
            data.SetFileDropList(files);
 
            // start drag & drop operation
            DragFileListBox.DoDragDrop(data, DragDropEffects.All);
        }
 
        private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (!string.IsNullOrEmpty(_tempTextFile))
            {
                try
                {
                    File.Delete(_tempTextFile);
                }
                catch { }
            }
        }
    }
}

The mostly used drag & and drop operation from external application onto AutoCAD would be dragging file name, or dragging text string. Here are some explanations about the code and dropping behaviours of AutoCAD.

1. Dragging file name

When dragging file name (single, or multiple), the file name should be stuffed into DataObject with method SetDropFileList(StringCollection), instead of one of the overloaded SetData() methods.

The argument of DragDropEffects used in DoDragDrop() method not only paly the role of showing a different mouse cursor image, but would also result in AutoCAD doing different things at dropping end. For example, when a file name, which is not a plain text/ASCII file, is dropped onto AutoCAD editor, DragDropEffects.Move makes AutoCAD insert a DWG file as block, or as OLE entity, if applicable; however, DragDropEffects.Copy makes AutoCAD open DWG file, and ignore other types of file. Also, regardless DragDropEffects, dropping DWG file onto anywhere of AutoCAD other than editor, AutoCAD would open the drawing file or files. I recommend to use DragDropEffects.All in general to that AutoCAD would do whatever it is designed to do (or not to do) at dropping end and only try other DragDropEffects specific for your special need, if any.

It is worth being pointed out that when dragging a plain text file (typically as *.txt file), AutoCAD will insert the content of the text file as MText.

2. Dragging text string

When text string is filed into DataObject by calling DataObject.SetData(string), or passed to DataObject via its Constructor (new DataObject(string)), AutoCAD receives it and create an MText entity. However, the MText is not created at the location where it is dropped (when mouse' left button releases), instead, it is created at the upper-left corner of current view of AutoCAD editor.

The trick to fix this issue (i.e. let AutoCAD create the MText at where the text string is dropped) is to save the text string into a temporary plain text file, and add this temporary file name to the DataObject by its SetDropFileList(StringCollection) method. That is why you see the extra code that create text file and clean it up when the UI is closed.

Here is the video clip showing drag & drop operation from an external application to AutoCAD;

The next article I will discuss on dragging from AutoCAD and dropping onto modeless form/window of a plugin application in AutoCAD.










Wednesday, February 3, 2021

Drag & Drop In AutoCAD (1) - The Scenarios

Drag & drop is a common computer UI operation that most computer users know. The most used drag & drop operation is drag one or more files or folders in Windows Explorer to copy/move files/folders, or drag file and drop it into an application to open it. All AutoCAD users should know that when a file is dragged from Windows Explorer and dropped into AutoCAD, it is then be either inserted into currently opened drawing in AutoCAD as block, or be opened in AutoCAD for editing/viewing.

As AutoCAD programmers, we are, of course, more interested in how to incorporate drag & drop operation into our own AutoCAD custom applications.

Firstly I want to make it clear what is standard drag & drop operation known by average computer user: user selects something on an application's UI by pressing down mouse' left button and then holds the left button down while moving the mouse - this is drag; then user moves the mouse to a desired location on the same application, or another running application, releases the mouse left button - this is drop. During the dragging, the mouse cursor should show some some kind of visual hint to indicate whether the dragged data is valid to be dropped at certain location.

In terms of drag & drop in AutoCAD use, there are 5 scenarios that might interest us, as AutoCAD programmers:

  • Drag something from AutoCAD and drop onto an external application, be it developed by ourselves or not
  • Drag something from external application, again, be it developed by ourselves or not, and drop onto AutoCAD
  • Drag something from AutoCAD and drop onto our custom plugin UI (obviously, the UI has to be modeless form/window, or we wouldn't be able to drag something from AutoCAD)
  • Drag something from our custom plugin UI and drop onto AutoCAD. In this case, our custom plugin UI can be either modal or modeless
  • Drag something from our custom plugin UI to external application developed by ourselves, or vice versa

In Windows platform, the data exchange in drag & drop process uses IDataObject interface (System.Windows.Forms.IDataObject for WinForm, and System.Windows.IDataObject for WPF). What happens is that when dragging begins, if the UI component, where the dragging begins, has a MouseMove event and has a DoDragDrop() method, then we can handle the MouseMove event to test if the left button of the move is pressed; if yes, we can then create an instance of IDataObject and stuff some data into it (say, call its SetData() method), followed by calling DoDragDrop() method, which takes the data-filled IDataObject as argument. When the mouse' left button is released at a location of UI component of the same application, or another application, if the UI component allows dropping (AllowDrop property of the UI component), we can handle Drop event, where the data is passed in as IDataObject in the event handler's argument, calling IDataObject.GetData() can retrieve the data. Obviously, depending on the data type/format that was filled into IDataObject, the code on dropping side must be able to recognize the data type/format in order to retrieve/use it as expected. Based on these understandings, now we can see what we can do with each of the 4 aforementioned scenarios.

1. Drag something from AutoCAD (entities in AutoCAD's editor) and drop onto external application. 

In this scenario, there could be 2 different cases in terms of external application: an application has no, or very little accessible API for you, as programmer, to intercept into the drag & drop operation; or an application that we developed, thus we could, it seems at least, control what/how the data dragged from AutoCAD be dropped into your application.

For the former, there is nothing we can do, obviously. 

For the latter, even we have control at the dropping/receiving end, we do not have control what/how data get into IDataObject when user drags from AutoCAD editor. In code debugging process, we can peek into the IDataObject before it is dropped, where we could find quite a few pieces of data and the data most relevant to AutoCAD among them is in the format of System.IO.MemoryStream, which we have no way to translate it into meaningful information without Autodesk exposing a suitable API for it.

Therefore, this drag & drop scenario is not a "do-able" scenario.

2. Drag something from external application and drop onto AutoCAD (editor and/or other droppable area beyond editor).

Again, there could be 2 cases: an application we do not have much control as for what/how the data is placed in IDataObject when dragging begins; and an application we build, hence have control to stuff whatever data we desire into the IDataObject for the dragging.

For the former, again, what happens for the drag & drop operation is totally depends on AutoCAD and the application. For example, when dragging *.dwg file name from Windows Explorer into AutoCAD editor triggers AutoCAD's block insertion, or opens the drawing file in AutoCAD; dropping a plain *.txt file would trigger AutoCAD to insert an MText entity; dropping most other types of file would insert an OLE entity. There is nothing we can control here. It is all depends on how the external application is developed to handle its MouseMove event and how AutoCAD is designed to react to the dropping event.

For the latter, while we can control what to be packaged into IDataObject, AutoCAD on the receiving end would only do what it is built to do, which I am no sure I know all of them, but at least I know:

  • inserting a block/or opening a drawing when a *.dwg file name is dropped
  • inserting MText when a plain text file name or text string is dropped
So, when we develop our own desktop application, if we need somehow to drag something over to an running AutoCAD session, at least we know we could do these things when the business workflow requires them.

3. Drag entities from AutoCAD's editor and drop onto our custom developed plugin's modeless UI. While AutoCAD's API does not provide something to allow us a chance to stuff IDataObject with the data we need, therefore the usual way of using IDataObject to transfer data in drag & drop operation does not work. Fortunately, since our custom plugin works inside AutoCAD process, its .NET API provide different way to let us receive dragged data when they are dropped onto our custom UI. So, it is definitely a do-able task.

4. Drag something from our custom plugin UI and drop onto AutoCAD's editor and let AutoCAD generate whatever data in the drawing, be it database-residing objects or not (most likely, they would be entities). In this case, our UI can be either modeless or modal.

5. Drag something from one side of UI and drop on the other side of UI between our custom plugin application in AutoCAD and external application developed by ourselves. In this case, as long as we make sure both ends understand data inside IDataObject. One thing to be remembered is that since the data types defined in AutoCAD managed APIs cannot be used outside AutoCAD, thus the data transferred between our plugins and our custom external application cannot contain these data type. 

I'll follow this up with a series of posts to demonstrate some code on each of the "do-able" scenarios. Stay tuned.