【问题标题】:WPF RichTextBox Insert Image and Undo OperationWPF RichTextBox 插入图像和撤消操作
【发布时间】:2015-09-26 15:20:07
【问题描述】:

我有将图像从剪贴板导入 RichtTextBox 的代码。

Image i = new Image();
i.Source =  Clipboard.GetImage();
paragraph.Inlines.Add(i);

当我尝试删除图像并按 Undo() 时出现异常。

在类型上找不到匹配的构造函数 'System.Windows.Interop.InteropBitmap'。您可以使用参数或 构造此类型的 FactoryMethod 指令。行号“1”和 行位置'226'。

这是因为 RichTextBox 生成的 XAML 如下所示:

<Image.Source><swi:InteropBitmap /></Image.Source>

我尝试将 BitmapSource 的类型更改为 BitmapImage。但在这种情况下,我有 XAML:

<Image.Source><BitmapImage BaseUri="{x:Null}" /></Image.Source></Image>

删除后,撤消我有异常:

抛出异常:“System.Windows.Markup.XamlParseException”在 PresentationFramework.dll

附加信息:'System.Windows.Media.Imaging.BitmapImage' 的初始化引发了异常。线 数字“1”和行位置“243”。

我什至尝试了来自:http://wpftutorial.net/InlineImagesXaml.html 的 InlineImage

<InlineImage Width="100" Height="100" Stretch="Fill">
   <![CDATA[iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAB3RJTUUH2AQP
        SFlzAAALEgAACxIB0t1+/AAAAARnQU1BAACxjwv8YQUAAAnOSURBVHjaxVcLcBvVFT1vV
        ki3Hju3GCQnGjkObONQkJkxCSIHQQGnIdEr5TFs+LaGl7RRCSUvDp8nglH4mDGQ6EwZIm=]]>
    </InlineImage>

即使在这种情况下,我在撤消/重做操作中也有异常。有没有可能不编写自己的撤消/重做操作来处理这种情况。

