如何在Windows Form中精准将TextBox覆盖于Label指定字符串位置?
Hey there! Let's tackle this WinForms problem neatly—since you don't work with it often, I'll keep this approach straightforward, scalable, and precise for your 300+ fill-in-the-blank strings. The core idea is to dynamically parse your text, calculate exact positions for missing-letter spots, and auto-generate aligned TextBox controls instead of manually placing them.
First, we need to scan your input strings and flag sequences of underscores (the missing letter spots). We'll store each placeholder's start index and length to reference later.
// Helper class to track placeholder details private class Placeholder { public int StartIndex { get; set; } public int Length { get; set; } public RectangleF Bounds { get; set; } // Will hold exact on-screen position later } private List<Placeholder> ParsePlaceholders(string inputText) { var placeholders = new List<Placeholder>(); int placeholderStart = -1; for (int i = 0; i < inputText.Length; i++) { if (inputText[i] == '_') { if (placeholderStart == -1) placeholderStart = i; } else { if (placeholderStart != -1) { placeholders.Add(new Placeholder { StartIndex = placeholderStart, Length = i - placeholderStart }); placeholderStart = -1; } } } // Catch any trailing underscores at the end of the text if (placeholderStart != -1) { placeholders.Add(new Placeholder { StartIndex = placeholderStart, Length = inputText.Length - placeholderStart }); } return placeholders; }
To get pixel-perfect alignment, we'll use WinForms' GDI+ tools to measure where each placeholder sits in the Label. This accounts for line breaks, font size, and Label alignment.
private void CalculatePlaceholderPositions(Label targetLabel, List<Placeholder> placeholders) { using (var graphics = targetLabel.CreateGraphics()) { graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; var textFormat = new StringFormat(StringFormat.GenericTypographic); // Match Label's text alignment to ensure accurate measurements textFormat.Alignment = targetLabel.TextAlign switch { ContentAlignment.TopLeft => StringAlignment.Near, ContentAlignment.TopCenter => StringAlignment.Center, ContentAlignment.TopRight => StringAlignment.Far, _ => StringAlignment.Near }; textFormat.LineAlignment = StringAlignment.Near; textFormat.Trimming = StringTrimming.None; // Split text into lines to handle multi-line support var textLines = targetLabel.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.None); int currentCharIndex = 0; foreach (var line in textLines) { // Get character ranges for every character in the line var charRanges = new CharacterRange[line.Length]; for (int i = 0; i < line.Length; i++) charRanges[i] = new CharacterRange(i, 1); textFormat.SetMeasurableCharacterRanges(charRanges); var charRegions = graphics.MeasureCharacterRanges( line, targetLabel.Font, new RectangleF(0, 0, targetLabel.ClientSize.Width, targetLabel.ClientSize.Height), textFormat ); // Map each character's position to our placeholders for (int i = 0; i < line.Length; i++) { var charBounds = charRegions[i].GetBounds(graphics); foreach (var placeholder in placeholders.Where(p => p.StartIndex <= currentCharIndex && currentCharIndex < p.StartIndex + p.Length)) { // Merge bounds for continuous underscores placeholder.Bounds = placeholder.Bounds.IsEmpty ? charBounds : RectangleF.Union(placeholder.Bounds, charBounds); } currentCharIndex++; } currentCharIndex += Environment.NewLine.Length; // Account for line break chars } } }
With placeholder positions calculated, we'll dynamically create TextBoxes, size them to fit the placeholder, and place them directly over the underscores. We'll also clean up old TextBoxes if we refresh the text.
private void GenerateAlignedTextBoxes(Label targetLabel, List<Placeholder> placeholders) { // Remove existing TextBoxes linked to placeholders to avoid duplicates var oldTextBoxes = targetLabel.Parent.Controls .OfType<TextBox>() .Where(tb => tb.Tag is Placeholder) .ToList(); foreach (var tb in oldTextBoxes) tb.Dispose(); foreach (var placeholder in placeholders) { var inputBox = new TextBox { Tag = placeholder, // Link back to placeholder for future reference Font = targetLabel.Font, // Match Label font to prevent alignment shifts BorderStyle = BorderStyle.FixedSingle, MaxLength = placeholder.Length, // Limit input to the number of underscores TextAlign = HorizontalAlignment.Center // Center text for better visual fit }; // Calculate absolute position relative to the Label's parent container var absoluteX = targetLabel.Left + placeholder.Bounds.X + targetLabel.Padding.Left; var absoluteY = targetLabel.Top + placeholder.Bounds.Y + targetLabel.Padding.Top; inputBox.Location = new Point((int)Math.Round(absoluteX), (int)Math.Round(absoluteY)); inputBox.Size = new Size((int)Math.Round(placeholder.Bounds.Width), (int)Math.Round(placeholder.Bounds.Height)); // Add to parent container and bring to front so it sits over the Label targetLabel.Parent.Controls.Add(inputBox); inputBox.BringToFront(); } }
Create a single method to run the full workflow, and hook it up to events like Form Load or Label Resize to handle dynamic changes.
private void UpdateFillInTheBlanks(Label targetLabel, string fillInText) { targetLabel.Text = fillInText; var placeholders = ParsePlaceholders(fillInText); CalculatePlaceholderPositions(targetLabel, placeholders); GenerateAlignedTextBoxes(targetLabel, placeholders); } // Example: Call on Form load private void YourForm_Load(object sender, EventArgs e) { var sampleText = "The quick brown fox j___ed over the l__y hound\nAnother line with t__o placeh__lders"; UpdateFillInTheBlanks(yourLabelControl, sampleText); } // Refresh when Label is resized (handles text wrapping changes) private void yourLabelControl_Resize(object sender, EventArgs e) { UpdateFillInTheBlanks(yourLabelControl, yourLabelControl.Text); }
- Font Consistency: Always set the TextBox font to match the Label—different fonts have different character widths, which will break alignment.
- DPI Awareness: If your app needs to support high-DPI displays, enable
PerMonitorV2DPI awareness in your project settings to avoid scaling shifts. - Container Choice: Place your Label and generated TextBoxes inside a Panel instead of directly on the Form—this keeps their relative positions intact if you move the Panel.
内容的提问来源于stack exchange,提问作者Galwegian

