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
9 Answers, 1 is accepted
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.
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 >>
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
);
}
}
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 >>
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.
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.
Ivailo Karamanolev
the Telerik team
Explore the entire Telerik portfolio by downloading the Ultimate Collection trial package. Get it now >>
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
; }
}
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.
Martin
the Telerik team
Explore the entire Telerik portfolio by downloading the Ultimate Collection trial package. Get it now >>
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 >>