Programmatically add a picture merge field

10 posts, 0 answers
  1. Quincy
    Quincy avatar
    10 posts
    Member since:
    Aug 2010

    Posted 02 May 2012 Link to this post

    Hi,

    I'm having problems trying to programmatically add an IncludePictureField to my rich text box document.
    I can insert a picture field element, but when I try to preview it, it displays as an empty space.  I have set the mail merge data source.
    I have tried to set the value in the merge data source to a url and a base64 encoded image and both doesn't show up in the preview.

    The code below attempts to set the RawData on the ImageInline object which doesn't seem to work.
    IncludePictureField pictureField = new IncludePictureField();
    pictureField.ImageUri = placeholder.Name;
    pictureField.DisplayMode = FieldDisplayMode.DisplayName;                   
     
    var fragment = pictureField.GetFragmentByDisplayMode(FieldDisplayMode.DisplayName);                   
    var images = fragment.EnumerateChildrenOfType<ImageInline>();                   
    if (images.Count() == 1)
    {                       
        var imageInline = images.First();
        imageInline.RawData = placeholder.PreviewValue;                       
    }
     
    this.richTextBox.InsertField(pictureField, FieldDisplayMode.DisplayName);

    The saved xaml file extract for the above element does not write the RawData attribute:

    <t:Paragraph FontSize="14.6700000762939" LineSpacing="1" SpacingAfter="1.33333337306976">
      <t:FieldRangeStart AnnotationID="12">
        <t:IncludePictureField DisplayMode="DisplayName" ImageUri="ContactSignature" />
      </t:FieldRangeStart>
      <t:ImageInline Extension="png" Height="10" RawData="" RotateAngle="0" Width="10" />
      <t:FieldRangeEnd AnnotationID="12" />
    </t:Paragraph>

    Any ideas would be appreciated.

    Thanks













  2. Quincy
    Quincy avatar
    10 posts
    Member since:
    Aug 2010

    Posted 04 May 2012 Link to this post

    I managed to get it working by creating a custom field (inheriting CodeBasedField object and overriding it's method).  However the only way I could get the base64 string persisted is by adding it as a switch value.

    The problem now is if I click on the "Show All Fields Code" button it will display the merge field as eg. { PICTUREMERGEFIELD ContactSignature \i iVBORw0KGgoA....... }, instead of just { PICTUREMERGEFIELD ContactSignature }.  So it can look pretty ugly as the base64 string is very long.

    My custom field class code:
    public class CustomIncludePictureField : CodeBasedField
        {
            public static readonly string FieldType;
            private static readonly string InvalidImageError;
            private static readonly string ImageSourceSwitch = @"\i";
     
            private string imageUri;
            private string imageSource;
            private const string Regex_Base64Encoding = "^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$";
     
            static CustomIncludePictureField()
            {
                FieldType = "PICTUREMERGEFIELD";
                InvalidImageError = "Cannot display the image!";
                CodeBasedFieldFactory.RegisterFieldType(CustomIncludePictureField.FieldType, () => { return new CustomIncludePictureField(); });
            }
     
            public override string FieldTypeName
            {
                get
                {
                    return FieldType;
                }
            }
     
            [XamlSerializable("")]
            public string ImageUri
            {
                get
                {
                    return this.imageUri;
                }
                set
                {
                    if (this.imageUri != value)
                    {
                        this.imageUri = value;
                        base.InvalidateCode();
                    }
                }
            }
     
     
            /// <summary>
            /// This either can be a url or an encoded base64 string of an image
            /// </summary>
            public string ImageSource
            {
                get
                {
                    return this.imageSource;
                }
                set
                {
                    if (this.imageSource != value)
                    {
                        this.imageSource = value;
                        base.InvalidateCode();
                    }
                }
            }
     
            protected override void BuildCodeOverride()
            {
                if (!string.IsNullOrEmpty(this.ImageUri))
                {
                    base.CodeBuilder.SetFieldArgument(this.ImageUri);
                }
                if (!string.IsNullOrEmpty(this.ImageSource))
                {
                    base.CodeBuilder.AddSwitch(ImageSourceSwitch, this.ImageSource);
                }
            }
     
            public override void CopyPropertiesFrom(Field fromField)
            {
                base.CopyPropertiesFrom(fromField);
                CustomIncludePictureField field = (CustomIncludePictureField)fromField;
                this.ImageUri = field.ImageUri;
                this.ImageSource = field.ImageSource;
            }
     
            protected override void CopyPropertiesFromCodeExpression(Telerik.Windows.Documents.Model.Fields.CodeExpressions.FieldCodeExpression fieldCodeExpression)
            {
                if (fieldCodeExpression.FieldArgumentNode != null)
                {
                    this.ImageUri = fieldCodeExpression.FieldArgumentNode.Text;
                }
                else
                {
                    this.ImageUri = null;
                }
     
                FieldSwitchNode fieldSwitch = fieldCodeExpression.GetFieldSwitch(ImageSourceSwitch);
                if (fieldSwitch != null)
                {
                    this.ImageSource = fieldSwitch.FieldArgumentNode.Text;
                }
            }
     
            public override Field CreateInstance()
            {
                return new CustomIncludePictureField();
            }
     
            protected override DocumentFragment GetResultFragment()
            {
                ImageInline inline = null;
                if (!string.IsNullOrEmpty(this.ImageUri))
                {
                    Uri uri;
                    //if (base.Document != null)
                    //{
                    //    StreamFromUriResolvingEventArgs e = new StreamFromUriResolvingEventArgs(this.ImageUri);
                    //    base.Document.OnStreamFromUriResolving(e);
                    //    if (e.Stream != null)
                    //    {
                    //        inline = new ImageInline(e.Stream);
                    //    }
                    //}
     
                    if ((inline == null) && !string.IsNullOrEmpty(this.ImageSource))
                    {
                        // The preview value is stored as an encoded base64 string
                        if (Regex.IsMatch(this.ImageSource, Regex_Base64Encoding))
                        {
                            inline = new ImageInline() { RawData = this.ImageSource };
                        }
                        else
                        {
                            if (Uri.TryCreate(this.ImageSource, UriKind.RelativeOrAbsolute, out uri))
                            {
                                // Assume else the preview value is a well formed url
                                inline = new ImageInline(uri);
                            }
                        }
                    }
     
                    if ((inline == null) && Uri.TryCreate(this.ImageUri, UriKind.RelativeOrAbsolute, out uri))
                    {
                        inline = new ImageInline(uri);
                    }
                }
                if (inline != null)
                {
                    return DocumentFragment.CreateFromInline(inline);
                }
                return base.CreateFragmentFromText(InvalidImageError);
     
            }
        }

    I programmatically add the field by using the following code, where ImageSource is my base64 string.

    if (string.Compare(placeholder.DataType, "Image") == 0)
    {
        CustomIncludePictureField pictureField = new CustomIncludePictureField();
        pictureField.ImageUri = placeholder.Name;
        pictureField.ImageSource = placeholder.PreviewValue;
                
        this.richTextBox.ActiveDocumentEditor.Document.InsertField(pictureField, FieldDisplayMode.DisplayName);                   
    }

    How I can get the code displaying properly.

    Thanks.





  3. DevCraft banner
  4. Andrew
    Admin
    Andrew avatar
    140 posts

    Posted 04 May 2012 Link to this post

    Hi Quincy,

    To programatically add a picture you need to first create an object of type IncludePictureField and set its ImageUri property. You should also attach to the StreamFromUriResolving event of RadRichTextBox and create a stream which you should then assign to the Stream property of StreamFromUriResolvingEventArgs. We created a sample project illustrating the described approach.

    All the best,
    Andrew
    the Telerik team

    Explore the entire Telerik portfolio by downloading the Ultimate Collection trial package. Get it now >>

  5. Quincy
    Quincy avatar
    10 posts
    Member since:
    Aug 2010

    Posted 07 May 2012 Link to this post

    Appreciate your response.

    However the difference is that my image resources are either stored on the file system or on in SharePoint, which means I need to make an async call to retrieve the resource.
    On the async callback setting the Stream doesn't seem to show the picture.  I'm using RIA domain services to retrieve the resource.

    My StreamFromUriResolving event code:

    void richTextBox_StreamFromUriResolving(object sender, StreamFromUriResolvingEventArgs e)
    {
        Uri uri = null;
     
        if (Uri.TryCreate(e.Uri, UriKind.RelativeOrAbsolute, out uri))
        {
            FileRetrieverContext client = new FileRetrieverContext();
            client.GetFile(uri, (callback) =>
            {
                if (callback.Error == null)
                {
                    e.Stream = new MemoryStream(callback.Value);
                    this.richTextBox.ActiveDocumentEditor.UpdateAllFields();
                }
            }, null);
        }
    }


  6. Ivailo Karamanolev
    Admin
    Ivailo Karamanolev avatar
    276 posts

    Posted 08 May 2012 Link to this post

    Hello,

    Unfortunately, async loading on this event is not supported, as the field has to return an image immediately. The suggested way to implement this correctly is to cache the image bytes somewhere after it loads and call UpdateAllFields to refresh the images. In the handler of the event you should return the cached image synchronously and it should get displayed correctly.
    Let us know if that doesn't work out for you.

    All the best,
    Ivailo Karamanolev
    the Telerik team

    Explore the entire Telerik portfolio by downloading the Ultimate Collection trial package. Get it now >>

  7. Quincy
    Quincy avatar
    10 posts
    Member since:
    Aug 2010

    Posted 08 May 2012 Link to this post

    Got it working the way you suggested by caching the image byte array.

    However if you save the document, close the application and restartthe application, re-open the document, pressing the preview results button in the rich text box fires the StreamFromUriResolving event where the cache is now empty.

    Looking at the xaml source of the document, the ImageInline RawData is set, but clicking on the preview button fires the StreamFromUriResolving event instead eventhough the RawData is there. 

    Can you suggest a workaround for this?

    Thanks.
  8. Ivailo Karamanolev
    Admin
    Ivailo Karamanolev avatar
    276 posts

    Posted 11 May 2012 Link to this post

    Hello,

    I understand the issue you are experiencing, however, unfortunately, there is no direct workaround for it. The logic you have in place to cache the image byte array and update the field again once it's loaded will need to be executed every time the application is started. Even if the image is already populated, the current field logic will throw it away and re-update the field anew. In this light, you'll have to load the image (from the web or the cache) on every update.
    Let us know if that doesn't work out for you.

    Regards,
    Ivailo Karamanolev
    the Telerik team

    Explore the entire Telerik portfolio by downloading the Ultimate Collection trial package. Get it now >>

  9. Quincy
    Quincy avatar
    10 posts
    Member since:
    Aug 2010

    Posted 03 Jun 2012 Link to this post

    Thanks for your help.

    I've managed to get the images appearing.  However I've encounted another issue where I can't add the image field to the header/footer sections of the rich text box.

    I'm inserting the field to the ActiveDocumentEditor property on the RichTextBox object. However adding text merge fields works fine in the headers/footers.

    Eg. Modified code from the sample you sent (SilverlightApplication194.zip).

    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            this.editor.StreamFromUriResolving += editor_StreamFromUriResolving;
            List<Person> persons = new List<Person>()
            {
                new Person() {Name = "Nancy", ImagePath="female1.png"},
                new Person() {Name = "David", ImagePath="male2.png"}
            };
            this.editor.Document.MailMergeDataSource.ItemsSource = persons;
        }
     
        void editor_StreamFromUriResolving(object sender, StreamFromUriResolvingEventArgs e)
        {
            if (e.Uri == "ImagePath")
            {
                Person currentRecord = (Person)this.editor.Document.MailMergeDataSource.CurrentItem;
                if (currentRecord != null)
                {
                    Uri currentRecordPhotoUri = new Uri("/SilverlightApplication194;component/" + currentRecord.ImagePath, UriKind.Relative);
                    StreamResourceInfo streamResourceInfo = Application.GetResourceStream(currentRecordPhotoUri);
                    e.Stream = streamResourceInfo.Stream;
                }
            }
        }
     
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            MergeField mergeField = new MergeField();
            mergeField.PropertyPath = "Name";
            this.editor.ActiveDocumentEditor.InsertField(mergeField);
        }
     
        private void button2_Click(object sender, RoutedEventArgs e)
        {
            this.editor.ChangeAllFieldsDisplayMode(FieldDisplayMode.DisplayName);
        }
     
        private void button3_Click(object sender, RoutedEventArgs e)
        {
            IncludePictureField includePictureField = new IncludePictureField();
            includePictureField.ImageUri = "ImagePath";
            this.editor.ActiveDocumentEditor.InsertField(includePictureField);
        }
    }
     
    public class Person
    {
        public string Name { get; set; }
        public string ImagePath { get; set; }
    }
  10. Martin Ivanov
    Admin
    Martin Ivanov avatar
    104 posts

    Posted 07 Jun 2012 Link to this post

    Hello Quincy,

    Your code seems to be right. The problem is that there is a but with picture merge fields in the header.

    We investigated about this issue and it seems that there is currently no workaround to have merge field image in header or footer. We are sorry for the inconvenience and will be working on this problem as soon as possible to have it fixed. 

    Kind regards,
    Martin
    the Telerik team

    Explore the entire Telerik portfolio by downloading the Ultimate Collection trial package. Get it now >>

  11. Martin Ivanov
    Admin
    Martin Ivanov avatar
    104 posts

    Posted 08 Jun 2012 Link to this post

    Hi Quincy, 

    As a follow-up on the issue, in the Q2 release, which will go live some time next week, this scenario could be implemented using the new nested merge fields feature. If you update your version once the release is out, you'll be able to implement it the following way:

    Create a button to insert a merge field at the caret position with this code:

     MergeField mf = new MergeField();
     mf.PropertyPath = "RecipientPhoto";
     IncludePictureField picField = new IncludePictureField();
     picField.SetPropertyValue(IncludePictureField.ImageUriProperty, mf);
     this.editor.InsertField(picField);

    having RecipientPhoto field in the mail merge data source in the following format:

    RecipientPhoto = @"/MailMergeDemo;component/Images/male1.png",

    I am attaching a sample project which you will be able to use with the next version of the controls. Don't hesitate to contact us if you have other questions.

    All the best,
    Martin
    the Telerik team

    Explore the entire Telerik portfolio by downloading the Ultimate Collection trial package. Get it now >>

Back to Top
DevCraft banner