|
| 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 | + |
| 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