13 Dec
2010

WPF–Copy UIElement to Clipboard as Multiple Formats

Category:UncategorizedTag: , :

So about a week ago I wrote a post showing how to Copy a WPF UIElement as an Image to the Clipboard.  In reply to that post Rob Relyea made a comment regarding supporting multiple formats on the Clipboard.  What do I mean by formats?  Well, when copying items to the Clipboard, you an specify different formats in which to copy the data, such as Text, Bitmap, HTML, XAML, and many others.

The idea behind it is that when you copy data to the Clipboard, depending on what application you are pasting into, you can support the different application supported formats.  For instance, I may copy some data from my application to the clipboard in two formats; a Bitmap and Text.  When that data is pasted into Paint, an image will be pasted.  When I paste the same data into Notepad, text will be pasted instead.  So let?s go ahead and modify the code snippet from my previously mentioned post to support two formats, an image format and a text format.

To support the Image format, I will keep the majority of the code the same.  In order to support the text format, I need to make some assumptions.  First, I need to ask myself, ?Where will the text representation of the UIElement being copied come from??.  The simplest approach I came up with is to use the DataContext of the UIElement.  I will assume that the object that is set as the DataContext of the UIElement being copied will be the text representation and will override the ToString() method to provide the text.  So let?s take a look at the new snippet.

/// <summary>
/// Copies a UI element to the clipboard as an image, and as text.
/// </summary>
/// <param name="element">The element to copy.</param>
public static void CopyUIElementToClipboard(FrameworkElement element)
{
    //data object to hold our different formats representing the element
    DataObject dataObject = new DataObject();

    //lets start with the text representation
    //to make is easy we will just assume the object set as the DataContext has the ToString method overrideen and we use that as the text
    dataObject.SetData(DataFormats.Text, element.DataContext.ToString(), true);

    //now lets do the image representation
    double width = element.ActualWidth;
    double height = element.ActualHeight;
    RenderTargetBitmap bmpCopied = new RenderTargetBitmap((int)Math.Round(width), (int)Math.Round(height), 96, 96, PixelFormats.Default);
    DrawingVisual dv = new DrawingVisual();
    using (DrawingContext dc = dv.RenderOpen())
    {
        VisualBrush vb = new VisualBrush(element);
        dc.DrawRectangle(vb, null, new Rect(new Point(), new Size(width, height)));
    }
    bmpCopied.Render(dv);
    dataObject.SetData(DataFormats.Bitmap, bmpCopied, true);

    //now place our object in the clipboard
    Clipboard.SetDataObject(dataObject, true);
}

 

Well, lets see this is action.  Rob mentioned the idea of a scenario of a ListBoxItem with a rich DataTemplate perhaps a tweet in a Twitter client.  So, I am imagining a list of Tweets; a user selects a tweet in the list, copies it (ctrl+c), and then pastes it into various applications.  If it is an image application like Paint, the image of the selected ItemTemplate should be pasted.  If it is pasted in Notepad then the tweet message should be pasted.

The first thing I want to do is create a very simple DataTempate for my ListBox containing the tweets.

<DataTemplate x:Key="TweetTemplate">
    <Border CornerRadius="5" Background="AliceBlue" BorderBrush="Black" BorderThickness="1" Padding="8">
        <StackPanel Orientation="Horizontal" >
            <Image Source="{Binding Image}" Width="73" Height="73" Margin="0,0,5,0" />
            <TextBlock Text="{Binding Message}">
                <TextBlock.Style>
                    <Style>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}"
                                        Value="True">
                                <Setter Property="TextBlock.Foreground" Value="Red" />
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </TextBlock.Style>
            </TextBlock>
        </StackPanel>
    </Border>
</DataTemplate>

 

This DataTemplate will display an Image of the Poster and the tweet message next to it.  When the item is selected I simply turn the message text red.

Next, I want to create a ListBox and bind a collection to the ItemsSource property.  In this case, I have an ObservableCollection<Tweet> used as the ItemsSource of the ListBox.  The Tweet is a simple object with two properties, Image and Message, and implements the INotifyPropertyChanged interface.

<ListBox ItemsSource="{Binding}" ItemTemplate="{StaticResource TweetTemplate}">
    <ListBox.CommandBindings>
        <CommandBinding Command="Copy" Executed="CommandBinding_Executed" CanExecute="CommandBinding_CanExecute" />
    </ListBox.CommandBindings>
</ListBox>

 

As you can see I have set the ItemTemplate to our DataTemplate we created earlier, and I have also added a CommandBinding to the ListBox for the Copy command.  Anytime the ListBox has focus, and the user hits ctrl+c, the corresponding command will execute.  now of course I don?t want the command to execute unless the is actually an item selected, so lets handle the CanExecute methods accordingly.

private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = (sender as ListBox).SelectedItem != null;
}

 

Now the next part can be a little tricky.  Basically what we want to do is get the selected ListBoxItem from the ListBox, then grab the first UIElement in the visual tree of that item.  Once we have the first UIElement in the visual tree of the selected item, we can pass that element to our copy method.

private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
????ListBox listBox = sender as ListBox;

????//Get the ListBoxItem
????ListBoxItem listItem = listBox.ItemContainerGenerator.ContainerFromIndex(listBox.SelectedIndex) as ListBoxItem;

????//Retrieve the first child which is assumed to be a framework element
????FrameworkElement element = VisualTreeHelper.GetChild(listItem, 0) as FrameworkElement;

????//now perform the copy
????CopyUIElementToClipboard(element);
}

 

Lets see this thing in action.

image

Here is what the application looks like when it is running.  It is a simple list of messages with my image next to it.  Lets go ahead and select an item, hit ctrl+c to copy it to the clip board and paste it into Paint.

image

Now lets not copy anything new to the clipboard.  I just want to take what is currently there and paste it into a different program that doesn?t support images, such as Notepad.

image

As you can see, the text representation is pasted instead of the image representation.  Of course, this was a very simple implementation and is labeled with the ?Demo Code? use policy.  Feel free to download the Source and start playing around.  Try adding new formats, or improving on the code that is there.  What ever you do, have fun with it.

Find me

RSS
Facebook
Twitter
LinkedIn
SOCIALICON
SOCIALICON

Disclaimer

The opinions and content expressed here are my own and not those of my employer.