Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
65 changes: 65 additions & 0 deletions knowledge-base/radpdfprocessing-draw-pdf-page-background.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
title: Draw a PDF Page Background Using RadPdfProcessing
description: Learn how to draw a PDF page background by adding a colored rectangle behind the page content using RadPdfProcessing.
type: how-to
page_title: Draw PDF Page Background
slug: radpdfprocessing-draw-pdf-page-background
tags: pdf, processing, background, page, content, rectangle, color, fixed, document, draw
res_type: kb
---

## Environment

| Version | Product | Author |
| ---- | ---- | ---- |
| 2025.4.1104 | RadPdfProcessing | [Yoan Karamanov](https://www.telerik.com/blogs/author/yoan-karamanov) |

## Description

To draw a page background, you can create a colored [rectangle]({%slug radpdfprocessing-concepts-geometry%}#rectanglegeometry) and insert it at the beginning of the page's content collection so that it renders behind the rest of the content.

## Solution

The approach is to import the PDF, create a rectangle path sized to the page, and insert it into the page content at index `0` (first position).

### Key Points
* PDF pages don't have a built-in background property. Draw a rectangle to create a background.
* Backgrounds are typically the first element in the page content, subsequent elements render above earlier ones.
* Insert at index `0` to draw a background behind all content.
* Size the rectangle to match `firstPage.Size` for full-page coverage.

```csharp
RadFixedDocument inputDocument;
PdfFormatProvider pdfFormatProvider = new PdfFormatProvider();

using (System.IO.Stream input = System.IO.File.OpenRead("input.pdf"))
{
inputDocument = pdfFormatProvider.Import(input);
}

RadFixedPage firstPage = inputDocument.Pages.First();

// Create the background
RectangleGeometry rectangleGeometry = new RectangleGeometry();
rectangleGeometry.Rect = new Rect(1, 1, firstPage.Size.Width, firstPage.Size.Height);

Telerik.Windows.Documents.Fixed.Model.Graphics.Path path = new Telerik.Windows.Documents.Fixed.Model.Graphics.Path();
path.Geometry = rectangleGeometry;

path.IsFilled = true;
path.Fill = new RgbColor(100, 150, 0, 0);

// Insert the background (at position 1) into the first page
firstPage.Content.Insert(0, path); // Use index '1' if the document already has a set background

string pdfOutputPath = "output.pdf";

System.IO.File.Delete(pdfOutputPath);
using (System.IO.Stream output = System.IO.File.OpenWrite(pdfOutputPath))
{
pdfFormatProvider.Export(inputDocument, output, TimeSpan.FromSeconds(10));
}
```
## See Also
* [RadPdfProcessing Overview]({%slug radpdfprocessing-overview%})

Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
title: Protect Specific Worksheet Cells Using RadSpreadProcessing
description: Learn how to lock only specific worksheet cells while keeping the rest editable using RadSpreadProcessing.
type: how-to
page_title: Protect Specific Cells in RadSpreadProcessing
slug: radspreadprocessing-protect-specific-worksheet-cells
tags: spread, processing, worksheet, protection, lock, cells, used, range, performance, document
res_type: kb
---

## Environment

| Version | Product | Author |
| ---- | ---- | ---- |
| 2025.4.1104 | RadSpreadProcessing | [Yoan Karamanov](https://www.telerik.com/blogs/author/yoan-karamanov) |

## Description

This article shows how to protect only certain cells in a [Worksheet]({%slug radspreadprocessing-working-with-worksheets-what-is-worksheet%}) using [SpreadProcessing]({%slug radspreadprocessing-overview%}). The example demonstrates the most efficient way to keep all cells unlocked except the first row of a used range, which will remain locked and protected.

## Solution

In [SpreadProcessing]({%slug radspreadprocessing-overview%}), [Worksheet protection]({%slug radspreadprocessing-features-protection-worksheet%}) locks all cells by default. To protect only specific cells:

1. Protect the worksheet.
2. Unlock all columns (faster than iterating rows).
3. Determine the used cell range.
4. Lock only the first row cells within the used range.
5. Export the result.

Below is a complete example:

```csharp
Workbook workbook;
IWorkbookFormatProvider xlsxFormatProvider = new XlsxFormatProvider();

using (Stream input = new FileStream("input.xlsx", FileMode.Open))
{
workbook = xlsxFormatProvider.Import(input, TimeSpan.FromSeconds(10));
}

Worksheet worksheet = workbook.ActiveWorksheet;

// Protect the worksheet (default options)
worksheet.Protect("telerik", WorksheetProtectionOptions.Default);

// Unlock all columns in the worksheet (empty ones included)
for (int columnIndex = 0; columnIndex <= SpreadsheetDefaultValues.ColumnCount - 1; columnIndex++)
{
worksheet.Columns[columnIndex].SetIsLocked(false);
}

// Get the used cells range
CellRange usedCellRange = worksheet.UsedCellRange;

// Lock all first row cells of the used range
for (int columnIndex = usedCellRange.FromIndex.ColumnIndex; columnIndex <= usedCellRange.ToIndex.ColumnIndex; columnIndex++)
{
CellSelection cell = worksheet.Cells[0, columnIndex];
cell.SetIsLocked(true);
}

// Export to XLSX
string xlsxOutputPath = "output.xlsx";
using (Stream output = new FileStream(xlsxOutputPath, FileMode.Create))
{
xlsxFormatProvider.Export(workbook, output, TimeSpan.FromSeconds(10));
}
```

### Key Points
* Protecting columns instead of rows is significantly faster because the maximum number of columns (16,384) is far smaller than the maximum number of rows (1,048,576).
* By unlocking all columns first, you can selectively lock only the cells you need before applying protection.

## See Also
* [RadSpreadProcessing Overview]({%slug radspreadprocessing-overview%})
* [Worksheet Protection]({%slug radspreadprocessing-features-protection-worksheet%})
* [Iterating Used Cells]({%slug radspreadprocessing-working-with-cells-iterating-used-cells%})
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
title: Change Bookmark Content While Preserving Formatting using WordsProcessing
description: Change the content of a bookmark while preserving its original formatting and properties using the WordsProcessing library.
type: how-to
page_title: Change Bookmark Content While Preserving Formatting
slug: radwordsprocessing-change-bookmark-content-preserve-formatting
position: 0
tags: bookmarks, formatting, words, processing, content, replace, change, flow, docx
res_type: kb
---

|Product Version|Product|Author|
|----|----|----|
|2025.4.1104|RadWordsProcessing|[Yoan Karamanov](https://www.telerik.com/blogs/author/yoan-karamanov)|

## Description

This article shows how to change the content of an existing [Bookmark]({%slug radwordsprocessing-model-bookmark%}) in a DOCX document while preserving the original text formatting and character properties using the [WordsProcessing]({%slug radwordsprocessing-overview%}) library.

## Solution

* **Import DOCX**: Use [DocxFormatProvider]({%slug radwordsprocessing-formats-and-conversion-docx-docxformatprovider%}) to read the input DOCX and obtain a [RadFlowDocument]({%slug radwordsprocessing-model-radflowdocument%}).
* **Initialize editor**: Create a [RadFlowDocumentEditor]({%slug radwordsprocessing-editing-radflowdocumenteditor%}) for cursor movement and editing.
* **Find bookmark**: Enumerate [BookmarkRangeStart]({%slug radwordsprocessing-model-bookmark%}) elements and select the bookmark by **Name**.
* **Capture formatting**: Get the first [Run]({%slug radwordsprocessing-model-run%}) within the bookmark and copy its **CharacterFormatting** properties.
* **Delete original bookmark content**: Delete only the content between the start and end markers while keeping the bookmark structure intact.
* **Position cursor**: Move the editor back to the start of the bookmark to insert new text in place.
* **Copy formatting**: Apply the formatting of the original bookmark content to the editor properties.
* **Insert text**: Add the replacement content.
* **Export DOCX**: Write the updated document using [DocxFormatProvider]({%slug radwordsprocessing-formats-and-conversion-docx-docxformatprovider%}).

#### [C#] Replace bookmark content but keep formatting

```csharp
RadFlowDocument document;
DocxFormatProvider docxFormatProvider = new DocxFormatProvider();

using (Stream input = File.OpenRead("input.docx"))
{
document = docxFormatProvider.Import(input);
RadFlowDocumentEditor editor = new RadFlowDocumentEditor(document);

var documentBookmarks = document.EnumerateChildrenOfType<BookmarkRangeStart>().Select(b => b.Bookmark).ToList();

// Obtain bookmark by name
var specificBookmarkByName = documentBookmarks.FirstOrDefault(b => b.Name == "bookmark1");
// Obtain the first Run element inside the bookmark to copy its formatting
Run oldBookmarkText = (Run)specificBookmarkByName.BookmarkRangeStart.Paragraph.Inlines.FirstOrDefault(i => i is Run);

// Keep the bookmark and just change the content
editor.DeleteContent(specificBookmarkByName.BookmarkRangeStart, specificBookmarkByName.BookmarkRangeEnd, false);
editor.MoveToInlineEnd(specificBookmarkByName.BookmarkRangeStart);

// Apply the old formatting to the editor Method 1

editor.CharacterFormatting.CopyPropertiesFrom(oldBookmarkText.Properties);

// Apply the old formatting to the editor Method 2

editor.CharacterFormatting.FontSize.LocalValue = oldBookmarkText.FontSize;
editor.CharacterFormatting.FontFamily.LocalValue = oldBookmarkText.FontFamily;
editor.CharacterFormatting.FontStyle.LocalValue = oldBookmarkText.FontStyle;
editor.CharacterFormatting.FontWeight.LocalValue = oldBookmarkText.FontWeight;
editor.CharacterFormatting.Strikethrough.LocalValue = oldBookmarkText.Strikethrough;
editor.CharacterFormatting.FlowDirection.LocalValue = oldBookmarkText.FlowDirection;
editor.CharacterFormatting.BaselineAlignment.LocalValue = oldBookmarkText.BaselineAlignment;
editor.CharacterFormatting.ForegroundColor.LocalValue = oldBookmarkText.ForegroundColor;
editor.CharacterFormatting.HighlightColor.LocalValue = oldBookmarkText.HighlightColor;
editor.CharacterFormatting.UnderlineColor.LocalValue = oldBookmarkText.Underline.Color;
editor.CharacterFormatting.UnderlinePattern.LocalValue = oldBookmarkText.Underline.Pattern;

editor.InsertText("NEW CONTENT");

using (Stream output = File.OpenWrite("output.docx"))
{
docxFormatProvider.Export(document, output);
}
}
```
![WordsProcessing Change Bookmark Content](images/words-processing-change-bookmark-content.png)

## See Also
* [Bookmark]({%slug radwordsprocessing-model-bookmark%})
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
---
title: Modify the Content of Content Controls (SDTs) using WordsProcessing
description: Learn how to change the content inside Structured Document Tags (Content Controls) by editing the document elements between their start and end markers.
type: how-to
page_title: How to Modify the Content of Structured Document Tags (Content Controls)
slug: radwordsprocessing-modify-content-controls
tags: content, controls, sdt, words, processing, structured, document, tags, flow, docx, fields
res_type: kb
---

## Environment

| Version | Product | Author |
| --- | --- | ---- |
| 2025.4.1104 | RadWordsProcessing | [Yoan Karamanov](https://www.telerik.com/blogs/author/yoan-karamanov) |

## Description
Structured Document Tags (SDTs), also known as [Content Controls]({%slug wordsprocessing-model-content-controls%}), are implemented in [WordsProcessing]({%slug radwordsprocessing-overview%}) using annotation markers. The markers are placed before and after the control’s content - **SdtRangeStart** at the beginning and **SdtRangeEnd** at the end. To modify the content of a content control, you must change the document elements between these two markers.

## Solution

The following example covers:

* **Load and parse**: Imports an input DOCX using **DocxFormatProvider** and retrieves all SDTs via **EnumerateChildrenOfType<SdtRangeStart>()**.
* **Classify by alias**: Iterates each SDT and uses **SdtProperties.Alias** to route updates for specific control types: "RichText", "ComboBox", "CheckBox", and "DatePicker".
* **Preserve formatting**: For inline SDTs, collects all **Run** elements between **SdtRangeStart** and **SdtRangeEnd**, removes all but the first **Run**, and reuses that first **Run** to keep existing text formatting.
* **Update values**:
* **RichText**: Sets the first run’s **Text** to the new string.
* **ComboBox**: Sets the first run’s **Text** to the selected item display text (e.g., "Item 3").
* **CheckBox**: Toggles **CheckBoxProperties.Checked** and updates the glyph in the first run using the appropriate **SdtCheckBoxState** (font + character code).
* **DatePicker**: Formats **DateTime.Now** using the SDT’s **DateProperties.DateFormat** and assigns the result to the first run’s **Text**.
* **Save and open**: Exports the modified **RadFlowDocument** back to DOCX and opens the file to verify the changes.

```csharp
const string InputFile = "input.docx";
const string OutputFile = "output.docx";

static void Main(string[] args)
{
var provider = new DocxFormatProvider();
var document = provider.Import(File.ReadAllBytes(InputFile), null);
var sdtRangeStarts = document.EnumerateChildrenOfType<SdtRangeStart>().ToList();

foreach (var sdtRangeStart in sdtRangeStarts)
{
if (sdtRangeStart.SdtProperties.Alias == "RichText")
{
ChangeRichTextValue(sdtRangeStart, "New RichText Value");
}
else if (sdtRangeStart.SdtProperties.Alias == "ComboBox")
{
ChangeComboBoxValue(sdtRangeStart, "Item 3");
}
else if (sdtRangeStart.SdtProperties.Alias == "CheckBox")
{
ChangeCheckBoxValue(sdtRangeStart);
}
else if (sdtRangeStart.SdtProperties.Alias == "DatePicker")
{
ChangeDatePickerValue(sdtRangeStart, DateTime.Now);
}
}

var bytes = provider.Export(document, null);
File.WriteAllBytes(OutputFile, bytes);
Process.Start(new ProcessStartInfo(OutputFile) { UseShellExecute = true });
}

private static void ChangeDatePickerValue(SdtRangeStart sdtRangeStart, DateTime now)
{
var firstRun = ReturnFirstRunAndRemoveOthersFromSdt(sdtRangeStart);

var properties = (DateProperties)sdtRangeStart.SdtProperties;
firstRun.Text = now.ToString(properties.DateFormat);
}

private static void ChangeRichTextValue(SdtRangeStart sdtRangeStart, string value)
{
var firstRun = ReturnFirstRunAndRemoveOthersFromSdt(sdtRangeStart);

firstRun.Text = value;
}

private static void ChangeComboBoxValue(SdtRangeStart sdtRangeStart, string value)
{
var firstRun = ReturnFirstRunAndRemoveOthersFromSdt(sdtRangeStart);

firstRun.Text = value;
}

private static void ChangeCheckBoxValue(SdtRangeStart sdtRangeStart)
{
var firstRun = ReturnFirstRunAndRemoveOthersFromSdt(sdtRangeStart);
CheckBoxProperties properties = (CheckBoxProperties)sdtRangeStart.SdtProperties;
if (properties.Checked.HasValue && properties.Checked.Value)
{
properties.Checked = false;

// If check box is currently checked, change it to unchecked
ApplyNewCheckBoxState(firstRun, properties.UncheckedState);
}
else
{
properties.Checked = true;

// If check box is currently unchecked, change it to checked
ApplyNewCheckBoxState(firstRun, properties.CheckedState);
}
}

private static void ApplyNewCheckBoxState(Run run, SdtCheckBoxState state)
{
if (run != null)
{
run.Properties.FontFamily.LocalValue = new ThemableFontFamily(state.Font);
run.Text = ((char)state.CharacterCode).ToString();
}
}

private static Run ReturnFirstRunAndRemoveOthersFromSdt(SdtRangeStart sdtRangeStart)
{
var runs = GetRunsInsideSdt(sdtRangeStart);

// Remove all but the first run inside the SdtRangeStart
// We want to keep the first run because it contains the formatting of the text
var paragraph = sdtRangeStart.Paragraph;
for (int i = 1; i < runs.Count; i++)
{
paragraph.Inlines.Remove(runs[i]);
}

return runs[0];
}

private static IList<Run> GetRunsInsideSdt(SdtRangeStart sdtRangeStart)
{
List<Run> runs = new List<Run>();
var paragraph = sdtRangeStart.Paragraph;
var sdtStartIndex = paragraph.Inlines.IndexOf(sdtRangeStart);
for (int i = sdtStartIndex + 1; i < paragraph.Inlines.Count; i++)
{
if (paragraph.Inlines[i] is SdtRangeEnd sdtRangeEnd && sdtRangeEnd.Start == sdtRangeStart)
{
return runs;
}

if (paragraph.Inlines[i] is Run run)
{
runs.Add(run);
}
}

// It is possible that the SdtRangeEnd is inside another Paragraph
// For demo purposes we will handle only the case where the SdtRangeEnd is in the same Paragraph
return runs;
}
```
![WordsProcessing Change SDT Content](images/words-processing-change-sdt-content.png)

### Notes
* Use [RadFlowDocumentEditor]({%slug radwordsprocessing-editing-radflowdocumenteditor%}) for higher-level operations like inserting SDTs via **InsertStructuredDocumentTag**.
* The content control type (plain text, combo box, checkbox, etc.) is available through **SdtProperties.Type**. Adjust the replacement logic based on the control type when necessary.
* Content controls can exist at different levels (block, inline, row, cell). Ensure you modify the correct collection (**Inlines**, **Blocks**, **Cells**, etc.) depending on where the SDT is placed. See [**Content Controls**]({%slug wordsprocessing-model-content-controls%}).

## See Also
* [Content Controls]({%slug wordsprocessing-model-content-controls%})
Loading