【问题讨论】:

    标签: c# wpf xaml richtextbox


    【解决方案1】:

    我对编辑旧答案或添加新答案感到困惑。所以我要换一个新的。

    使用下面的方法,我现在可以复制现有的图像文件并将其粘贴到 RTB 中,我现在也可以从 MSPaint、Photoshop 中复制一些未保存的图像数据并粘贴。按下保存按钮后,rtf 文件被保存并在 MSWord 中正常打开。

    Ctrl+Z 不起作用,因为图像数据在流中。我正在做。将图片复制为文件时,Ctrl+Z 没有问题。

    欢迎提出更多疑问。下面的代码是完整的,可以按原样使用。

    ImageCode.cs 用于获取存储在剪贴板中的图像

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.IO;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows;
    using System.Runtime.InteropServices;
    
    namespace WpfRichTextBox._32648134
    {
        public class ImageCode
        {
            public static ImageSource ImageFromClipboardDibAsSource()
            {
                MemoryStream ms = Clipboard.GetData("DeviceIndependentBitmap") as MemoryStream;
                if (ms != null)
                {
                    byte[] dibBuffer = new byte[ms.Length];
                    ms.Read(dibBuffer, 0, dibBuffer.Length);
    
                    BITMAPINFOHEADER infoHeader =
                        BinaryStructConverter.FromByteArray<BITMAPINFOHEADER>(dibBuffer);
    
                    int fileHeaderSize = Marshal.SizeOf(typeof(BITMAPFILEHEADER));
                    int infoHeaderSize = infoHeader.biSize;
                    int fileSize = fileHeaderSize + infoHeader.biSize + infoHeader.biSizeImage;
    
                    BITMAPFILEHEADER fileHeader = new BITMAPFILEHEADER();
                    fileHeader.bfType = BITMAPFILEHEADER.BM;
                    fileHeader.bfSize = fileSize;
                    fileHeader.bfReserved1 = 0;
                    fileHeader.bfReserved2 = 0;
                    fileHeader.bfOffBits = fileHeaderSize + infoHeaderSize + infoHeader.biClrUsed * 4;
    
                    byte[] fileHeaderBytes =
                        BinaryStructConverter.ToByteArray<BITMAPFILEHEADER>(fileHeader);
    
                    MemoryStream msBitmap = new MemoryStream();
                    msBitmap.Write(fileHeaderBytes, 0, fileHeaderSize);
                    msBitmap.Write(dibBuffer, 0, dibBuffer.Length);
                    msBitmap.Seek(0, SeekOrigin.Begin);
    
                    BitmapImage img = new BitmapImage();
                    img.BeginInit();
                    img.CacheOption = BitmapCacheOption.OnDemand;
                    img.CreateOptions = BitmapCreateOptions.DelayCreation;
                    img.StreamSource = msBitmap;
                    img.EndInit();
    
                    return img;
                }
                return null;
            }
    
            public static MemoryStream ImageFromClipboardDibAsStream()
            {
                MemoryStream ms = Clipboard.GetData("DeviceIndependentBitmap") as MemoryStream;
                if (ms != null)
                {
                    byte[] dibBuffer = new byte[ms.Length];
                    ms.Read(dibBuffer, 0, dibBuffer.Length);
    
                    BITMAPINFOHEADER infoHeader =
                        BinaryStructConverter.FromByteArray<BITMAPINFOHEADER>(dibBuffer);
    
                    int fileHeaderSize = Marshal.SizeOf(typeof(BITMAPFILEHEADER));
                    int infoHeaderSize = infoHeader.biSize;
                    int fileSize = fileHeaderSize + infoHeader.biSize + infoHeader.biSizeImage;
    
                    BITMAPFILEHEADER fileHeader = new BITMAPFILEHEADER();
                    fileHeader.bfType = BITMAPFILEHEADER.BM;
                    fileHeader.bfSize = fileSize;
                    fileHeader.bfReserved1 = 0;
                    fileHeader.bfReserved2 = 0;
                    fileHeader.bfOffBits = fileHeaderSize + infoHeaderSize + infoHeader.biClrUsed * 4;
    
                    byte[] fileHeaderBytes =
                        BinaryStructConverter.ToByteArray<BITMAPFILEHEADER>(fileHeader);
    
                    MemoryStream msBitmap = new MemoryStream();
                    msBitmap.Write(fileHeaderBytes, 0, fileHeaderSize);
                    msBitmap.Write(dibBuffer, 0, dibBuffer.Length);
                    msBitmap.Seek(0, SeekOrigin.Begin);
    
                    return msBitmap;
                }
                return null;
            }
        }
    
        public static class BinaryStructConverter
        {
            public static T FromByteArray<T>(byte[] bytes) where T : struct
            {
                IntPtr ptr = IntPtr.Zero;
                try
                {
                    int size = Marshal.SizeOf(typeof(T));
                    ptr = Marshal.AllocHGlobal(size);
                    Marshal.Copy(bytes, 0, ptr, size);
                    object obj = Marshal.PtrToStructure(ptr, typeof(T));
                    return (T)obj;
                }
                finally
                {
                    if (ptr != IntPtr.Zero)
                        Marshal.FreeHGlobal(ptr);
                }
            }
    
            public static byte[] ToByteArray<T>(T obj) where T : struct
            {
                IntPtr ptr = IntPtr.Zero;
                try
                {
                    int size = Marshal.SizeOf(typeof(T));
                    ptr = Marshal.AllocHGlobal(size);
                    Marshal.StructureToPtr(obj, ptr, true);
                    byte[] bytes = new byte[size];
                    Marshal.Copy(ptr, bytes, 0, size);
                    return bytes;
                }
                finally
                {
                    if (ptr != IntPtr.Zero)
                        Marshal.FreeHGlobal(ptr);
                }
            }
        }
    
        [StructLayout(LayoutKind.Sequential, Pack = 2)]
         struct BITMAPFILEHEADER
        {
            public static readonly short BM = 0x4d42; // BM
    
            public short bfType;
            public int bfSize;
            public short bfReserved1;
            public short bfReserved2;
            public int bfOffBits;
        }
    
        [StructLayout(LayoutKind.Sequential)]
         struct BITMAPINFOHEADER
        {
            public int biSize;
            public int biWidth;
            public int biHeight;
            public short biPlanes;
            public short biBitCount;
            public int biCompression;
            public int biSizeImage;
            public int biXPelsPerMeter;
            public int biYPelsPerMeter;
            public int biClrUsed;
            public int biClrImportant;
        }
    
    }
    

    MainWindow.xaml

    <Window x:Class="WpfRichTextBox._32648134.Win32648134"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="Win32648134" Height="600" Width="700">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="41*"/>
                <RowDefinition Height="5*"/>
                <RowDefinition Height="21*"/>
            </Grid.RowDefinitions>
            <RichTextBox x:Name="RtbCompose" Width="500" Height="300" ScrollViewer.VerticalScrollBarVisibility="Visible">
                <FlowDocument x:Name="FdDocument">
                    <Paragraph x:Name="Para1"></Paragraph>
                </FlowDocument>
            </RichTextBox>        
            <Button x:Name="BtnCopyImgFile"  Content="Paste image" HorizontalAlignment="Left" Margin="96,10,0,0" Grid.Row="1" VerticalAlignment="Top" Width="75" Click="BtnCopyImgFile_Click"/>
            <Button x:Name="BtnSave" Content="Save" HorizontalAlignment="Left" Grid.Row="1" VerticalAlignment="Top" Width="75" Margin="521,10,0,0" Click="BtnSave_Click"/>
            <Button x:Name="BtnCopyImgData" Content="Paste image data" HorizontalAlignment="Left" Margin="190,11,0,0" Grid.Row="1" VerticalAlignment="Top" Click="BtnCopyImgData_Click"/>
        </Grid>
    </Window>
    

    MainWindow.xaml.cs

    using System;
    using System.Collections.Specialized;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Shapes;
    using System.Diagnostics;
    using System.IO;
    
    namespace WpfRichTextBox._32648134
    {
        /// <summary>
        /// Interaction logic for Win32648134.xaml
        /// </summary>
        public partial class Win32648134 : Window
        {
            public Win32648134()
            {
                InitializeComponent();
            }
    
            private void BtnCopyImgFile_Click(object sender, RoutedEventArgs e)
            {              
                Image i = new Image();
    
                if (Clipboard.ContainsFileDropList())
                {
                    StringCollection fileNames = Clipboard.GetFileDropList();
                    BitmapImage img = new BitmapImage(new Uri(fileNames[0], UriKind.Absolute));
                    i.Source = img;
                    Para1.Inlines.Add(i);
                }     
    
                Para1.Inlines.Add(new Run("first rtb app"));
            }
    
            private void BtnSave_Click(object sender, RoutedEventArgs e)
            {
                TextRange allText = new TextRange(RtbCompose.Document.ContentStart, RtbCompose.Document.ContentEnd);
    
                FileStream stream = new FileStream(@"I:\RTB.rtf", FileMode.Create);
    
                allText.Save(stream, DataFormats.Rtf);
    
                if (stream != null)
                    stream.Close();
            }
    
            private void BtnCopyImgData_Click(object sender, RoutedEventArgs e)
            {
                bool hasImgData = Clipboard.ContainsImage();
                Image i = new Image();
                if (hasImgData)
                {
                    BitmapSource imgData = (BitmapSource)ImageCode.ImageFromClipboardDibAsSource();
                    i.Source = imgData;
    
                    Para1.Inlines.Add(i);
                }
    
                Para1.Inlines.Add(new Run("rtb app, image comes from image data instead of file"));
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      我找到了解决方案。我不得不从 RichTextBox 源代码中复制 WpfLoad 类。此代码将 Package 保存为 Stream,Image 作为 Content,Uri 作为文档的 Source。

      因为 WpfPayload 类是内部的,所以我无权访问此类。我需要创建自己的。

      下面是 WpfPayLoad 类的源代码。

      using System;
      using System.Collections.Generic;
      using System.IO;
      using System.IO.Packaging;
      using System.Linq;
      using System.Text;
      using System.Threading.Tasks;
      using System.Windows.Markup;
      using System.Windows.Media.Imaging;
      
      namespace YATE
      {
          internal class WpfPayload
          {
              private const string XamlPayloadDirectory = "/Xaml"; // 
              private const string XamlEntryName = "/Document.xaml"; // 
              private const string XamlContentType = "application/vnd.ms-wpf.xaml+xml";
              private const string XamlImageName = "/Image"; // 
              private const string XamlRelationshipFromPackageToEntryPart = "http://schemas.microsoft.com/wpf/2005/10/xaml/entry";
              private const string XamlRelationshipFromXamlPartToComponentPart = "http://schemas.microsoft.com/wpf/2005/10/xaml/component";
      
              internal const string ImageBmpContentType = "image/bmp";
              private const string ImageGifContentType = "image/gif";
              private const string ImageJpegContentType = "image/jpeg";
              private const string ImageTiffContentType = "image/tiff";
              private const string ImagePngContentType = "image/png";
      
              private const string ImageBmpFileExtension = ".bmp";
              private const string ImageGifFileExtension = ".gif";
              private const string ImageJpegFileExtension = ".jpeg";
              private const string ImageJpgFileExtension = ".jpg";
              private const string ImageTiffFileExtension = ".tiff";
              private const string ImagePngFileExtension = ".png";
      
              Package _package = null;
      
              private static BitmapEncoder GetBitmapEncoder(string imageContentType)
              {
                  BitmapEncoder bitmapEncoder;
      
                  switch (imageContentType)
                  {
                      case ImageBmpContentType:
                          bitmapEncoder = new BmpBitmapEncoder();
                          break;
                      case ImageGifContentType:
                          bitmapEncoder = new GifBitmapEncoder();
                          break;
                      case ImageJpegContentType:
                          bitmapEncoder = new JpegBitmapEncoder();
                          // 
      
                          break;
                      case ImageTiffContentType:
                          bitmapEncoder = new TiffBitmapEncoder();
                          break;
                      case ImagePngContentType:
                          bitmapEncoder = new PngBitmapEncoder();
                          break;
                      default:
                          bitmapEncoder = null;
                          break;
                  }
                  return bitmapEncoder;
              }
      
              // Returns a file extension corresponding to a given imageContentType
              private static string GetImageFileExtension(string imageContentType)
              {
                  string imageFileExtension;
                  switch (imageContentType)
                  {
                      case ImageBmpContentType:
                          imageFileExtension = ImageBmpFileExtension;
                          break;
                      case ImageGifContentType:
                          imageFileExtension = ImageGifFileExtension;
                          break;
                      case ImageJpegContentType:
                          imageFileExtension = ImageJpegFileExtension;
                          break;
                      case ImageTiffContentType:
                          imageFileExtension = ImageTiffFileExtension;
                          break;
                      case ImagePngContentType:
                          imageFileExtension = ImagePngFileExtension;
                          break;
                      default:
                          imageFileExtension = null;
                          break;
                  }
                  return imageFileExtension;
              }
      
              WpfPayload(Package p = null)
              {
                  this._package = p;
              }
      
              private Package CreatePackage(Stream stream)
              {
                  _package = Package.Open(stream, FileMode.Create, FileAccess.ReadWrite);
                  return _package;
              }
      
      
              // Generates a image part Uri for the given image index
              private static string GetImageName(int imageIndex, string imageContentType)
              {
                  string imageFileExtension = GetImageFileExtension(imageContentType);
                  return XamlImageName + (imageIndex + 1) + imageFileExtension;
              }
      
              // Generates a relative URL for using from within xaml Image tag.
              private static string GetImageReference(string imageName)
              {
                  return "." + imageName; // imageName is supposed to be created by GetImageName method
              }
      
              private PackagePart CreateWpfEntryPart()
              {
                  // Define an entry part uri
                  Uri entryPartUri = new Uri(XamlPayloadDirectory + XamlEntryName, UriKind.Relative);
      
                  // Create the main xaml part
                  PackagePart part = _package.CreatePart(entryPartUri, XamlContentType, CompressionOption.Normal);
                  // Compression is turned off in this mode.
                  //NotCompressed = -1,
                  // Compression is optimized for a resonable compromise between size and performance. 
                  //Normal = 0,
                  // Compression is optimized for size. 
                  //Maximum = 1,
                  // Compression is optimized for performance. 
                  //Fast = 2 ,
                  // Compression is optimized for super performance. 
                  //SuperFast = 3,
      
                  // Create the relationship referring to the entry part
                  PackageRelationship entryRelationship = _package.CreateRelationship(entryPartUri, TargetMode.Internal, XamlRelationshipFromPackageToEntryPart);
      
                  return part;
              }
      
              private void CreateImagePart(PackagePart sourcePart, BitmapSource imageSource, string imageContentType, int imageIndex)
              {
                  // Generate a new unique image part name
                  string imagePartUriString = GetImageName(imageIndex, imageContentType);
      
                  // Define an image part uri
                  Uri imagePartUri = new Uri(XamlPayloadDirectory + imagePartUriString, UriKind.Relative);
      
                  // Create a part for the image
                  PackagePart imagePart = _package.CreatePart(imagePartUri, imageContentType, CompressionOption.NotCompressed);
      
                  // Create the relationship referring from the enrty part to the image part
                  PackageRelationship componentRelationship = sourcePart.CreateRelationship(imagePartUri, TargetMode.Internal, XamlRelationshipFromXamlPartToComponentPart);
      
                  // Encode the image data
                  BitmapEncoder bitmapEncoder = GetBitmapEncoder(imageContentType);
                  bitmapEncoder.Frames.Add(BitmapFrame.Create(imageSource));
      
                  // Save encoded image data into the image part in the package
                  Stream imageStream = imagePart.GetStream();
                  using (imageStream)
                  {
                      bitmapEncoder.Save(imageStream);
                  }
              }
      
      
              internal PackagePart GetWpfEntryPart()
              {
                  PackagePart wpfEntryPart = null;
      
                  // Find a relationship to entry part
                  PackageRelationshipCollection entryPartRelationships = _package.GetRelationshipsByType(XamlRelationshipFromPackageToEntryPart);
                  PackageRelationship entryPartRelationship = null;
                  foreach (PackageRelationship packageRelationship in entryPartRelationships)
                  {
                      entryPartRelationship = packageRelationship;
                      break;
                  }
      
                  // Get a part referred by this relationship
                  if (entryPartRelationship != null)
                  {
                      // Get entry part uri
                      Uri entryPartUri = entryPartRelationship.TargetUri;
      
                      // Get the enrty part
                      wpfEntryPart = _package.GetPart(entryPartUri);
                  }
      
                  return wpfEntryPart;
              }
      
      
      
              [System.Security.SecurityCritical]
              internal static Stream SaveImage(BitmapSource bitmapSource, string imageContentType)
              {
                  MemoryStream stream = new MemoryStream();
                  // Create the wpf package in the stream
                  WpfPayload wpfPayload = new WpfPayload();
                  using (wpfPayload.CreatePackage(stream))
                  {
                      PackagePart xamlEntryPart = wpfPayload.CreateWpfEntryPart();
      
                      Stream xamlPartStream = xamlEntryPart.GetStream();
                      using (xamlPartStream)
                      {
                          int imageIndex = 0;
                          string imageReference = GetImageReference(GetImageName(imageIndex, imageContentType));
      
                          StreamWriter xamlPartWriter = new StreamWriter(xamlPartStream);
                          using (xamlPartWriter)
                          {
                              string xamlText =
                                  "<Span xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">" +
                                  "<InlineUIContainer><Image " +
                                  "Width=\"" +
                                  bitmapSource.Width + "\" " +
                                  "Height=\"" +
                                  bitmapSource.Height + "\" " +
                                  "><Image.Source><BitmapImage CacheOption=\"OnLoad\" UriSource=\"" +
                                  imageReference +
                                  "\"/></Image.Source></Image></InlineUIContainer></Span>";
                              xamlPartWriter.Write(xamlText);
                          }
                          wpfPayload.CreateImagePart(xamlEntryPart, bitmapSource, imageContentType, imageIndex);
                      }
                  }
                  return stream;
              }
      
              static int _wpfPayloadCount; // used to disambiguate between all acts of loading from different WPF payloads.
      
      
              internal static object LoadElement(Stream stream)
              {
                  Package package = Package.Open(stream, FileMode.Open, FileAccess.Read);
                  WpfPayload wpfPayload = new WpfPayload(package);
      
                  PackagePart xamlEntryPart = wpfPayload.GetWpfEntryPart();
      
      
                  int newWpfPayoutCount = _wpfPayloadCount++;
                  Uri payloadUri = new Uri("payload://wpf" + newWpfPayoutCount, UriKind.Absolute);
                  Uri entryPartUri = PackUriHelper.Create(payloadUri, xamlEntryPart.Uri); // gives an absolute uri of the entry part
                  Uri packageUri = PackUriHelper.GetPackageUri(entryPartUri); // extracts package uri from combined package+part uri
                  PackageStore.AddPackage(packageUri, wpfPayload.Package); // Register the package
                  ParserContext parserContext = new ParserContext();
                  parserContext.BaseUri = entryPartUri;
      
                  object xamlObject = XamlReader.Load(xamlEntryPart.GetStream(), parserContext);
                  // Remove the temporary uri from the PackageStore
                  PackageStore.RemovePackage(packageUri);
      
                  return xamlObject;
              }
      
              public Package Package
              {
                  get { return _package; }
              }
      
          };
      }
      

      接下来如果我们有下面的 RichTextBox:

      <RichTextBox x:Name="RtbCompose" Width="500" Height="300">
                  <FlowDocument x:Name="FdDocument">
                      <Paragraph x:Name="Para1"></Paragraph>
                  </FlowDocument>
      </RichTextBox>        
      <Button  Content="Paste image" HorizontalAlignment="Left" Margin="96,10,0,0" Grid.Row="1" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
      

      在 Button_Click 上,我们可以从剪贴板中保存我们的图像。首先我得到了包的流,接下来我们可以将其转换为 XamlReader.Load()。

       private void Button_Click(object sender, RoutedEventArgs e)
          {
              BitmapSource image = Clipboard.GetImage();
              Stream packagedImage = WpfPayload.SaveImage(image, WpfPayload.ImageBmpContentType);
              object element = WpfPayload.LoadElement(packagedImage);
              Para1.Inlines.Add(element as Span);
          }
      

      我们可以使用 XAMLPackage 保存结果。

         public byte[] SaveAllContent(RichTextBox rtb)
          {
              var content = new TextRange(rtb.Document.ContentStart, rtb_Main.Document.ContentEnd);
              using (MemoryStream ms = new MemoryStream())
              {
                  content.Save(ms, DataFormats.XamlPackage, true);
                  return ms.ToArray();
              }
      
          }
      
          public void LoadAllContent(byte [] bd, RichTextBox rtb)
          {
              var content = new TextRange(rtb.Document.ContentStart, rtb_Main.Document.ContentEnd);
              MemoryStream ms = new MemoryStream(bd);
              content.Load(ms, System.Windows.DataFormats.XamlPackage);
          }
      

      使用此解决方案 Undo() 和 Redo() 工作正常 :)

      【讨论】:

        猜你喜欢
        • 2013-06-19
        • 1970-01-01
        • 2010-09-21
        • 2013-01-09
        • 1970-01-01
        • 1970-01-01
        • 2011-01-07
        • 2010-10-07
        相关资源
        最近更新 更多