Drag and Drop
With the mouse, users of the WPF TextControl move selected text to another place, drag text between the editor and other applications, and drop files onto the document. The first two need at most a property or two; dropped files are handled by your code, which checks the file in DragEnter and loads it in Drop.
The examples use a window with a TextControl named textControl1, as in Create Your First WPF Application.
Move and Copy Text With the Mouse
| Mouse action | Result |
|---|---|
| Drag the selected text and drop it | Moves the text to the drop position |
| Hold down Ctrl while dropping the selected text | Copies the text to the drop position |
Both belong to the built-in mouse interface of the editor. The other mouse actions are listed on Selections and Caret Position.
Allow Dragging and Dropping
| Property | Controls |
|---|---|
| AllowDrag | Whether the editor can be the source of a drag operation, for example to drag selected text into another application |
| AllowDrop | Whether the editor accepts data dragged onto it; the property is inherited from UIElement |
| DropFormats | Which formats the editor accepts on its own: PlainText, RichTextFormat, HTMLFormat and Image |
AllowDrag and AllowDrop are set as attributes in XAML, as in the example below. DropFormats takes a combination of DropFormat values; the following line accepts text with and without formatting, but no HTML or images:
textControl1.DropFormats = DropFormat.PlainText | DropFormat.RichTextFormat;
DropFormat.Image covers device-dependent and device-independent bitmaps, TIFF images and Windows metafiles.
Accept Files From File Explorer
Files dragged from File Explorer arrive as a list of paths, which your code interprets with the WPF routed events DragEnter and Drop of the control. In MainWindow.xaml, switch dragging and dropping on and connect both events:
<Window x:Class="SampleApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WPF="clr-namespace:TXTextControl.WPF;assembly=TXTextControl.WPF"
Title="MainWindow" Height="450" Width="800">
<Grid>
<WPF:TextControl x:Name="textControl1"
AllowDrag="True"
AllowDrop="True"
DragEnter="TextControl1_DragEnter"
Drop="TextControl1_Drop" />
</Grid>
</Window>
The handlers in MainWindow.xaml.cs accept a single Word, RTF, HTML, text, TX Text Control, PDF, XML or Excel file and open it as the document. Replace SampleApp with the namespace of your project:
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using TXTextControl;
namespace SampleApp;
public partial class MainWindow : Window
{
// The file types this window accepts and their formats
private static readonly Dictionary<string, StreamType> documentFormats =
new Dictionary<string, StreamType>(StringComparer.OrdinalIgnoreCase)
{
[] = StreamType.WordprocessingML,
[] = StreamType.MSWord,
[] = StreamType.RichTextFormat,
[] = StreamType.HTMLFormat,
[] = StreamType.HTMLFormat,
[] = StreamType.PlainText,
[] = StreamType.InternalUnicodeFormat,
[] = StreamType.AdobePDF,
[] = StreamType.XMLFormat,
[] = StreamType.SpreadsheetML
};
public MainWindow()
{
InitializeComponent();
}
private static string GetDocumentFile(IDataObject data)
{
// A file dragged from File Explorer arrives as an array of paths
string[] files = data.GetData(DataFormats.FileDrop) as string[];
if (files == null || files.Length != 1)
{
return null;
}
return documentFormats.ContainsKey(Path.GetExtension(files[0])) ? files[0] : null;
}
private void TextControl1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effects = GetDocumentFile(e.Data) != null ? DragDropEffects.Copy : DragDropEffects.None;
}
}
private void TextControl1_Drop(object sender, DragEventArgs e)
{
string path = GetDocumentFile(e.Data);
if (path == null)
{
return;
}
// Replace the document with the dropped file
textControl1.Load(path, documentFormats[Path.GetExtension(path)]);
}
}
Run the application and drag a DOCX file from File Explorer onto the editor: the file replaces the current document.
The code works as follows:
documentFormatsmaps file extensions to values of StreamType, the formats the editor loads. A PDF file is imported as described in PDF Import and Its Limitations, an XLSX file shows its first worksheet, and an XML file is loaded with the DTD and the stylesheet it refers to, as XML Processing explains.GetDocumentFilereads the paths withDataFormats.FileDropof theSystem.Windowsnamespace and returns a single file with a known extension, ornull.- In
DragEnter,e.Effectstells WPF whether the file is accepted. Other data is left to the editor, which handles the formats ofDropFormatsitself. - Load replaces the content of the editor with the file.
To insert the file into the current document instead of replacing it, call Selection.Load, which puts the content at the input position:
textControl1.Selection.Load(path, documentFormats[System.IO.Path.GetExtension(path)]);
Insert Dropped Images at the Drop Point
An image file dropped from File Explorer is best inserted where the mouse was released, not loaded as a document. Add a second map for the image extensions, with the import filter index of each format, and extend the handlers:
// The image file types and their import filter indexes
private static readonly Dictionary<string, int> imageFormats =
new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
[] = 1,
[] = 2,
[] = 2,
[] = 3,
[] = 4,
[] = 5,
[] = 5,
[] = 6,
[] = 7,
[] = 8
};
private void InsertDroppedImage(string path, Point dropPoint)
{
// WPF measures in device-independent units, TX Text Control in pixels
Matrix toDevice = PresentationSource.FromVisual(textControl1).CompositionTarget.TransformToDevice;
System.Drawing.Point location = new System.Drawing.Point(
(int)(dropPoint.X * toDevice.M11), (int)(dropPoint.Y * toDevice.M22));
// Move the input position to the drop point and insert the image inline there
textControl1.InputPosition = new InputPosition(location);
textControl1.Images.Add(new TXTextControl.Image(path, imageFormats[Path.GetExtension(path)]), -1);
}
In GetDocumentFile, accept a file whose extension is in either map:
string extension = Path.GetExtension(files[0]);
return documentFormats.ContainsKey(extension) || imageFormats.ContainsKey(extension) ? files[0] : null;
In TextControl1_Drop, right after the check for null, insert an image instead of loading it:
if (imageFormats.ContainsKey(Path.GetExtension(path)))
{
InsertDroppedImage(path, e.GetPosition(textControl1));
return;
}
The code works as follows:
e.GetPosition(textControl1)returns the drop point relative to the top-left corner of the control, in the device-independent units of WPF.PointandMatrixare the WPF types ofSystem.WindowsandSystem.Windows.Media; addusing System.Windows.Media;.- The InputPosition constructor with a
System.Drawing.Pointexpects pixels from the top-left corner of the control, so the point is scaled with the display's transformation first. - The Image constructor takes the file and its import filter index, and ImageCollection.Add with
-1inserts the image inline at the input position. WPF has anImageclass of its own, so the code writesTXTextControl.Image.
The setup installs a TX Words sample that handles documents and images this way, in %USERPROFILE%\Documents\TX Text Control 34.0.NET for WPF\CSharp\TX Words\Drag And Drop. Anchoring, sizing and saving images are covered on Images.
Next Steps
- Loading and Saving Documents: the formats and options of
Load. - Clipboard Operations: copy and paste instead of dragging.
- Events: the other events of the editor.