简体   繁体   中英

XAML WPF How to add inline background image on FlowDocument?

The following code is to add background image to a Flow Document

<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
  <FlowDocument.Background>
    <ImageBrush ImageSource="C:\licorice.jpg" />
  </FlowDocument.Background>
  <Paragraph>
    <Run>Hello World!</Run>
  </Paragraph>
</FlowDocument>

The question is, how to change the ImageSource so it stores the image data as string on the xaml file ? ImageBrush is sealed so I cannot derive from it. I am looking for something like this:

<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
  <FlowDocument.Background>
    <InlineImageBrush base64Source="iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCA..." /> <!--does not work-->
  </FlowDocument.Background>
  <Paragraph>
    <Run>Hello World!</Run>
  </Paragraph>
</FlowDocument>

You can do this through the following mechanism (desktop WPF):

  1. Create a custom DependencyObject subclass with one DependencyProperty - the base 64 image string. Call your class, eg, ImageData . Having done this, you can add named instances of the class to your FlowDocument.Resources or Window.Resources (or Grid.Resources or whatever), with the base64 string initialized directly in the XAML.

  2. Create a custom IValueConverter that converts a base64 string to a BitmapImage . Add this to your Window.Resources as a named static resource.

  3. For each image you want to use as a flow document background, add an ImageData to the static resources of the flow document itself, or a higher level control such as the window. (Note -- on my older version of Visual Studio the forms designer became confused if the image resource was added to the flow document itself. Nevertheless, the application compiled and ran successfully.)

  4. Finally, add a DataBinding for the Background.ImageBrush.ImageSource , linking it to the base 64 string property of your named ImageData resource and using your custom converter to convert it to an image.

The details are as follows. Firstly, the custom ImageData class is quite simple:

public class ImageData : DependencyObject
{
    public static readonly DependencyProperty Base64ImageDataProperty =
        DependencyProperty.Register("Base64ImageData",
        typeof(string),
        typeof(ImageData));

    public string Base64ImageData
    {
        get { return (string)(GetValue(Base64ImageDataProperty)); }
        set { SetValue(Base64ImageDataProperty, value); }
    }
}

Next, the custom converter and some helper utilities to go with it:

public class Base64ImageConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string base64String = value as string;
        if (base64String == null)
            return null;
        return ImageHelper.Base64StringToBitmapImage(base64String);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

public static class ImageHelper
{
    public static BitmapImage Base64StringToBitmapImage(string base64String)
    {
        BitmapImage bitmapImage = new BitmapImage();
        bitmapImage.BeginInit();
        bitmapImage.StreamSource = new MemoryStream(Convert.FromBase64String(base64String));
        bitmapImage.EndInit();
        return bitmapImage;
    }

    public static string FileToBase64String(string filename)
    {
        using (var stream = File.Open(filename, FileMode.Open))
        using (var reader = new BinaryReader(stream))
        {
            byte[] allData = reader.ReadBytes((int)reader.BaseStream.Length);
            return Convert.ToBase64String(allData);
        }
    }
}

Place it in the static resources for the window, or the app, or some other central location that's convenient for you and can be reused throughout your application:

<Window x:Class="RichTextBoxInputPanel.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:w="clr-namespace:RichTextBoxInputPanel" 
    Title="RichTextBoxInputPanel" Height="600" Width="1200" Loaded="Window_Loaded">
    <Window.Resources>
        <w:Base64ImageConverter x:Key="Base64ImageConverter"></w:Base64ImageConverter>
    </Window.Resources>

You can also add it to the static resources of the flow document itself, alongside the ImageData shown below.

Now, add an ImageData to the resources of your flow document:

            <FlowDocument >
                <FlowDocument.Resources>
                    <w:ImageData x:Key="DocumentBackground" Base64ImageData="iVBORw0K...etc etc">
                    </w:ImageData>
                </FlowDocument.Resources>

Lastly, add the binding property for the background:

               <FlowDocument.Background>
                    <ImageBrush>
                        <ImageBrush.ImageSource>
                            <Binding Converter="{StaticResource Base64ImageConverter}" 
                                     Source="{StaticResource DocumentBackground}"
                                     Path="Base64ImageData" Mode="OneWay"></Binding>
                        </ImageBrush.ImageSource>
                    </ImageBrush>
                </FlowDocument.Background>

Finally, as I mentioned above putting the static ImageData resource on the flow document itself causes the WPF forms designer (on VS2008) to throw up a spurious error. Despite the error the application compiles and runs successfully. Moving the ImageData static resource from the flow document to a higher control such as the RichTextBox or FlowDocumentReader that contains it resolves the issue.

After multiple refining, here is the simplest way to add inline background image. Thank you @dbc for the converter.

<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:ns1="MyProject;assembly=MyProject"
              xmlns:system="clr-namespace:System;assembly=mscorlib"
              >
  <FlowDocument.Resources>
        <ns1:Base64ImageConverter x:Key="base64ImageConverter" ></ns1:Base64ImageConverter>
        <system:String x:Key="backgroundImage">/9j/4AAQSkZJRgABAQAAAQAB...</system:String>
  </FlowDocument.Resources>
  <FlowDocument.Background>
    <ImageBrush>
        <ImageBrush.ImageSource>
            <Binding Converter="{StaticResource base64ImageConverter}" 
                     Source="{StaticResource backgroundImage}"
                     Mode="OneWay"></Binding>
        </ImageBrush.ImageSource>
    </ImageBrush>
  </FlowDocument.Background>
  <Paragraph>
    <Run>Hello World!</Run>
  </Paragraph>
</FlowDocument>

And here is the converter

using System;
using System.IO;
using System.Windows.Data;
using System.Windows.Media.Imaging;

namespace MyProject
{
    public class Base64ImageConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string base64String = value as string;
            if (base64String == null)
                return null;
            BitmapImage bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.StreamSource = new MemoryStream(System.Convert.FromBase64String(base64String));
            bitmapImage.EndInit();
            return bitmapImage;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

It works with just these two piece of code. And yes, you can bind system:String with Mode=OneWay.

According to Documentation the ImageBrush class is sealed and cannot be extended. Documentation says that if the ImageBrush fails to load the ImageSource an ImageBrush.ImageFailed event is called. Please see here: Documentation

You can register your handler in XAML like that: <ImageBrush ImageFailed="eventhandler"/> Your handler just needs to read the value of ImageBrush.ImageSource, parse the String to an BitmapImage Object like shown here in another question and set the BitmapImage object to ImageSource like this: yourImageBrush.ImageSource=Base64StringToBitmap(ImageBrush.ImageSource.GetValue.ToString()) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM