Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allowing 'pasting data' into a WPF textbox

Tags:

c#

clipboard

wpf

I'm trying to intercept data pasted into a WPF textbox.

For example, the user creates a screen capture with the Windows snipping tool, which automatically places the image data on the clipboard. The idea here is to allow the user to simply CTRL+V on the TextBox so that I can intercept it, check if it's data and then do whatever I want with it.

public class PasteBehavior : Behavior<UIElement>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        DataObject.AddPastingHandler(AssociatedObject, new DataObjectPastingEventHandler(OnPaste));
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
    }

    private void OnPaste(object sender, DataObjectPastingEventArgs e)
    {
        if (e.SourceDataObject.GetDataPresent(DataFormats.Text))
            return;

        var formats = e.SourceDataObject.GetFormats();
        foreach (var format in formats)
            Console.WriteLine(format);
    }
}

Using the behavior above, the code does get triggered when text is pasted into the TextBox but it would seem the TextBox does not allow anything else to be pasted so it never even reaches this code if it's not text.

I'm wondering, is there a property that needs to be set on the TextBox, or something else that would allow data to be pasted (even though the TextBox can never display that data)

If not, what UI elements do allow data to be pasted, as I might be able to use that to my advantage as well.

Update Someone posted out to me that I'd have to use a RichTextBox to allow pasting
like this, which is not something I can use, so I decided to take a different (somewhat hacky) approach:

public class PasteBehavior : Behavior<UIElement>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.PreviewKeyDown += AssociatedObject_PreviewKeyDown;
    }

    void AssociatedObject_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.V)
        {
            if (Clipboard.ContainsData(DataFormats.Dib))
            {
                using (var stream = new MemoryStream())
                {
                    var image = Clipboard.GetImage();
                    var message = new ImagePastedMessage()
                    {
                        ImageData = GetImagePngData(image)
                    };

                    Messenger.Default.Send(message);
                }

                e.Handled = true;
            }
            else if (Clipboard.ContainsFileDropList())
            {
                var results = Clipboard.GetFileDropList();
                var filenames = new string[results.Count];
                results.CopyTo(filenames, 0);

                var message = new FilesDroppedMessage()
                {
                    Filenames = filenames
                };

                Messenger.Default.Send(message);
                e.Handled = true;
            }
        }
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
    }

    private byte[] GetImagePngData(BitmapSource source)
    {
        using (var stream = new MemoryStream())            
        {
            var encoder = new PngBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(source));
            encoder.Save(stream);
            return stream.ToArray();
        }
    }
}

This allows me to paste images and files into the TextBox but only using the CTRL+V keys, not using the default context menu of the TextBox.

So I'm still interested in knowing if there is a better/easier way

Update 2 Based on the solution by Daniel, which works really well, I've updated the OnAttached:

protected override void OnAttached()
{
    base.OnAttached();

    CommandManager.AddPreviewCanExecuteHandler(AssociatedObject, onPreviewCanExecute);
    CommandManager.AddPreviewExecutedHandler(AssociatedObject, onPreviewExecuted);
}

And removed the PreviewKeyDownHandler.

like image 432
TimothyP Avatar asked Dec 06 '12 03:12

TimothyP


1 Answers

You can use CommandManager.PreviewExecuted and CommandManager.PreviewCanExecute routed events to handle your pasting logic.

For example, let's suppose you want to accept an image from the clipboard when a user tries to paste it into your TextBox. So first, define the methods that will handle both events:

    private void onPreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        // In this case, we just say it always can be executed (only for a Paste command), but you can 
        // write some checks here
        if (e.Command == ApplicationCommands.Paste)
        {
            e.CanExecute = true;
            e.Handled = true;
        }
    }

    private void onPreviewExecuted(object sender, ExecutedRoutedEventArgs e)
    {
        // If it is a paste command..
        if (e.Command == ApplicationCommands.Paste)
        {
            // .. and the clipboard contains an image
            if (Clipboard.ContainsImage())
            {
                // proccess it somehow
                e.Handled = true;
            }

        }
    }

Then, you have to associate those methods with the routed events (this could go in the constructor, for example):

CommandManager.AddPreviewExecutedHandler(myTextBox, onPreviewExecuted);
CommandManager.AddPreviewCanExecuteHandler(myTextBox, onPreviewCanExecute);

And it should work with both the keyboard shortcut and the menu 'button'.

It is important to handle the PreviewCanExecute event. By default, the TextBox will only accept text as a 'pasteable' content, so you need to mark that content somehow in order to paste it.

EDIT: Also, it is a good practice to remove the 'listeners' from the event if you can. As you're using behaviors, you can do this by overriding the 'OnDetaching' method in your behavior. This could prevent memory leaks if the events are not Weak Events:

    protected override void OnDetaching()
    {
        base.OnDetaching();
        CommandManager.RemovePreviewExecutedHandler(myTextBox, onPreviewExecuted);
        CommandManager.RemovePreviewCanExecuteHandler(myTextBox, onPreviewCanExecute);
    }
like image 175
Daniel Castro Avatar answered Sep 17 '22 13:09

Daniel Castro