Skip to content

Commit 9749385

Browse files
authored
Merge pull request #687 from telerik/yoan/kbs
4 KB articles
2 parents 3554700 + f51ec2d commit 9749385

File tree

10 files changed

+396
-0
lines changed

10 files changed

+396
-0
lines changed
11.3 KB
Loading
17.1 KB
Loading
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
title: Draw a PDF Page Background Using RadPdfProcessing
3+
description: Learn how to draw a PDF page background by adding a colored rectangle behind the page content using RadPdfProcessing.
4+
type: how-to
5+
page_title: Draw PDF Page Background
6+
slug: radpdfprocessing-draw-pdf-page-background
7+
tags: pdf, processing, background, page, content, rectangle, color, fixed, document, draw
8+
res_type: kb
9+
---
10+
11+
## Environment
12+
13+
| Version | Product | Author |
14+
| ---- | ---- | ---- |
15+
| 2025.4.1104 | RadPdfProcessing | [Yoan Karamanov](https://www.telerik.com/blogs/author/yoan-karamanov) |
16+
17+
## Description
18+
19+
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.
20+
21+
## Solution
22+
23+
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).
24+
25+
### Key Points
26+
* PDF pages don't have a built-in background property. Draw a rectangle to create a background.
27+
* Backgrounds are typically the first element in the page content, subsequent elements render above earlier ones.
28+
* Insert at index `0` to draw a background behind all content.
29+
* Size the rectangle to match `firstPage.Size` for full-page coverage.
30+
31+
```csharp
32+
RadFixedDocument inputDocument;
33+
PdfFormatProvider pdfFormatProvider = new PdfFormatProvider();
34+
35+
using (System.IO.Stream input = System.IO.File.OpenRead("input.pdf"))
36+
{
37+
inputDocument = pdfFormatProvider.Import(input);
38+
}
39+
40+
RadFixedPage firstPage = inputDocument.Pages.First();
41+
42+
// Create the background
43+
RectangleGeometry rectangleGeometry = new RectangleGeometry();
44+
rectangleGeometry.Rect = new Rect(1, 1, firstPage.Size.Width, firstPage.Size.Height);
45+
46+
Telerik.Windows.Documents.Fixed.Model.Graphics.Path path = new Telerik.Windows.Documents.Fixed.Model.Graphics.Path();
47+
path.Geometry = rectangleGeometry;
48+
49+
path.IsFilled = true;
50+
path.Fill = new RgbColor(100, 150, 0, 0);
51+
52+
// Insert the background (at position 1) into the first page
53+
firstPage.Content.Insert(0, path); // Use index '1' if the document already has a set background
54+
55+
string pdfOutputPath = "output.pdf";
56+
57+
System.IO.File.Delete(pdfOutputPath);
58+
using (System.IO.Stream output = System.IO.File.OpenWrite(pdfOutputPath))
59+
{
60+
pdfFormatProvider.Export(inputDocument, output, TimeSpan.FromSeconds(10));
61+
}
62+
```
63+
## See Also
64+
* [RadPdfProcessing Overview]({%slug radpdfprocessing-overview%})
65+
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
title: Protect Specific Worksheet Cells Using RadSpreadProcessing
3+
description: Learn how to lock only specific worksheet cells while keeping the rest editable using RadSpreadProcessing.
4+
type: how-to
5+
page_title: Protect Specific Cells in RadSpreadProcessing
6+
slug: radspreadprocessing-protect-specific-worksheet-cells
7+
tags: spread, processing, worksheet, protection, lock, cells, used, range, performance, document
8+
res_type: kb
9+
---
10+
11+
## Environment
12+
13+
| Version | Product | Author |
14+
| ---- | ---- | ---- |
15+
| 2025.4.1104 | RadSpreadProcessing | [Yoan Karamanov](https://www.telerik.com/blogs/author/yoan-karamanov) |
16+
17+
## Description
18+
19+
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.
20+
21+
## Solution
22+
23+
In [SpreadProcessing]({%slug radspreadprocessing-overview%}), [Worksheet protection]({%slug radspreadprocessing-features-protection-worksheet%}) locks all cells by default. To protect only specific cells:
24+
25+
1. Protect the worksheet.
26+
2. Unlock all columns (faster than iterating rows).
27+
3. Determine the used cell range.
28+
4. Lock only the first row cells within the used range.
29+
5. Export the result.
30+
31+
Below is a complete example:
32+
33+
```csharp
34+
Workbook workbook;
35+
IWorkbookFormatProvider xlsxFormatProvider = new XlsxFormatProvider();
36+
37+
using (Stream input = new FileStream("input.xlsx", FileMode.Open))
38+
{
39+
workbook = xlsxFormatProvider.Import(input, TimeSpan.FromSeconds(10));
40+
}
41+
42+
Worksheet worksheet = workbook.ActiveWorksheet;
43+
44+
// Protect the worksheet (default options)
45+
worksheet.Protect("telerik", WorksheetProtectionOptions.Default);
46+
47+
// Unlock all columns in the worksheet (empty ones included)
48+
for (int columnIndex = 0; columnIndex <= SpreadsheetDefaultValues.ColumnCount - 1; columnIndex++)
49+
{
50+
worksheet.Columns[columnIndex].SetIsLocked(false);
51+
}
52+
53+
// Get the used cells range
54+
CellRange usedCellRange = worksheet.UsedCellRange;
55+
56+
// Lock all first row cells of the used range
57+
for (int columnIndex = usedCellRange.FromIndex.ColumnIndex; columnIndex <= usedCellRange.ToIndex.ColumnIndex; columnIndex++)
58+
{
59+
CellSelection cell = worksheet.Cells[0, columnIndex];
60+
cell.SetIsLocked(true);
61+
}
62+
63+
// Export to XLSX
64+
string xlsxOutputPath = "output.xlsx";
65+
using (Stream output = new FileStream(xlsxOutputPath, FileMode.Create))
66+
{
67+
xlsxFormatProvider.Export(workbook, output, TimeSpan.FromSeconds(10));
68+
}
69+
```
70+
71+
### Key Points
72+
* 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).
73+
* By unlocking all columns first, you can selectively lock only the cells you need before applying protection.
74+
75+
## See Also
76+
* [RadSpreadProcessing Overview]({%slug radspreadprocessing-overview%})
77+
* [Worksheet Protection]({%slug radspreadprocessing-features-protection-worksheet%})
78+
* [Iterating Used Cells]({%slug radspreadprocessing-working-with-cells-iterating-used-cells%})
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
---
2+
title: Change Bookmark Content While Preserving Formatting using WordsProcessing
3+
description: Change the content of a bookmark while preserving its original formatting and properties using the WordsProcessing library.
4+
type: how-to
5+
page_title: Change Bookmark Content While Preserving Formatting
6+
slug: radwordsprocessing-change-bookmark-content-preserve-formatting
7+
position: 0
8+
tags: bookmarks, formatting, words, processing, content, replace, change, flow, docx
9+
res_type: kb
10+
---
11+
12+
|Product Version|Product|Author|
13+
|----|----|----|
14+
|2025.4.1104|RadWordsProcessing|[Yoan Karamanov](https://www.telerik.com/blogs/author/yoan-karamanov)|
15+
16+
## Description
17+
18+
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.
19+
20+
## Solution
21+
22+
* **Import DOCX**: Use [DocxFormatProvider]({%slug radwordsprocessing-formats-and-conversion-docx-docxformatprovider%}) to read the input DOCX and obtain a [RadFlowDocument]({%slug radwordsprocessing-model-radflowdocument%}).
23+
* **Initialize editor**: Create a [RadFlowDocumentEditor]({%slug radwordsprocessing-editing-radflowdocumenteditor%}) for cursor movement and editing.
24+
* **Find bookmark**: Enumerate [BookmarkRangeStart]({%slug radwordsprocessing-model-bookmark%}) elements and select the bookmark by **Name**.
25+
* **Capture formatting**: Get the first [Run]({%slug radwordsprocessing-model-run%}) within the bookmark and copy its **CharacterFormatting** properties.
26+
* **Delete original bookmark content**: Delete only the content between the start and end markers while keeping the bookmark structure intact.
27+
* **Position cursor**: Move the editor back to the start of the bookmark to insert new text in place.
28+
* **Copy formatting**: Apply the formatting of the original bookmark content to the editor properties.
29+
* **Insert text**: Add the replacement content.
30+
* **Export DOCX**: Write the updated document using [DocxFormatProvider]({%slug radwordsprocessing-formats-and-conversion-docx-docxformatprovider%}).
31+
32+
#### [C#] Replace bookmark content but keep formatting
33+
34+
```csharp
35+
RadFlowDocument document;
36+
DocxFormatProvider docxFormatProvider = new DocxFormatProvider();
37+
38+
using (Stream input = File.OpenRead("input.docx"))
39+
{
40+
document = docxFormatProvider.Import(input);
41+
RadFlowDocumentEditor editor = new RadFlowDocumentEditor(document);
42+
43+
var documentBookmarks = document.EnumerateChildrenOfType<BookmarkRangeStart>().Select(b => b.Bookmark).ToList();
44+
45+
// Obtain bookmark by name
46+
var specificBookmarkByName = documentBookmarks.FirstOrDefault(b => b.Name == "bookmark1");
47+
// Obtain the first Run element inside the bookmark to copy its formatting
48+
Run oldBookmarkText = (Run)specificBookmarkByName.BookmarkRangeStart.Paragraph.Inlines.FirstOrDefault(i => i is Run);
49+
50+
// Keep the bookmark and just change the content
51+
editor.DeleteContent(specificBookmarkByName.BookmarkRangeStart, specificBookmarkByName.BookmarkRangeEnd, false);
52+
editor.MoveToInlineEnd(specificBookmarkByName.BookmarkRangeStart);
53+
54+
// Apply the old formatting to the editor Method 1
55+
56+
editor.CharacterFormatting.CopyPropertiesFrom(oldBookmarkText.Properties);
57+
58+
// Apply the old formatting to the editor Method 2
59+
60+
editor.CharacterFormatting.FontSize.LocalValue = oldBookmarkText.FontSize;
61+
editor.CharacterFormatting.FontFamily.LocalValue = oldBookmarkText.FontFamily;
62+
editor.CharacterFormatting.FontStyle.LocalValue = oldBookmarkText.FontStyle;
63+
editor.CharacterFormatting.FontWeight.LocalValue = oldBookmarkText.FontWeight;
64+
editor.CharacterFormatting.Strikethrough.LocalValue = oldBookmarkText.Strikethrough;
65+
editor.CharacterFormatting.FlowDirection.LocalValue = oldBookmarkText.FlowDirection;
66+
editor.CharacterFormatting.BaselineAlignment.LocalValue = oldBookmarkText.BaselineAlignment;
67+
editor.CharacterFormatting.ForegroundColor.LocalValue = oldBookmarkText.ForegroundColor;
68+
editor.CharacterFormatting.HighlightColor.LocalValue = oldBookmarkText.HighlightColor;
69+
editor.CharacterFormatting.UnderlineColor.LocalValue = oldBookmarkText.Underline.Color;
70+
editor.CharacterFormatting.UnderlinePattern.LocalValue = oldBookmarkText.Underline.Pattern;
71+
72+
editor.InsertText("NEW CONTENT");
73+
74+
using (Stream output = File.OpenWrite("output.docx"))
75+
{
76+
docxFormatProvider.Export(document, output);
77+
}
78+
}
79+
```
80+
![WordsProcessing Change Bookmark Content](images/words-processing-change-bookmark-content.png)
81+
82+
## See Also
83+
* [Bookmark]({%slug radwordsprocessing-model-bookmark%})
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
---
2+
title: Modify the Content of Content Controls (SDTs) using WordsProcessing
3+
description: Learn how to change the content inside Structured Document Tags (Content Controls) by editing the document elements between their start and end markers.
4+
type: how-to
5+
page_title: How to Modify the Content of Structured Document Tags (Content Controls)
6+
slug: radwordsprocessing-modify-content-controls
7+
tags: content, controls, sdt, words, processing, structured, document, tags, flow, docx, fields
8+
res_type: kb
9+
---
10+
11+
## Environment
12+
13+
| Version | Product | Author |
14+
| --- | --- | ---- |
15+
| 2025.4.1104 | RadWordsProcessing | [Yoan Karamanov](https://www.telerik.com/blogs/author/yoan-karamanov) |
16+
17+
## Description
18+
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.
19+
20+
## Solution
21+
22+
The following example covers:
23+
24+
* **Load and parse**: Imports an input DOCX using **DocxFormatProvider** and retrieves all SDTs via **EnumerateChildrenOfType<SdtRangeStart>()**.
25+
* **Classify by alias**: Iterates each SDT and uses **SdtProperties.Alias** to route updates for specific control types: "RichText", "ComboBox", "CheckBox", and "DatePicker".
26+
* **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.
27+
* **Update values**:
28+
* **RichText**: Sets the first run’s **Text** to the new string.
29+
* **ComboBox**: Sets the first run’s **Text** to the selected item display text (e.g., "Item 3").
30+
* **CheckBox**: Toggles **CheckBoxProperties.Checked** and updates the glyph in the first run using the appropriate **SdtCheckBoxState** (font + character code).
31+
* **DatePicker**: Formats **DateTime.Now** using the SDT’s **DateProperties.DateFormat** and assigns the result to the first run’s **Text**.
32+
* **Save and open**: Exports the modified **RadFlowDocument** back to DOCX and opens the file to verify the changes.
33+
34+
```csharp
35+
const string InputFile = "input.docx";
36+
const string OutputFile = "output.docx";
37+
38+
static void Main(string[] args)
39+
{
40+
var provider = new DocxFormatProvider();
41+
var document = provider.Import(File.ReadAllBytes(InputFile), null);
42+
var sdtRangeStarts = document.EnumerateChildrenOfType<SdtRangeStart>().ToList();
43+
44+
foreach (var sdtRangeStart in sdtRangeStarts)
45+
{
46+
if (sdtRangeStart.SdtProperties.Alias == "RichText")
47+
{
48+
ChangeRichTextValue(sdtRangeStart, "New RichText Value");
49+
}
50+
else if (sdtRangeStart.SdtProperties.Alias == "ComboBox")
51+
{
52+
ChangeComboBoxValue(sdtRangeStart, "Item 3");
53+
}
54+
else if (sdtRangeStart.SdtProperties.Alias == "CheckBox")
55+
{
56+
ChangeCheckBoxValue(sdtRangeStart);
57+
}
58+
else if (sdtRangeStart.SdtProperties.Alias == "DatePicker")
59+
{
60+
ChangeDatePickerValue(sdtRangeStart, DateTime.Now);
61+
}
62+
}
63+
64+
var bytes = provider.Export(document, null);
65+
File.WriteAllBytes(OutputFile, bytes);
66+
Process.Start(new ProcessStartInfo(OutputFile) { UseShellExecute = true });
67+
}
68+
69+
private static void ChangeDatePickerValue(SdtRangeStart sdtRangeStart, DateTime now)
70+
{
71+
var firstRun = ReturnFirstRunAndRemoveOthersFromSdt(sdtRangeStart);
72+
73+
var properties = (DateProperties)sdtRangeStart.SdtProperties;
74+
firstRun.Text = now.ToString(properties.DateFormat);
75+
}
76+
77+
private static void ChangeRichTextValue(SdtRangeStart sdtRangeStart, string value)
78+
{
79+
var firstRun = ReturnFirstRunAndRemoveOthersFromSdt(sdtRangeStart);
80+
81+
firstRun.Text = value;
82+
}
83+
84+
private static void ChangeComboBoxValue(SdtRangeStart sdtRangeStart, string value)
85+
{
86+
var firstRun = ReturnFirstRunAndRemoveOthersFromSdt(sdtRangeStart);
87+
88+
firstRun.Text = value;
89+
}
90+
91+
private static void ChangeCheckBoxValue(SdtRangeStart sdtRangeStart)
92+
{
93+
var firstRun = ReturnFirstRunAndRemoveOthersFromSdt(sdtRangeStart);
94+
CheckBoxProperties properties = (CheckBoxProperties)sdtRangeStart.SdtProperties;
95+
if (properties.Checked.HasValue && properties.Checked.Value)
96+
{
97+
properties.Checked = false;
98+
99+
// If check box is currently checked, change it to unchecked
100+
ApplyNewCheckBoxState(firstRun, properties.UncheckedState);
101+
}
102+
else
103+
{
104+
properties.Checked = true;
105+
106+
// If check box is currently unchecked, change it to checked
107+
ApplyNewCheckBoxState(firstRun, properties.CheckedState);
108+
}
109+
}
110+
111+
private static void ApplyNewCheckBoxState(Run run, SdtCheckBoxState state)
112+
{
113+
if (run != null)
114+
{
115+
run.Properties.FontFamily.LocalValue = new ThemableFontFamily(state.Font);
116+
run.Text = ((char)state.CharacterCode).ToString();
117+
}
118+
}
119+
120+
private static Run ReturnFirstRunAndRemoveOthersFromSdt(SdtRangeStart sdtRangeStart)
121+
{
122+
var runs = GetRunsInsideSdt(sdtRangeStart);
123+
124+
// Remove all but the first run inside the SdtRangeStart
125+
// We want to keep the first run because it contains the formatting of the text
126+
var paragraph = sdtRangeStart.Paragraph;
127+
for (int i = 1; i < runs.Count; i++)
128+
{
129+
paragraph.Inlines.Remove(runs[i]);
130+
}
131+
132+
return runs[0];
133+
}
134+
135+
private static IList<Run> GetRunsInsideSdt(SdtRangeStart sdtRangeStart)
136+
{
137+
List<Run> runs = new List<Run>();
138+
var paragraph = sdtRangeStart.Paragraph;
139+
var sdtStartIndex = paragraph.Inlines.IndexOf(sdtRangeStart);
140+
for (int i = sdtStartIndex + 1; i < paragraph.Inlines.Count; i++)
141+
{
142+
if (paragraph.Inlines[i] is SdtRangeEnd sdtRangeEnd && sdtRangeEnd.Start == sdtRangeStart)
143+
{
144+
return runs;
145+
}
146+
147+
if (paragraph.Inlines[i] is Run run)
148+
{
149+
runs.Add(run);
150+
}
151+
}
152+
153+
// It is possible that the SdtRangeEnd is inside another Paragraph
154+
// For demo purposes we will handle only the case where the SdtRangeEnd is in the same Paragraph
155+
return runs;
156+
}
157+
```
158+
![WordsProcessing Change SDT Content](images/words-processing-change-sdt-content.png)
159+
160+
### Notes
161+
* Use [RadFlowDocumentEditor]({%slug radwordsprocessing-editing-radflowdocumenteditor%}) for higher-level operations like inserting SDTs via **InsertStructuredDocumentTag**.
162+
* 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.
163+
* 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%}).
164+
165+
## See Also
166+
* [Content Controls]({%slug wordsprocessing-model-content-controls%})

0 commit comments

Comments
 (0)