I recently encountered an interesting situation. I am subclassing the RichTextBox control to support some syntax highlighting and custom selection mechanism unique to an application that I'm writing. The control has two modes: simple and expert. In the simple mode there are certain blocks of code that are identified by a string such as [KEYWORD]. When the user clicks in or navigates via the arrow keys into the keyword, the full keyword gets selected. However, in expert mode, the [KEYWORD] expands to something much more verbose.
Additionally, this application has a toolbar from which the user can drag and drop keywords onto the RichTextBox (which I've dubbed RichEditor). However, being an MDI application, the user might very well have multiple RichEditors each in a different mode. I need a mechanism to drag a tool from the toolbox onto the RichEditor and then make a decision as to which text to display - the [KEYWORD] version, or the expanded text.
Easy I hear you say...and in fact I would tend to agree. All you have to do is create a [Serializable()] class and pass it to the DoDragDrop() method of my toolbox item as in the following:
[Serializable()]internal sealed class KeywordItem { private string _fullText; private string _keywordText; // properties and ctor that establish and expose the // two private text properties // ...}
then within the toolbox item I might have the following:
DragDropEffects effects = this.DoDragDrop(new KeywordItem(...), DragDropEffects.Copy);
This is all fine and good. Now it's time for me to implement the code in the RichEditor to support the dragging and dropping of the appropriate text:
protected override void OnDragEnter(DragEventArgs e) { base.OnDragEnter(e); e.Effect = !e.Data.GetDataPresent(typeof(KeywordItem)) ? DragDropEffects.None : DragDropEffects.Copy;}protected override void OnDragDrop(DragEventArgs e) { base.OnDragDrop(e); KeywordItem kw = (KeywordItem)e.Data.GetData(typeof(KeywordItem)); if ( null != kw ) { int selStart = this.SelectionStart; switch ( _mode ) { case Mode.Basic: this.SelectedText = kw.KeywordText; this.Select(selStart, kw.KeywordText.Length); break; case Mode.Advanced: this.SelectedText = kw.FullText; this.Select(selStart, kw.FullText.Length); break; } }}
This gets the job done, and for the most part I might consider this functional and move on, however an astute observer of the behavior might be troubled. To begin with, usually a TextBox provides feedback of the drag/drop locationi by moving the insertion point and following the mouse. This code does not enable that functionality.
The reason for this is the TextBox-based controls expect the data being dragged to be a string (i.e. System.String) or some other text-based type. If I'm not mistaken, the RichTextBox also accepts other datatypes in the drag/drop such as Bitmap but I haven't looked into that at all. If we provide our own custom drag/drop objects as we have here, the RichTextBox recognizes that a string is not being dragged and does not move the insertion point (although the text will drop in the right place when the mouse is released).
In order to trick the RichTextBox we must tell it that we're dragging a string, but drop something else...easier said than done.
We could create our own custom object that derives from IDataObject, but that gets messy and I'm not feeling particularly masochistic right now. Instead, the developers of the .NET framework have given us a DataObject. It is recommended that we use this object rather than implementing the IDataObject interface ourselves.
The DataObject object is used for both drag/drop operations as well as the clipboard. I'm sure you're aware that the clipboard can contain items of varying types simultaneously; and that's the trick to getting this to work. We need to not only add our KeywordItem instance to the DataObject, but also a string.
Now our code at the start of the drag operation resembles the following:
DataObject dragObj = new DataObject();dragObj.SetData(””); // add the string that will allow the RichTextBox to move the insertion pointdragObj.SetData(new KeywordItem(...));DragDropEffects effects = this.DoDragDrop(dragObj, DragDropEffects.Copy);
I'd by lying if I said that the work is done. As it turns out, the RichTextBox trumps our OnDragDrop(...) operation with a string present in the DataObject. Though our code in the OnDragDrop() method runs, it will also drop the string we added to the DataObject.
Ok, so how do we work around this? The solution I came up with is to allow the RichTextBox to accept the string (which is blank so it has no noticeable effect on the RichTextBox's contents) and add our text after the fact. The easiest way to accomplish this is to use a timer with an almost imperceptible delay (such as 10-25 ms).
// temporary keyword storage for the data being// dropped on the control from the toolboxprivate KeywordItem _dragDropObj = null;protected override void OnDragDrop(DragEventArgs e) { base.OnDragDrop(e); KeywordItem kw = (KeywordItem)e.Data.GetData(typeof(KeywordItem)); if ( null != kw ) { _dragDropObj = kw; tmrDragDrop.Enabled = true; }}private void tmrDragDrop_Tick(object sender, EventArgs e) { tmrDragDrop.Enabled = false; switch ( _mode ) { // same as above except using _dragDropObj as keyword source... } _dragDropObj = null;}
Powered by: newtelligence dasBlog 2.0.7226.0
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.
© Copyright 2010R. Aaron Zupancic
E-mail