一、剪贴板管理器
Clipboard Manager: October’s ADN Plugin of the Month, now live on Autodesk Labs
As Scott is leaving on a well-deserved sabbatical, he has gone ahead and posted our next Plugin of the Month a few days ahead of schedule. Here’s a link to Scott’s post announcing the tool.
This is a very cool little application developed by Mark Dubbelaar from Australia. Mark has been drafting/designing with for the last 10+ years and, during this time, has used a variety of programming languages to customize AutoCAD: LISP, VBA and now VB.NET. Mark was inspired by the “clipboard ring” functionality that used to be in Microsoft Office (at least I say “used to be” because I haven’t found it in Office 2007), and decided to implement similar functionality in AutoCAD.
The implementation of the tool is quite straightforward but the functionality is really very compelling: after having NETLOADed the tool and run the CLIPBOARD command, as you use Ctrl-C to copy drawing objects from inside AutoCAD to the clipboard a custom palette gets populated with entries containing these sets of objects. Each entry contains a time-stamp and an automatically-generated name which you can then change to something more meaningful.
When you want to use these clipboard entries, you simply right-click on one and choose the appropriate paste option (which ultimately just calls through to the standard AutoCAD paste commands, PASTECLIP, PASTEBLOCK and PASTEORIG, reducing the complexity of the tool).
ns55gn12hjb.png
That’s really all there is to it: a simple yet really useful application. Thanks for providing such a great little tool, Mark! :-)
Under the hood, the code is quite straightforward. The main file, Clipboard.vb, sets up the application to create demand-loading entries when first loaded into AutoCAD and defines a couple of commands – CLIPBOARD and REMOVECB, which removes the demand-loading entries to “uninstall” the application. It also contains the PaletteSet that contains our CbPalette and gets displayed by the CLIPBOARD command.
+ " automatically in future editing sessions.") End Sub
End Class
It’s the Clipboard_Palette.vb file that contains the more interesting code, implementing the behaviour of the CbPalette object. The real “magic” is how it hooks into AutoCAD’s COPYCLIP by attaching itself as the default “clipboard viewer”.
Imports System.Windows.FormsPublic Class CbPalette ' Constants for Windows API calls Private Const WM_DRAWCLIPBOARD As Integer = &H308
Private Const WM_CHANGECBCHAIN As Integer = &H30D ' Handle for next clipboard viewer Private _nxtCbVwrHWnd As IntPtr ' Boolean to control access to clipboard data Private _internalHold As Boolean = False ' Counter for our visible clipboard name Private _clipboardCounter As Integer = 0 ' Windows API declarations Declare Auto Function SetClipboardViewer Lib "user32" _
(ByVal HWnd As IntPtr) As IntPtr
Declare Auto Function SendMessage Lib "User32" _
(ByVal HWnd As IntPtr, ByVal Msg As Integer, _
ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Long ' Class constructor Public Sub New() ' This call is required by the Windows Form Designer InitializeComponent() ' Register ourselves to handle clipboard modifications _nxtCbVwrHWnd = SetClipboardViewer(Handle) End Sub Private Sub AddDataToGrid() Dim currentClipboardData As DataObject = _
My.Computer.Clipboard.GetDataObject ' If the clipboard contents are AutoCAD-related If IsAutoCAD(currentClipboardData.GetFormats) Then ' Create a new row for our grid and add our clipboard
' data stored in the "tag" Dim newRow As New DataGridViewRow()
newRow.Tag = currentClipboardData ' Increment our counter _clipboardCounter += 1 ' Create and add a cell for the name, using our counter Dim newNameCell As New DataGridViewTextBoxCell
newRow.Selected = True End If End Sub ' Move the selected item's data into the clipboard ' Check whether the clipboard data was created by AutoCAD Private Function IsAutoCAD(ByVal Formats As String()) As Boolean For Each item As String In Formats
If item.Contains("AutoCAD") Then Return True
Next
Return False End Function Private Sub PasteToClipboard() ' Use a variable to make sure we don't edit the
' clipboard contents at the wrong time _internalHold = True
My.Computer.Clipboard.SetDataObject( _
clipboardDataGridView.SelectedRows.Item(0).Tag)
_internalHold = False End Sub ' Send a command to AutoCAD Private Sub SendAutoCADCommand(ByVal cmd As String) AcApp.DocumentManager.MdiActiveDocument.SendStringToExecute( _
cmd, True, False, True) End Sub ' Our context-menu command handlers Private Sub PasteToolStripButton_Click( _
ByVal sender As Object, ByVal e As EventArgs) _
Handles PasteToolStripMenuItem.Click ' Swap the data from the selected item in the grid into the
' clipboard and use the internal AutoCAD command to paste it If clipboardDataGridView.SelectedRows.Count = 1 Then
PasteToClipboard()
SendAutoCADCommand("_pasteclip ")
End If
End Sub Private Sub PasteAsBlockToolStripMenuItem_Click( _
ByVal sender As Object, ByVal e As EventArgs) _
Handles PasteAsBlockToolStripMenuItem.Click ' Swap the data from the selected item in the grid into the
' clipboard and use the internal AutoCAD command to paste it
' as a block If clipboardDataGridView.SelectedRows.Count = 1 Then
PasteToClipboard()
SendAutoCADCommand("_pasteblock ")
End If
End Sub Private Sub PasteToOriginalCoordinatesToolStripMenuItem_Click( _
ByVal sender As Object, ByVal e As EventArgs) _
Handles PasteToOriginalCoordinatesToolStripMenuItem.Click ' Swap the data from the selected item in the grid into the
' clipboard and use the internal AutoCAD command to paste it
' at the original location If clipboardDataGridView.SelectedRows.Count = 1 Then
PasteToClipboard()
SendAutoCADCommand("_pasteorig ")
End If
End Sub Private Sub RemoveAllToolStripButton_Click( _
ByVal sender As Object, ByVal e As EventArgs) _
Handles RemoveAllToolStripButton.Click ' Remove all the items in the grid clipboardDataGridView.Rows.Clear()
End Sub Private Sub RenameToolStripMenuItem_Click( _
ByVal sender As Object, ByVal e As EventArgs) _
Handles RenameToolStripMenuItem.Click ' Rename the selected row by editing the name cell If clipboardDataGridView.SelectedRows.Count = 1 Then
clipboardDataGridView.BeginEdit(True)
End If
End Sub Private Sub RemoveToolStripMenuItem_Click( _
ByVal sender As Object, ByVal e As EventArgs) _
Handles RemoveToolStripMenuItem.Click ' Remove the selected grid item If clipboardDataGridView.SelectedRows.Count = 1 Then
clipboardDataGridView.Rows.Remove( _
clipboardDataGridView.SelectedRows.Item(0))
End If
End Sub ' Our grid view event handlers Private Sub ClipboardDataGridView_CellMouseDown( _
ByVal sender As Object, _
ByVal e As DataGridViewCellMouseEventArgs) _
Handles clipboardDataGridView.CellMouseDown ' Responding to this event allows us to make sure the
' correct row is properly selected on right-click If e.Button = Windows.Forms.MouseButtons.Right Then
End If End Sub ' Override WndProc to get messages Protected Overrides Sub WndProc(ByRef m As Message)
Select Case m.Msg ' The clipboard has changed Case Is = WM_DRAWCLIPBOARD If Not _internalHold Then AddDataToGrid() SendMessage(_nxtCbVwrHWnd, m.Msg, m.WParam, m.LParam) ' Another clipboard viewer has removed itself Case Is = WM_CHANGECBCHAIN If m.WParam = CType(_nxtCbVwrHWnd, IntPtr) Then
End SubEnd ClassPublic Class PaletteToolStrip Inherits ToolStrip Public Sub New()
MyBase.New()
End Sub Public Sub New(ByVal ParamArray Items() As ToolStripItem)
MyBase.New(Items)
End Sub Protected Overrides Sub WndProc(ByRef m As Message)
If m.Msg = &H21 AndAlso CanFocus AndAlso Not Focused Then
Focus()
End If
MyBase.WndProc(m)
End SubEnd Class
I also added a VB.NET version of the C# code that automatically registers an AutoCAD .NET application for demand-loading based on the commands it defines:
二、加强版本的截图 http://through-the-interface.typepad.com/through_the_interface/2009/11/novembers-plugin-of-the-month-screenshot.html
November 16, 2009
Updated version of Screenshot now available
We’ve had a few reports of issues with the Screenshot “Plugin of the Month”. They fall into two main categories:
Attempting to NETLOAD the application DLL from a network share
Within the ReadMe for each of the plugins we’ve documented that each application’s DLL module should be copied to the local file system – preferably inside the AutoCAD Program Files folder – before being loaded by NETLOAD. We recommend this because it essentially stops users from hitting a whole category of .NET Framework-related problems when loading and running the plugins.
If you didn’t heed this advice then you’d probably find that, as soon as the SCREENSHOT command was launched, you received a message such as “FATAL ERROR: Unsupported version of Windows Presentation Foundation.”
Now I don’t have an exhaustive list of reasons it’s best to place .NET DLLs in the AutoCAD root program folder, but my understanding/belief is that it’s down to two main ones:
1. Security
The .NET Framework implements security for different zones, and – up until the .NET Framework 3.5 SP1 – the default security setting for the “Local Intranet” (which affects applications being loaded from network shares) was “Medium Trust”. This level of trust means:
Programs might not be able to access most protected resources such as the registry or security policy settings, or access your local file system without user interaction. Programs will not be able to connect back to their site of origin, resolve domain names, and use all windowing resources.
Our “Plugin of the Month” applications target lower versions of the .NET Framework (we want to avoid forcing people to use the latest version of the framework and want the applications to run – where possible – at least as far back as AutoCAD 2007), and so their default level of trust will make running from a network share a problem.
Now it’s possible to use the Control Panel to configure earlier versions of the .NET Framework to be more tolerant of network-resident DLLs (you can change the trust level for the local intranet zone to be higher), but it’s still not something I’d recommend: I’m pretty sure this is only one aspect of the situation, and it would be dangerous to assume it’s all that’s needed.
2. Assembly Loading
The other main reason for putting modules in AutoCAD’s root program folder is related to the loading of .NET assemblies into AutoCAD’s AppDomain (which is basically what the NETLOAD command does, and this choice of architecture is why there’s no NETUNLOAD command).
While NETLOAD uses Assembly.LoadFrom() to load in .NET assembly DLLs – which does allow you to specify a path other than the current folder – there does appear to be some fragility overall with the location of assemblies and how they reference each other. It’s safest to place assemblies at a location beneath the calling executable (i.e. AutoCAD).
Capturing images with the “force foreground to black” option results in a completely black image
This one is definitely down to me. A big thank you to Harry Kortekaas for very diligently helping me identify the problem.
It actually came down to some poor application logic on my side: I was forgetting to check the “use white background” flag at the right place, and had also inverted the test for being in modelspace vs. paperspace. By chance – in many situations – the white background was picked up correctly from the paperspace, and so the issue wasn’t easily reproducible.
Anyway, a fix has now been integrated into version 1.0.2 of the application, which is now downloadable via Autodesk Labs. I’ve also included an updated version of the code below.
A third type of issue has been reported, but I haven’t yet been able to determine the cause: on one system (meaning: one reported, so far) the file selection dialog and the dynamic input graphics are not being refreshed away in time for the capture to take place.
This is similar to something we’ve been aware of from early on - the fact that the call to Editor.GetFileNameForSave() returns control to the application before the Operating System has had the chance to refresh the screen – so I built a delay (a call to System.Threading.Thread.Sleep()) into the original application to wait for a second (we started at 0.1s and worked our way upwards) which seemed to address it for all the systems upon which the application was tested.
At first it seemed – during the diagnosis of this particular issue – that this delay needed increasing, and so I added it to the per-user application settings (with the default value of 1.0, i.e. a second). This is now configurable via a new command – CONFIGSS – the logic being to keep rarely-used configuration options apart from the common ones accessible via the SCREENSHOT command. In this particular situation, though, no amount of delay seemed to help.
The same person reported an issue with the input box and temporary dimensions displayed during dynamic input not being repainted away on this particular system when the image is placed on the clipboard, so I’ve also introduced a configurable delay there, too. This is also set using CONFIGSS – the default is currently 0 seconds, to replicate the previous behaviour.
I suspect that both issues are actually down to a configuration problem (we’ll hopefully see whether my suspicion is valid), but I’ve left the capability to configure them in the application, in any case. People may also choose to reduce the current delay using CONFIGSS, as one second was on the high end: 0.3 seconds should be enough for the majority of systems. Just think of all the time you can get back! :-)
If anyone else out there has seen something similar to this with the Screenshot application, please do let us know.
The CONFIGSS command has a third setting exposed (although it’s the first one it prompts for), and this was also introduced after receiving feedback from the same source: it’s sometimes preferred to have a boundary zone or buffer around the extents chosen via the “Objects” option, so that the selection isn’t so tight-fitting around the objects. The application now has a default of 10% of the screen extents’ width or height (whichever is larger) that will get added automatically. To go back to the old behaviour you can configure this percentage to zero using CONFIGSS.
Here’s the updated C# code: the compiled version of which (again, version 1.0.2) is now available: