With the release of .Net 4.0 I have updated the FormatC syntax highlighter (used for all code samples on ookii.org) to support new keywords introduced in Visual Basic 10.0 and C# 4.0.
That's not the only update, however. FormatC 2.0 also has the ability to escape contextual keywords (previously, it would treat them as keywords regardless of the context), type name highlighting support for Visual Basic and improved XML literal support in Visual Basic.
PowerShell support is also improved; FormatC will try to use the parser included with PowerShell 2.0 to parse the code. Unfortunately, this web server does not have PowerShell 2.0 installed, so the online version of the highlighter will still use regular expressions to highlight PowerShell code.
If you have any feedback about FormatC, please leave it as a comment on this blog post.
Sample C# code highlighted using FormatC:
public static int SumAuthorPrices(IEnumerable<Book> books, string author) { if( books == null ) throw new ArgumentNullException("books"); return (from b in books where b.Author == author select b.Price).Sum(); }
Sample Visual Basic code highlighted using FormatC:
Public Shared Function ConvertToXml(ByVal books As IEnumerable(Of Book)) As XDocument If books Is Nothing Then Throw New ArgumentNullException("books") End If Return <?xml version="1.0"?> <Books> From b In Books Select <Book Author= b.Author Price= b.Price > b.Title </Book> </Books> End Function
Sample XML code highlighted using FormatC:
<?xml version="1.0"?> <Books> <Book Author="Peter F. Hamilton" Price="18.99"> The Evolutionary Void </Book> </Books>
Sample T-SQL code highlighted using FormatC:
SELECT title, author, publisherName FROM Books b INNER JOIN Publisher p ON b.publisherId = p.id WHERE b.price >= 10
Sample PowerShell code highlighted using FormatC (this used the parser-based method, not regular expressions):
foreach ($file in Get-ChildItem) { if ($file.Length -gt 100kb) { Write-Host -ForegroundColor Green $file Write-Host $file.Length Write-Host $file.LastAccessTime } }
A while ago, Raymond Chen wrote about the mechanism for using IDropTarget to receive a list of files to open.
The DropTarget method provides you with an alternative way to register a verb for a file type (i.e. a right-click context menu option). The other alternatives are to receive the files via the command line or via DDE. Since DDE is deprecated, the DropTarget method is specifically meant to replace that.
The primary reason you might want to use this method is if you want to open multiple files in the same application instance. If you select multiple files at once in Explorer and then right-click them and select your custom option, if you used the traditional command line method, your application would be launched multiple times. With the DropTarget method, the application is launched only once and that one instance receives all the files. What’s more, if your application is already running the existing instance will receive the files.
Raymond provided sample code for how to use this method in C++, but what if you want to do this in .Net? Fortunately, it’s relatively easy to do.
Although both Windows Forms and Windows Presentation Foundation have support for drag and drop and probably implement IDragDrop somewhere internally, we need to have access to an implementation we can expose as a COM local server so we cannot use this. And although Windows Forms exposes an IDropTarget interface, this interface does not match the COM interface we need so it’s also of no use to us.
So the first thing we need is the COM IDropTarget interface. I couldn’t find any type library we could import in .Net, so we’ll define the interface manually. Fortunately, it’s not very big so this is pretty easy.
using System.Drawing; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("00000122-0000-0000-C000-000000000046")] // This is the value of IID_IDropTarget from the Platform SDK. [ComImport] interface IDropTarget { void DragEnter([In] IDataObject dataObject, [In] uint keyState, [In] Point pt, [In, Out] ref uint effect); void DragOver([In] uint keyState, [In] Point pt, [In, Out] ref uint effect); void DragLeave(); void Drop([In] IDataObject dataObject, [In] uint keyState, [In] Point pt, [In, Out] ref uint effect); }
Since this code will only ever be used for the shell DropTarget method, and not for actual dragging and dropping, I’ve not bothered to use proper enumerations for keyState
and effect
, because we won’t use those arguments. Also note I’m making use of the IDataObject
COM interface from the System.Runtime.InteropServices.ComTypes
namespace.
Besides the COM interface, we’ll also need two define two PInvoke methods as well as a constant for the data format we’ll use.
using System.Text; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; static class NativeMethods { public const int CF_HDROP = 15; [DllImport("shell32.dll", CharSet=CharSet.Unicode)] public static extern int DragQueryFile(HandleRef hDrop, int iFile, [Out] StringBuilder lpszFile, int cch); [DllImport("ole32.dll")] internal static extern void ReleaseStgMedium(ref STGMEDIUM medium); }
Here we’re using the STGMEDIUM
structure, also defined in the System.Runtime.InteropServices.ComTypes
namespace.
Next, we have to create a class that implements this interface. The only members the shell ever calls are DragEnter
and Drop
, and the only one we’ll actually need is Drop
, so we’ll leave the rest empty.
using System; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Text; [ComVisible(true)] [Guid("your-guid-here")] public class MyDropTarget : IDropTarget { public void Drop(IDataObject dataObject, uint keyState, System.Drawing.Point pt, ref uint effect) { FORMATETC format = new FORMATETC() { cfFormat = NativeMethods.CF_HDROP, dwAspect = DVASPECT.DVASPECT_CONTENT, tymed = TYMED.TYMED_HGLOBAL }; STGMEDIUM medium; string[] files; dataObject.GetData(ref format, out medium); try { IntPtr dropHandle = medium.unionmember; int fileCount = NativeMethods.DragQueryFile(new HandleRef(this, dropHandle), -1, null, 0); files = new string[fileCount]; for( int x = 0; x < fileCount; ++x ) { int size = NativeMethods.DragQueryFile(new HandleRef(this, dropHandle), x, null, 0); if( size > 0 ) { StringBuilder fileName = new StringBuilder(size + 1); if( NativeMethods.DragQueryFile(new HandleRef(this, dropHandle), x, fileName, fileName.Capacity) > 0 ) files[x] = fileName.ToString(); } } } finally { NativeMethods.ReleaseStgMedium(ref medium); } // Do something with the files here. } public void DragEnter(System.Runtime.InteropServices.ComTypes.IDataObject dataObject, uint keyState, System.Drawing.Point pt, ref uint effect) { } public void DragOver(uint keyState, System.Drawing.Point pt, ref uint effect) { } public void DragLeave() { } }
This code simply retrieves the files we were passed and stores them in an array. It’s up to you to do something interesting with them afterwards (and I’d recommend not doing any further processing on the thread that called Drop because you’ll make the shell wait for your drop handler to complete).
Finally, you need to put some special code in your Main method to make the COM server available to other applications while your application is running.
static void Main(string[] args) { RegistrationServices reg = new RegistrationServices(); // this is the equivalent to CoRegisterClassObject in Win32 int cookie = reg.RegisterTypeForComClients(typeof(MyDropTarget), RegistrationClassContext.LocalServer, RegistrationConnectionType.MultipleUse); try { if( args.Length > 0 && !(string.Equals(args[0], "-embedding", StringComparison.OrdinalIgnoreCase) || string.Equals(args[0], "/embedding", StringComparison.OrdinalIgnoreCase)) ) { // If your application was launched by COM as a local server, it'll specify the -Embedding or /Embedding switch. // If the -embedding argument was not specified, we should process the command line normally OpenFilesFromCommandLine(args); } // Run your application, e.g. by using Application.Run in Windows Forms. } finally { reg.UnregisterTypeForComClients(cookie); } }
Now all that’s left to do is to create the relevant registry entries, which Raymond already covered and which is not any different for .Net:
[HKCR\CLSID\{your-guid-here}\LocalServer32]
@="C:\\Path\\To\\Your\\.Net\\App.exe"
[HKCR\txtfile\shell\yourverb\DropTarget]
"Clsid"="{your-guid-here}"
This registers your application as a context-menu item for text files. Change txtfile to the appropriate value for the file type you wish to handle. Note that you can also register per-user by putting these keys in HKCU\Software\Classes rather than HKCR.
Note that you shouldn’t register your assembly using regasm.exe. Regasm.exe can only be used to register in-process COM servers, not local servers. It also registers your object via the .Net COM InterOp proxy DLL which isn’t necessary for out-of-process COM.
I’ve created a small sample that shows you how to use these techniques in a GUI application: download the ShellDropTargetSample here.
That sample is a .Net 4.0 WPF application created in Visual Studio 2010, but the techniques discussed here are also usable with older versions of .Net and with Windows Forms and console applications.
Note that the sample registers the COM object and parses the command line in App.OnStartUp (in the App.xaml.cs file) rather than the Main method.
Every developer has at some point in time written code to interpret the command line arguments of their program. Of course, I'm no exception. When I was recently in need of a quick way to parse the arguments for a whole bunch of different scenarios, I decided to go the extra mile and just write a generalised class that would let you easily define what arguments you want and then parses it for you.
I've now made that class available, in the Ookii.CommandLine class library.
It's very easy to use. Just create a class, and the constructor parameters of that class, as well as properties marked with a special attribute, will make up the command line arguments of your application. It can even create help text for the command line usage of your application.
More information, documentation, downloads and samples at the link above.
Yeah, I know there's a lot of similar stuff out there already. Now there's one more. :)
A priority queue is a queue data structure where the element with the lowest (or highest) value is always at the front. A common analogy is to think of a queue of people ordered by some criteria such as age or height. Many programming languages include a priority queue in their standard library, including C++ and Java. However, the Microsoft .Net Framework does not include a priority queue, which is why I provide one here.
You might wonder why you can’t just use a sorted list to get the same effect as a priority queue. And the answer is, you can. A sorted list is a perfectly valid way to implement a priority queue. However, with a priority queue we are only interest in the smallest element at any time, and not any of the other elements, which means it is not necessary to maintain a total order of all the elements. Because of this, we can use an implementation that maintains only a partial order, which can provide better performance.
The most common such implementation is one that uses a binary heap. A binary heap is a binary tree with the property that any element in the tree is always smaller than both of its children. This guarantees that the smallest element is always on top, although the binary heap does not maintain a total order like a binary search tree. Efficient operations exist to maintain this heap property when inserting and deleting elements, making the binary heap perform better for priority queues than a sorted list. My PriorityQueue class for .Net uses a binary heap implementation.
The PriorityQueue<T>
generic class that I provide here is written to conform to the conventions used by .Net’s built-in collection classes in the System.Collections.Generic namespace, particularly the Queue<T>
class. The PriorityQueue<T>
class implements IEnumerable<T>
and ICollection
.
The three main operations provided are Enqueue
, which adds an item to the queue; Dequeue
, which removes and returns the first element of the queue; and Peek
, which returns the first element without removing it. In this implementation, these operations are O(log n), O(log n) and O(1) respectively. A special operation, AdjustFirstItem
, allows you to change the value of the first item and re-evaluate its position in the queue, in less time than it would’ve taken to remove the element and add a new one (note that this is only possible with reference types that are not immutable).
The PriorityQueue<T>
class provides several constructors, including one that allows you to create a priority queue from an existing list of elements with O(n) time complexity, faster than individually adding each element to the list with Enqueue
.
You can put any type of element in the priority queue as long as it implements IComparable<T>
, or you provide a custom IComparer<T>
for the type. Note that the PriorityQueue<T>
class will always put the smallest element at the front. If you want to have the largest element at the front, you can use the included InvertedComparer<T>
class.
A short example of how to use the PriorityQueue<T>
class is provided below.
PriorityQueue<int> queue = new PriorityQueue<int>(new[] { 4, 7, 3, 9, 12 }); // create a priority queue with the specified elements. int n = queue.Dequeue(); // returns and removes 3; the front element is now 4. queue.Enqueue(5); // the front element is still 4. queue.Enqueue(2); // the front element is now 2. n = queue.Peek(); // returns 2, but does not remove it.
Download the PriorityQueue<T>
class for .Net (class library, sample application, and source code provided).
I have made available a new download: Ookii.Dialogs.
Ookii.Dialogs is a class library that provides a number of common dialogs for use in .Net applications. The dialogs provided are the task dialog, progress dialog, credential dialog, input dialog and Vista-style common file dialogs.
The download contains two class libraries, one for Windows Forms and one for Windows Presentation Foundation (WPF). The contents are nearly identical; only the input dialog is not available for WPF. Some utility classes are provided for Windows Forms; these are not available for WPF either.
Most of these dialogs are wrappers around Windows API functionality. The TaskDialog class wraps the task dialog API provided in Windows Vista and later. The ProgressDialog class wraps the IProgressDialog API available since Windows 2000. The CredentialDialog class wraps the CredUI API introduced in Windows XP, and the VistaOpenFileDialog, VistaSaveFileDialog and VistaFolderBrowserDialog classes wrap the IFileDialog API introduced in Windows Vista. Only the InputDialog is not a wrapper; this is a custom dialog that performs the same functionality as the old Visual Basic InputBox function. Visit the link above for more details on each dialog.
Each class has been designed to be not merely a wrapper around their respective native API, but to provide a programming interface that is natural to .Net developers, with full support for the component designer. The complete source code of the class libraries, as well as documentation and a sample application are provided.
The classes aim to give the best experience possible on each OS, where applicable. In the case of the CredentialDialog class, this means that the new Vista-style dialog is automatically used on operating systems that support it (Vista and newer). The Vista-style file dialog classes will automatically fall back to the old style dialogs when using Windows XP. This is also true of the VistaFolderBrowserDialog class for WPF, even though WPF itself doesn't provide a folder browser dialog; the VistaFolderBrowserDialog class is a full folder browser dialog implementation for WPF supporting XP and newer.
This library replaces the Ookii.VistaDialogs library, which contained only the Vista-style file dialogs and didn't offer any support for WPF.
This library is a collection of classes that I have developed for personal use over the years. Because of the difference in age of some of the code, and the many modifications made over time, there may be some inconsistencies.
Let me know what you think of it, if you use it.