vbAccelerator - Contents of code file: DragDropTextBox.csusing System;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using vbAccelerator.Components.ImageList;
namespace vbAccelerator.Controls.TextBox
{
/// <summary>
/// An enhanced text box which supports drag and drop
/// of the text contents.
/// </summary>
public class DragDropTextBox : System.Windows.Forms.TextBox
{
/// <summary>
/// Maintains the start position of the last selection
/// made with the mouse.
/// </summary>
protected int lastSelStart = -1;
/// <summary>
/// Maintains the length of the last selection made with
/// the mouse.
/// </summary>
protected int lastSelLength = -1;
/// <summary>
/// The ImageListDrag class which is used when the
/// DrawDragImage property is set.
/// </summary>
protected ImageListDrag imageListDrag = new ImageListDrag();
/// <summary>
/// Whether the drag image is drawn or not.
/// </summary>
protected bool drawDragImage = false;
/// <summary>
/// Whether a mouse down event is a candidate for a drag event.
/// </summary>
protected bool dragCandidateAction = false;
/// <summary>
/// The point at which the mouse down for a dragCandidateAction occurred.
/// </summary>
protected Point dragDownPoint;
/// <summary>
/// Flag set when initially showing the custom drag image so we can
/// ensure the TextBox isn't corrupted.
/// </summary>
private bool refreshControl = false;
/// <summary>
/// Gets/sets whether a drag image will be created and
/// drawn during dragging or not. Defaults to <code>False</code>.
/// </summary>
public bool DrawDragImage
{
get
{
return this.drawDragImage;
}
set
{
this.drawDragImage = value;
}
}
private bool DoDrawDragImage
{
get
{
return ((this.drawDragImage) && (this.ImageList != null));
}
}
/// <summary>
/// Gets/sets the ImageList to use to construct the custom
/// drag image.
/// </summary>
public System.Windows.Forms.ImageList ImageList
{
get
{
return this.imageListDrag.Imagelist;
}
set
{
this.imageListDrag.Imagelist = value;
}
}
private void constructDragImage()
{
System.Windows.Forms.ImageList ils = this.ImageList;
// Clear images in image list:
ils.Images.Clear();
// ImageList is buggy, need to ensure we do this:
IntPtr ilsHandle = ils.Handle;
// Create the bitmap to hold the drag image:
Bitmap bitmap = new Bitmap(ils.ImageSize.Width, ils.ImageSize.Height);
// Get a graphics object from it:
Graphics gfx = Graphics.FromImage(bitmap);
// Default fill the bitmap with black:
gfx.FillRectangle(Brushes.Black, 0, 0, bitmap.Width, bitmap.Height);
// Draw text in highlighted form:
StringFormat fmt = new StringFormat(StringFormatFlags.LineLimit);
fmt.Alignment = StringAlignment.Center;
SizeF size = gfx.MeasureString(this.SelectedText, this.Font,
bitmap.Width, fmt);
float left = 0F;
if (size.Height> bitmap.Height)
{
size.Height = bitmap.Height;
}
if (size.Width < bitmap.Width)
{
left = (bitmap.Width - size.Width)/2F;
}
RectangleF textRect = new RectangleF(
left, 0F, size.Width, size.Height);
gfx.FillRectangle(SystemBrushes.Highlight, textRect);
gfx.DrawString(this.SelectedText, this.Font,
SystemBrushes.HighlightText,
textRect, fmt);
fmt.Dispose();
// Add the image to the ImageList:
ils.Images.Add(bitmap, Color.Black);
// Clear up the graphics object:
gfx.Dispose();
// Clear up the bitmap:
bitmap.Dispose();
Trace.Assert(ils.Images.Count > 0, "No images in drag image list!!!");
}
/// <summary>
/// Raises the MouseDown event and prepares to start a drag operation
/// if appropriate.
/// </summary>
/// <param name="e">A <code>MouseEventArgs</code> object
/// with the details of the mouse event.</param>
protected override void OnMouseDown(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left &&
(this.SelectionStart >= this.lastSelStart) &&
(this.SelectionStart <= this.lastSelStart + this.lastSelLength) &&
this.lastSelLength > 0)
{
base.OnMouseDown(e);
this.SelectionStart = this.lastSelStart;
this.SelectionLength = this.lastSelLength;
this.Update();
this.dragDownPoint = new Point(e.X, e.Y);
this.dragCandidateAction = true;
}
else
{
base.OnMouseDown(e);
this.lastSelStart = this.SelectionStart;
this.lastSelLength = this.SelectionLength;
this.dragCandidateAction = false;
}
}
/// <summary>
/// Raises the MouseMove event and starts a drag
/// operation if appropriate.
/// </summary>
/// <param name="e">A <code>MouseEventArgs</code> object
/// with the details of the mouse event.</param>
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.Button == MouseButtons.Left &&
this.dragCandidateAction &&
((Math.Abs(e.X - this.dragDownPoint.X) > 4) ||
(Math.Abs(e.Y - this.dragDownPoint.Y) > 4)))
{
this.dragCandidateAction = false;
this.SelectionStart = this.lastSelStart;
this.SelectionLength = this.lastSelLength;
if (DoDrawDragImage)
{
constructDragImage();
Trace.Assert(this.ImageList.Images.Count > 0, "No images in drag
image list!!!");
this.imageListDrag.StartDrag(0,
this.imageListDrag.Imagelist.ImageSize.Width / 2,
-this.imageListDrag.Imagelist.ImageSize.Height);
this.refreshControl = true;
}
this.DoDragDrop(this.SelectedText, DragDropEffects.Move |
DragDropEffects.Copy);
if (DoDrawDragImage)
{
this.imageListDrag.CompleteDrag();
}
}
}
/// <summary>
/// Raises the MouseUp event and stores some information
/// about the mouse up position to track whether the next
/// mouse operation should initiate a drag.
/// </summary>
/// <param name="e">A <code>MouseEventArgs</code> object
/// with the details of the mouse event.</param>
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if (this.dragCandidateAction)
{
this.SelectionLength = 0;
this.lastSelLength = 0;
}
else
{
this.lastSelStart = this.SelectionStart;
this.lastSelLength = this.SelectionLength;
}
this.dragCandidateAction = false;
}
/// <summary>
/// Raises the <code>DragOver</code> event, and if the client
/// sets the <code>Effect</code> member to allow
/// the data to be dropped, attempts to show
/// the caret in the position at which the item
/// would The standard System.Windows.Forms drag-drop functionality
provides a cursor to indicate that a drag-drop function is in progress.
This article demonstrates how to add an image of the object being
dragged to the drag-drop control, in the same way that Explorer does,
using the ImageList APIs.be dropped.
/// </summary>
/// <param name="e">A <code>DragEventArgs</code> object containing
/// the data associated with the <code>DragOver</code> event.</param>
protected override void OnDragOver(DragEventArgs e)
{
base.OnDragOver(e);
if (e.Effect != DragDropEffects.None)
{
// need to position the caret according to the drag
// position (this used to be automatic in VB)
Point pt = this.PointToClient(new Point(e.X, e.Y));
int index = TextBoxDragDropHelper.CharFromPos(this, pt);
this.SelectionStart = index;
this.SelectionLength = 0;
TextBoxDragDropHelper.ShowCaret(this);
}
}
/// <summary>
/// Raises the GiveFeedback event and displays the
/// customised drag image, if any.
/// </summary>
/// <param name="e">The details associated with the
/// GiveFeedback event.</param>
protected override void OnGiveFeedback(GiveFeedbackEventArgs e)
{
// Raise the GiveFeedback event
base.OnGiveFeedback(e);
// Draw the drag image:
if (DoDrawDragImage)
{
if (this.refreshControl)
{
imageListDrag.HideDragImage(true);
this.Update();
imageListDrag.HideDragImage(false);
this.refreshControl = false;
}
imageListDrag.DragDrop();
}
}
/// <summary>
/// Raises the <code>DragDrop</code> event and if the client
/// sets the <code>Effect</code> member to allow
/// the data to be dropped, calls the <code>GetTextDropData</code>
/// method to retrieve the text from the drag-drop data and inserts
/// it into the control at the drag-drop point.
/// </summary>
/// <param name="e">A <code>DragEventArgs</code> object containing
/// the data associated with the <code>DragDrop</code> event.</param>
protected override void OnDragDrop(DragEventArgs e)
{
base.OnDragDrop(e);
this.Focus();
if (e.Effect != DragDropEffects.None)
{
string data = GetTextDropData(e);
if (e.Effect == DragDropEffects.Move)
{
int dropPos = this.SelectionStart;
if (this.lastSelStart > dropPos)
{
// if the original text position is after the point
// we're dropping to, then we can just delete the
// original text and drop again:
this.SelectionStart = this.lastSelStart;
this.SelectionLength = this.lastSelLength;
this.SelectedText = "";
this.SelectionStart = dropPos;
this.SelectionLength = 0;
this.SelectedText = data;
}
else
{
// Once we've removed the text at the original
// position, the drop pos is going to move up
// by the number of characters in the original
// selection:
this.SelectionStart = this.lastSelStart;
this.SelectionLength = this.lastSelLength;
dropPos -= this.lastSelLength;
this.SelectedText = "";
this.SelectionStart = dropPos;
this.SelectionLength = 0;
this.SelectedText = data;
}
}
else
{
this.SelectedText = data;
}
this.Invalidate();
}
this.lastSelStart = -1;
this.lastSelLength = -1;
}
/// <summary>
/// Gets the text data for the drag event args. The preferred
/// format to use is <code>UnicodeText</code>, followed by
/// <code>StringFormat</code>. If neither of these are present
/// then the return from <code>ToString</code> on the first format
/// returned by the <code>GetFormats</code> method of the
/// <code>DragEventArgs</code> <code>Data</code> object is used.
/// </summary>
/// <param name="e">The event details associated with the
/// DragDrop event.</param>
/// <returns>A string containing the text to drop.</returns>
protected virtual string GetTextDropData(DragEventArgs e)
{
string[] formats = e.Data.GetFormats();
string useFormat = "";
bool firstTime = true;
foreach (string format in formats)
{
if (firstTime)
{
useFormat = format;
firstTime = false;
}
else
{
if (format.Equals(DataFormats.UnicodeText))
{
useFormat = format;
break;
}
else if (format.Equals(DataFormats.StringFormat))
{
useFormat = format;
}
}
}
object data = e.Data.GetData(useFormat);
return data.ToString();
}
}
/// <summary>
/// Wraps API calls for access to missing functionality
/// from the System.Windows.Forms text box.
/// </summary>
public class TextBoxDragDropHelper
{
#region Unmanaged Code
[DllImport("user32", CharSet=CharSet.Auto, EntryPoint="SendMessage")]
private extern static int SendMessageInt(
IntPtr handle,
int msg,
int wParam,
int lParam
);
private const int EM_LINEINDEX = 0x00BB;
private const int EM_POSFROMCHAR = 0x00D6;
private const int EM_CHARFROMPOS = 0x00D7;
[DllImport("user32", EntryPoint="ShowCaret")]
private extern static bool ShowCaretAPI (
IntPtr hwnd );
#endregion
/// <summary>
/// Attempts to make the caret visible in a TextBox control.
/// This will not always succeed since the TextBox control
/// appears to destroy its caret fairly frequently.
/// </summary>
/// <param name="txt">The text box to show the caret in.</param>
public static void ShowCaret(
System.Windows.Forms.TextBox txt
)
{
bool ret = false;
int iter = 0;
while (!ret && iter < 10)
{
ret = ShowCaretAPI(txt.Handle);
iter++;
}
}
/// <summary>
/// Returns the index of the character under the specified
/// point in the control, or the nearest character if there
/// is no character under the point.
/// </summary>
/// <param name="txt">The text box control to check.</param>
/// <param name="pt">The point to find the character for,
/// specified relative to the client area of the text box.</param>
/// <returns></returns>
public static int CharFromPos(
System.Windows.Forms.TextBox txt,
Point pt
)
{
unchecked
{
// Convert the point into a DWord with horizontal position
// in the loword and vertical position in the hiword:
int xy = (pt.X & 0xFFFF) + ((pt.Y & 0xFFFF) << 16);
// Get the position from the text box.
int res = SendMessageInt(txt.Handle, EM_CHARFROMPOS, 0, xy);
// the Platform SDK appears to be incorrect on this matter.
// the hiword is the line number and the loword is the index
// of the character on this line
int lineNumber = ((res & 0xFFFF) >> 16);
int charIndex = (res & 0xFFFF);
// Find the index of the first character on the line within
// the control:
int lineStartIndex = SendMessageInt(txt.Handle, EM_LINEINDEX,
lineNumber, 0);
// Return the combined index:
return lineStartIndex + charIndex;
}
}
/// <summary>
/// Returns the position of the specified character
/// </summary>
/// <param name="txt">The text box to find the character in.</param>
/// <param name="charIndex">The index of the character whose
/// position needs to be found.</param>
/// <returns>The position of the character relative to the client
/// area of the control.</returns>
public static Point PosFromChar(
System.Windows.Forms.TextBox txt,
int charIndex
)
{
unchecked
{
int xy = SendMessageInt(txt.Handle, EM_POSFROMCHAR, charIndex, 0);
return new Point(xy);
}
}
// private constructor, methods are static
private TextBoxDragDropHelper()
{
// intentionally left blank
}
}
}
|
|