diff --git a/knowledge-base/images/words-processing-change-bookmark-content.png b/knowledge-base/images/words-processing-change-bookmark-content.png new file mode 100644 index 00000000..ccf0bb03 Binary files /dev/null and b/knowledge-base/images/words-processing-change-bookmark-content.png differ diff --git a/knowledge-base/images/words-processing-change-sdt-content.png b/knowledge-base/images/words-processing-change-sdt-content.png new file mode 100644 index 00000000..f753d10b Binary files /dev/null and b/knowledge-base/images/words-processing-change-sdt-content.png differ diff --git a/knowledge-base/radpdfprocessing-draw-pdf-page-background.md b/knowledge-base/radpdfprocessing-draw-pdf-page-background.md new file mode 100644 index 00000000..614cf2f4 --- /dev/null +++ b/knowledge-base/radpdfprocessing-draw-pdf-page-background.md @@ -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%}) + diff --git a/knowledge-base/radspreadprocessing-protect-specific-worksheet-cells.md b/knowledge-base/radspreadprocessing-protect-specific-worksheet-cells.md new file mode 100644 index 00000000..70eb30bd --- /dev/null +++ b/knowledge-base/radspreadprocessing-protect-specific-worksheet-cells.md @@ -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%}) diff --git a/knowledge-base/radwordsprocessing-change-bookmark-content-preserve-formatting.md b/knowledge-base/radwordsprocessing-change-bookmark-content-preserve-formatting.md new file mode 100644 index 00000000..86670679 --- /dev/null +++ b/knowledge-base/radwordsprocessing-change-bookmark-content-preserve-formatting.md @@ -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().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%}) diff --git a/knowledge-base/radwordsprocessing-modify-content-of-content-controls.md b/knowledge-base/radwordsprocessing-modify-content-of-content-controls.md new file mode 100644 index 00000000..3da9f62e --- /dev/null +++ b/knowledge-base/radwordsprocessing-modify-content-of-content-controls.md @@ -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()**. +* **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().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 GetRunsInsideSdt(SdtRangeStart sdtRangeStart) +{ + List runs = new List(); + 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%}) \ No newline at end of file diff --git a/libraries/radpdfprocessing/concepts/geometry.md b/libraries/radpdfprocessing/concepts/geometry.md index ff408ba4..ce8ba299 100644 --- a/libraries/radpdfprocessing/concepts/geometry.md +++ b/libraries/radpdfprocessing/concepts/geometry.md @@ -148,3 +148,4 @@ Represents an elliptical arc between two points. The __ArcSegment__ exposes the * [Path]({%slug radpdfprocessing-model-path%}) * [FixedContentEditor]({%slug radpdfprocessing-editing-fixedcontenteditor%}) * [How to Draw Figures in PDF documents]({%slug pdf-processing-draw-figures-arcsegment%}) + * [Draw a PDF Page Background Using RadPdfProcessing]({%slug radpdfprocessing-draw-pdf-page-background%}) diff --git a/libraries/radspreadprocessing/features/protection/worksheet.md b/libraries/radspreadprocessing/features/protection/worksheet.md index 55855984..4837a4f4 100644 --- a/libraries/radspreadprocessing/features/protection/worksheet.md +++ b/libraries/radspreadprocessing/features/protection/worksheet.md @@ -96,3 +96,4 @@ The __WorksheetProtectionOptions__ class exposes the following properties so you * [What is a Worksheet?]({%slug radspreadprocessing-working-with-worksheets-what-is-worksheet%}) * [Workbook Protection]({%slug radspreadprocessing-features-protection-workbook%}) + * [Protect Specific Worksheet Cells Using RadSpreadProcessing]({%slug radspreadprocessing-protect-specific-worksheet-cells%}) diff --git a/libraries/radwordsprocessing/model/bookmark.md b/libraries/radwordsprocessing/model/bookmark.md index 78f35020..adadd72c 100644 --- a/libraries/radwordsprocessing/model/bookmark.md +++ b/libraries/radwordsprocessing/model/bookmark.md @@ -73,3 +73,4 @@ __Example 3__ demonstrates how you can delete the bookmark created in __Example * [Paragraph]({%slug radwordsprocessing-model-paragraph%}) * [RadFlowDocumentEditor]({%slug radwordsprocessing-editing-radflowdocumenteditor%}) * [Finding a Table Containing a Specific Bookmark in Word Documents]({%slug radwordsprocessing-find-table-by-bookmark%}) + * [Change Bookmark Content While Preserving Formatting]({%slug radwordsprocessing-change-bookmark-content-preserve-formatting%}) diff --git a/libraries/radwordsprocessing/model/content-controls/content-controls.md b/libraries/radwordsprocessing/model/content-controls/content-controls.md index 29d78e8b..f125270c 100644 --- a/libraries/radwordsprocessing/model/content-controls/content-controls.md +++ b/libraries/radwordsprocessing/model/content-controls/content-controls.md @@ -208,5 +208,6 @@ The __Text__ content control allows you to enter plain text. The text content co * [Working with Content Controls]({%slug wordsprocessing-model-working-with-content-controls%}) * [Generating Dynamic DOCX Documents with Tables and CheckBoxes using RadWordsProcessing]({%slug dynamic-docx-document-generation-radwordsprocessing%}) * [How to Clone and Populate Repeating Section Content Controls in RadWordsProcessing]({%slug clone-repeating-section-content-control-radwordsprocessing%}) + * [Modify the Content of Content Controls (SDTs) using WordsProcessing]({%slug radwordsprocessing-modify-content-controls%})