TX Text Control .NET for WPF

Drag and Drop

Topic

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 actionResult
Drag the selected text and drop itMoves the text to the drop position
Hold down Ctrl while dropping the selected textCopies 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

PropertyControls
AllowDragWhether the editor can be the source of a drag operation, for example to drag selected text into another application
AllowDropWhether the editor accepts data dragged onto it; the property is inherited from UIElement
DropFormatsWhich 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)
        {
            [".docx"] = StreamType.WordprocessingML,
            [".doc"] = StreamType.MSWord,
            [".rtf"] = StreamType.RichTextFormat,
            [".htm"] = StreamType.HTMLFormat,
            [".html"] = StreamType.HTMLFormat,
            [".txt"] = StreamType.PlainText,
            [".tx"] = StreamType.InternalUnicodeFormat,
            [".pdf"] = StreamType.AdobePDF,
            [".xml"] = StreamType.XMLFormat,
            [".xlsx"] = 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:

  • documentFormats maps 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.
  • GetDocumentFile reads the paths with DataFormats.FileDrop of the System.Windows namespace and returns a single file with a known extension, or null.
  • In DragEnter, e.Effects tells WPF whether the file is accepted. Other data is left to the editor, which handles the formats of DropFormats itself.
  • 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)
    {
        [".bmp"] = 1,
        [".tif"] = 2,
        [".tiff"] = 2,
        [".wmf"] = 3,
        [".png"] = 4,
        [".jpg"] = 5,
        [".jpeg"] = 5,
        [".gif"] = 6,
        [".emf"] = 7,
        [".svg"] = 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. Point and Matrix are the WPF types of System.Windows and System.Windows.Media; add using System.Windows.Media;.
  • The InputPosition constructor with a System.Drawing.Point expects 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 -1 inserts the image inline at the input position. WPF has an Image class of its own, so the code writes TXTextControl.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