Search code examples
c#imagerichtextboxdragelementhost

C# Windows Forms: Drop a picture into a WPF RichTextBox (in ElementHost)


I have a c# Windows Forms project with a WPF RichTextBox (in ElementHost) on the form and want to drag&drop a picture from the explorer (Windows 7 x64) into it, but the cursor shows only the not-allowed-symbol. This is my code:

    private void Form1_Load(object sender, EventArgs e)
    {
        this.AllowDrop = true;
        elementHost1.AllowDrop = true;
    }

    public UserControl1()
    {
        InitializeComponent();
        Background = System.Windows.Media.Brushes.Transparent;
        this.AllowDrop = true;
        richTextBox1.AllowDrop = true;
    }

The events are subscribed using the designer. None of them are fired:

    private void richTextBox1_DragEnter(object sender, DragEventArgs e)
    {
        MessageBox.Show("Test");
    }

    private void richTextBox1_DragLeave(object sender, DragEventArgs e)
    {
        MessageBox.Show("Test");
    }

    private void richTextBox1_DragOver(object sender, DragEventArgs e)
    {
        MessageBox.Show("Test");
    }

    private void richTextBox1_Drop(object sender, DragEventArgs e)
    {
        MessageBox.Show("Test");
    }

If i use a Windows Forms RichTextBox is works, but i need a WPF RichTextBox:

    private void Form1_Load(object sender, EventArgs e)
    {
        richTextBox1.AllowDrop = true;
        richTextBox1.DragDrop += new DragEventHandler(richTextBox1_DragDrop);
    }

    private void richTextBox1_DragDrop(object sender, EventArgs e)
    {
        MessageBox.Show("Test");
    }

Solution

  • You need to use the PreviewDragEnter, PreviewDragOver and PreviewDrop events:

        public Window1()
        {
            InitializeComponent();
    
            // mainRTB is the name of my RichTextBox.
    
            mainRTB.PreviewDragEnter += new DragEventHandler(mainRTB_PreviewDragEnter);
    
            mainRTB.PreviewDragOver += new DragEventHandler(mainRTB_PreviewDragEnter);
    
            mainRTB.PreviewDrop += new DragEventHandler(mainRTB_PreviewDrop);
    
            mainRTB.AllowDrop = true;
        }
    
        static bool IsImageFile(string fileName)
        {
            return true;  // REPLACE THIS STUB WITH A REAL METHOD.
        }
    
        void mainRTB_PreviewDrop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                // Note that you can have more than one file.
                string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
                if (files != null && files.Length > 0)
                {
                    // Filter out non-image files
                    if (mainRTB.Document.PasteImageFiles(mainRTB.Selection, files.Where(IsImageFile)))
                        e.Handled = true;
                }
            }
        }
    
        void mainRTB_PreviewDragEnter(object sender, DragEventArgs e)
        {
            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
            // Filter out non-image files
            if (files != null && files.Length > 0 && files.Where(IsImageFile).Any())
            {
                // Consider using DragEventArgs.GetPosition() to reposition the caret.
                e.Handled = true;
            }
        }
    

    And then the following method pastes the images over the current selection range:

        public static bool PasteImageFiles(this FlowDocument doc, TextRange selection, IEnumerable<string> files)
        {
            // Assuming you have one file that you care about, pass it off to whatever
            // handling code you have defined.
            FlowDocument tempDoc = new FlowDocument();
            Paragraph par = new Paragraph();
            tempDoc.Blocks.Add(par);
    
            foreach (var file in files)
            {
                try
                {
                    BitmapImage bitmap = new BitmapImage(new Uri(file));
                    Image image = new Image();
                    image.Source = bitmap;
                    image.Stretch = Stretch.None;
    
                    InlineUIContainer container = new InlineUIContainer(image);
                    par.Inlines.Add(container);
                }
                catch (Exception)
                {
                    Debug.WriteLine("\"file\" was not an image");
                }
            }
    
            if (par.Inlines.Count < 1)
                return false;
    
            try
            {
                var imageRange = new TextRange(par.Inlines.FirstInline.ContentStart, par.Inlines.LastInline.ContentEnd);
                using (var ms = new MemoryStream())
                {
                    string format = DataFormats.XamlPackage;
    
                    imageRange.Save(ms, format, true);
                    ms.Seek(0, SeekOrigin.Begin);
                    selection.Load(ms, format);
    
                    return true;
                }
            }
            catch (Exception)
            {
                Debug.WriteLine("Not an image");
                return false;
            }
        }
    }
    

    Incidentally, this method avoids using the clipboard to paste the images into the RichTextBox -- one sometimes sees this done, but it isn't ideal.

    Instead of pasting over the current selection, you might want to drop the images at the current drop location. If so, start with this: Get the mouse position during drag and drop and this: How can I insert an image into a WPF RichTextBox at runtime in between text so the text floats around the image