para emplea description descripcion caracteres atributo c# .net vb.net winforms

c# - emplea - meta descripcion



¿Cómo transpongo(con elegancia) el cuadro de texto sobre la etiqueta en una parte específica de la cadena? (10)

Considere utilizar una combinación de una columna DataGridView y una celda enmascarada.

En Mostrar control de edición, cambiaría la máscara de esa fila en particular.

Aquí hay un ejemplo de uso de código que incluye la cuadrícula y el enmascaramiento único para cada fila.

Public Class Form1 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load Dim mec As New MaskedEditColumn mec.Mask = "" mec.DataPropertyName = "Data" Me.DataGridView1.Columns.Add(mec) Dim tbl As New Data.DataTable tbl.Columns.Add("Data") tbl.Columns.Add("Mask") tbl.Rows.Add(New Object() {"The quick brown fox j ed over the lazy hound", "The quick brown fox jaaaed over the l/azy hound"}) tbl.Rows.Add(New Object() {" quick brown fox j ed over the lazy hound", "aaa quick brown fox jaaaed over the l/azy hound"}) tbl.Rows.Add(New Object() {"The brown fox j ed over the lazy hound", "The aaaaa brown fox jaaaed over the l/azy hound"}) Me.DataGridView1.AutoGenerateColumns = False Me.DataGridView1.DataSource = tbl End Sub Private Sub DataGridView1_EditingControlShowing(sender As Object, e As DataGridViewEditingControlShowingEventArgs) Handles DataGridView1.EditingControlShowing If e.Control.GetType().Equals(GetType(MaskedEditingControl)) Then Dim mec As MaskedEditingControl = e.Control Dim row As DataGridViewRow = Me.DataGridView1.CurrentRow mec.Mask = row.DataBoundItem("Mask") End If End Sub End Class

Y la columna de la cuadrícula, que proviene de aquí: http://www.vb-tips.com/MaskedEditColumn.aspx

Public Class MaskedEditColumn Inherits DataGridViewColumn Public Sub New() MyBase.New(New MaskedEditCell()) End Sub Public Overrides Property CellTemplate() As DataGridViewCell Get Return MyBase.CellTemplate End Get Set(ByVal value As DataGridViewCell) '' Ensure that the cell used for the template is a CalendarCell. If Not (value Is Nothing) AndAlso Not value.GetType().IsAssignableFrom(GetType(MaskedEditCell)) _ Then Throw New InvalidCastException("Must be a MaskedEditCell") End If MyBase.CellTemplate = value End Set End Property Private m_strMask As String Public Property Mask() As String Get Return m_strMask End Get Set(ByVal value As String) m_strMask = value End Set End Property Private m_tyValidatingType As Type Public Property ValidatingType() As Type Get Return m_tyValidatingType End Get Set(ByVal value As Type) m_tyValidatingType = value End Set End Property Private m_cPromptChar As Char = "_"c Public Property PromptChar() As Char Get Return m_cPromptChar End Get Set(ByVal value As Char) m_cPromptChar = value End Set End Property Private ReadOnly Property MaskedEditCellTemplate() As MaskedEditCell Get Return TryCast(Me.CellTemplate, MaskedEditCell) End Get End Property End Class Public Class MaskedEditCell Inherits DataGridViewTextBoxCell Public Overrides Sub InitializeEditingControl(ByVal rowIndex As Integer, ByVal initialFormattedValue As Object, ByVal dataGridViewCellStyle As DataGridViewCellStyle) '' Set the value of the editing control to the current cell value. MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle) Dim mecol As MaskedEditColumn = DirectCast(OwningColumn, MaskedEditColumn) Dim ctl As MaskedEditingControl = CType(DataGridView.EditingControl, MaskedEditingControl) Try ctl.Text = Me.Value.ToString Catch ctl.Text = "" End Try ctl.Mask = mecol.Mask ctl.PromptChar = mecol.PromptChar ctl.ValidatingType = mecol.ValidatingType End Sub Public Overrides ReadOnly Property EditType() As Type Get '' Return the type of the editing contol that CalendarCell uses. Return GetType(MaskedEditingControl) End Get End Property Public Overrides ReadOnly Property ValueType() As Type Get '' Return the type of the value that CalendarCell contains. Return GetType(String) End Get End Property Public Overrides ReadOnly Property DefaultNewRowValue() As Object Get '' Use the current date and time as the default value. Return "" End Get End Property Protected Overrides Sub Paint(ByVal graphics As System.Drawing.Graphics, ByVal clipBounds As System.Drawing.Rectangle, ByVal cellBounds As System.Drawing.Rectangle, ByVal rowIndex As Integer, ByVal cellState As System.Windows.Forms.DataGridViewElementStates, ByVal value As Object, ByVal formattedValue As Object, ByVal errorText As String, ByVal cellStyle As System.Windows.Forms.DataGridViewCellStyle, ByVal advancedBorderStyle As System.Windows.Forms.DataGridViewAdvancedBorderStyle, ByVal paintParts As System.Windows.Forms.DataGridViewPaintParts) MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts) End Sub End Class Class MaskedEditingControl Inherits MaskedTextBox Implements IDataGridViewEditingControl Private dataGridViewControl As DataGridView Private valueIsChanged As Boolean = False Private rowIndexNum As Integer Public Property EditingControlFormattedValue() As Object _ Implements IDataGridViewEditingControl.EditingControlFormattedValue Get Return Me.Text End Get Set(ByVal value As Object) Me.Text = value.ToString End Set End Property Public Function EditingControlWantsInputKey(ByVal key As Keys, ByVal dataGridViewWantsInputKey As Boolean) As Boolean _ Implements IDataGridViewEditingControl.EditingControlWantsInputKey Return True End Function Public Function GetEditingControlFormattedValue(ByVal context _ As DataGridViewDataErrorContexts) As Object _ Implements IDataGridViewEditingControl.GetEditingControlFormattedValue Return Me.Text End Function Public Sub ApplyCellStyleToEditingControl(ByVal dataGridViewCellStyle As _ DataGridViewCellStyle) _ Implements IDataGridViewEditingControl.ApplyCellStyleToEditingControl Me.Font = dataGridViewCellStyle.Font Me.ForeColor = dataGridViewCellStyle.ForeColor Me.BackColor = dataGridViewCellStyle.BackColor End Sub Public Property EditingControlRowIndex() As Integer _ Implements IDataGridViewEditingControl.EditingControlRowIndex Get Return rowIndexNum End Get Set(ByVal value As Integer) rowIndexNum = value End Set End Property Public Sub PrepareEditingControlForEdit(ByVal selectAll As Boolean) _ Implements IDataGridViewEditingControl.PrepareEditingControlForEdit '' No preparation needs to be done. End Sub Public ReadOnly Property RepositionEditingControlOnValueChange() _ As Boolean Implements _ IDataGridViewEditingControl.RepositionEditingControlOnValueChange Get Return False End Get End Property Public Property EditingControlDataGridView() As DataGridView _ Implements IDataGridViewEditingControl.EditingControlDataGridView Get Return dataGridViewControl End Get Set(ByVal value As DataGridView) dataGridViewControl = value End Set End Property Public Property EditingControlValueChanged() As Boolean _ Implements IDataGridViewEditingControl.EditingControlValueChanged Get Return valueIsChanged End Get Set(ByVal value As Boolean) valueIsChanged = value End Set End Property Public ReadOnly Property EditingControlCursor() As Cursor _ Implements IDataGridViewEditingControl.EditingPanelCursor Get Return MyBase.Cursor End Get End Property Protected Overrides Sub OnTextChanged(ByVal e As System.EventArgs) '' Notify the DataGridView that the contents of the cell have changed. valueIsChanged = True Me.EditingControlDataGridView.NotifyCurrentCellDirty(True) MyBase.OnTextChanged(e) End Sub End Class

Introduciré varias cadenas en las etiquetas de un formulario de Windows (no las uso mucho). Las cuerdas serán similares a las siguientes:

"El zorro marrón rápido j___ed sobre el perro l__y"

Quiero mostrar la cadena en una etiqueta pero superponer un TextBox exactamente donde están las letras que faltan.

Habrá más de 300 cuerdas, y estoy buscando la forma más sencilla y elegante de hacerlo.

¿Cómo reposiciono el cuadro de texto con precisión para cada cadena?

EDITAR: Un MaskTextBox no funcionará ya que necesito soporte multilínea.


Esto puede ser excesivo dependiendo de lo complejo que quiera que sea, pero un control de navegador web de Winforms (que es esencialmente MSIE que se ejecuta dentro de su aplicación Winforms) puede funcionar como un editor donde usted controla qué partes son editables.

Cargue su contenido con las partes editables etiquetadas como tales, por ejemplo:

<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> <meta http-equiv="X-UA-Compatible" content="IE=10" /> <style> span.spEditable { background-color: #f0f0f0; } </style> </head> <body> <div id="someText">The quick brown fox j<span contenteditable="true" class="spEditable">___</span>ed over the l<span contenteditable="true" class="spEditable">__</span>y hound</div> </body> </html>

Otra opción, un poco más de trabajo para codificar pero más liviano en términos de memoria / recursos, sería usar un Panel de Flujo de FlowLayoutPanel , agregar paneles normales al Panel de Flujo de Pagos, y luego colocar etiquetas o cuadros de texto en esos paneles dependiendo de si un panel representa un parte fija o editable, y cambiar su tamaño para que coincida con la longitud del contenido. Puede usar MeasureString para averiguar el ancho del contenido en cada etiqueta / cuadro de texto para cambiar el tamaño.


Otro, usando un simple control TextBox.

EDIT1: Añadido soporte para fuentes proporcionales. Solo se admiten las fuentes Unicode.
EDIT2: Abandonó el marcador de subrayado, ahora con 2 caracteres unicode y subrayado de fuente. Añadido soporte IDisposable.
EDIT3: Añadido soporte multilínea

Lo que hace este código:
1) Toma una lista de cadenas (palabras) y subcadenas de esas palabras y el control de Texto de una etiqueta
2) Crea una máscara de las subcadenas utilizando dos caracteres espaciales Unicode (U + 2007 y U + 2002) de diferente tamaño, para que coincida con el tamaño de las letras para sustituir
3) Clasifica un TextBox sin borde (un objeto de clase que hereda de Textbox) utilizando el Ancho y la Altura calculados (en píxeles) de la subcadena. Establece su propiedad MaxLength a la longitud de la subcadena.
4) Calcula la posición de las subcadenas dentro de un Texto de etiqueta multilínea, comprueba si hay patrones duplicados y superpone los objetos de Texbox (clase Editor)

He usado una fuente de tamaño fijo (Lucida Console) debido a la máscara de caracteres .
Para tratar con fuentes proporcionales, se utilizan dos caracteres de máscara diferentes, dependiendo del ancho de los caracteres
(Es decir, diferentes caracteres de máscara de diferente ancho para coincidir con el ancho de los caracteres sustituidos).

Una representación visual de los resultados:
La tecla TAB se usa para pasar de un control TextBox al siguiente / anterior.
La tecla ENTER se usa para aceptar la edición. Entonces el código comprueba si es una coincidencia.
La tecla ESC restablece el texto y muestra la máscara inicial.

Se inicializa una lista de palabras especificando una palabra completa y una serie de caracteres contiguos para sustituir con una máscara: => jumped : umpe
y el control de etiqueta asociado.
Cuando se inicializa una clase de prueba, subtitula automáticamente todas las palabras en el texto de etiqueta especificado con una máscara de cuadro de texto.

public class QuizWord { public string Word { get; set; } public string WordMask { get; set; } } List<Quiz> QuizList = new List<Quiz>(); QuizList.Add(new Quiz(lblSampleText1, new List<QuizWord> { new QuizWord { Word = "jumped", WordMask = "umpe" }, new QuizWord { Word = "lazy", WordMask = "az" } })); QuizList.Add(new Quiz(lblSampleText2, new List<QuizWord> { new QuizWord { Word = "dolor", WordMask = "olo" }, new QuizWord { Word = "elit", WordMask = "li" } })); QuizList.Add(new Quiz(lblSampleText3, new List<QuizWord> { new QuizWord { Word = "Brown", WordMask = "row" }, new QuizWord { Word = "Foxes", WordMask = "oxe" }, new QuizWord { Word = "latinorum", WordMask = "atinoru" }, new QuizWord { Word = "Support", WordMask = "uppor" } }));

Esta es la clase de prueba:
Su trabajo consiste en recopilar todos los editores (TextBoxes) que se utilizan para cada etiqueta y calcular su ubicación, dada la posición de la cadena que deben sustituir en cada texto de la etiqueta.

public class Quiz : IDisposable { private bool _disposed = false; private List<QuizWord> _Words = new List<QuizWord>(); private List<Editor> _Editors = new List<Editor>(); private MultilineSupport _Multiline; private Control _Container = null; public Quiz() : this(null, null) { } public Quiz(Label RefControl, List<QuizWord> Words) { this._Container = RefControl.Parent; this.Label = null; if (RefControl != null) { this.Label = RefControl; this.Matches = new List<QuizWord>(); if (Words != null) { this._Multiline = new MultilineSupport(RefControl); this.Matches = Words; } } } public Label Label { get; set; } public List<QuizWord> Matches { get { return this._Words; } set { this._Words = value; Editors_Setup(); } } private void Editors_Setup() { if ((this._Words == null) || (this._Words.Count < 1)) return; int i = 1; foreach (QuizWord _word in _Words) { List<Point> _Positions = GetEditorsPosition(this.Label.Text, _word); foreach (Point _P in _Positions) { Editor _editor = new Editor(this.Label, _word.WordMask); _editor.Location = _P; _editor.Name = this.Label.Name + "Editor" + i.ToString(); ++i; _Editors.Add(_editor); this._Container.Controls.Add(_editor); this._Container.Controls[_editor.Name].BringToFront(); } } } private List<Point> GetEditorsPosition(string _labeltext, QuizWord _word) { return Regex.Matches(_labeltext, _word.WordMask) .Cast<Match>() .Select(t => t.Index).ToList() .Select(idx => this._Multiline.GetPositionFromCharIndex(idx)) .ToList(); } private class MultilineSupport { Label RefLabel; float _FontSpacingCoef = 1.8F; private TextFormatFlags _flags = TextFormatFlags.SingleLine | TextFormatFlags.Left | TextFormatFlags.NoPadding | TextFormatFlags.TextBoxControl; public MultilineSupport(Label label) { this.Lines = new List<string>(); this.LinesFirstCharIndex = new List<int>(); this.NumberOfLines = 0; Initialize(label); } public int NumberOfLines { get; set; } public List<string> Lines { get; set; } public List<int> LinesFirstCharIndex { get; set; } public int GetFirstCharIndexFromLine(int line) { if (LinesFirstCharIndex.Count == 0) return -1; return LinesFirstCharIndex.Count - 1 >= line ? LinesFirstCharIndex[line] : -1; } public int GetLineFromCharIndex(int index) { if (LinesFirstCharIndex.Count == 0) return -1; return LinesFirstCharIndex.FindLastIndex(idx => idx <= Index);; } public Point GetPositionFromCharIndex(int Index) { return CalcPosition(GetLineFromCharIndex(Index), Index); } private void Initialize(Label label) { this.RefLabel = label; if (label.Text.Trim().Length == 0) return; List<string> _wordslist = new List<string>(); string _substring = string.Empty; this.LinesFirstCharIndex.Add(0); this.NumberOfLines = 1; int _currentlistindex = 0; int _start = 0; _wordslist.AddRange(label.Text.Split(new char[] { (char)32 }, StringSplitOptions.None)); foreach (string _word in _wordslist) { ++_currentlistindex; int _wordindex = label.Text.IndexOf(_word, _start); int _sublength = MeasureString((_substring + _word + (_currentlistindex < _wordslist.Count ? ((char)32).ToString() : string.Empty))); if (_sublength > label.Width) { this.Lines.Add(_substring); this.LinesFirstCharIndex.Add(_wordindex); this.NumberOfLines += 1; _substring = string.Empty; } _start += _word.Length + 1; _substring += _word + (char)32; } this.Lines.Add(_substring.TrimEnd()); } private Point CalcPosition(int Line, int Index) { int _font_padding = (int)((RefLabel.Font.Size - (int)(RefLabel.Font.Size % 12)) * _FontSpacingCoef); int _verticalpos = Line * this.RefLabel.Font.Height + this.RefLabel.Top; int _horizontalpos = MeasureString(this.Lines[Line].Substring(0, Index - GetFirstCharIndexFromLine(Line))); return new Point(_horizontalpos + _font_padding, _verticalpos); } private int MeasureString(string _string) { return TextRenderer.MeasureText(RefLabel.CreateGraphics(), _string, this.RefLabel.Font, this.RefLabel.Size, _flags).Width; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected void Dispose(bool IsSafeDisposing) { if (IsSafeDisposing && (!this._disposed)) { foreach (Editor _editor in _Editors) if (_editor != null) _editor.Dispose(); this._disposed = true; } } }

Esta es la clase Editor (se hereda de TextBox):
Construye y calcula la longitud de los caracteres de la máscara y se auto dimensiona usando este valor.
Tiene capacidades básicas de edición.

public class Editor : TextBox { private string SubstChar = string.Empty; private string SubstCharLarge = ((char)0x2007).ToString(); private string SubstCharSmall = ((char)0x2002).ToString(); private Font NormalFont = null; private Font UnderlineFont = null; private string WordMask = string.Empty; private TextFormatFlags _flags = TextFormatFlags.NoPadding | TextFormatFlags.Left | TextFormatFlags.Bottom | TextFormatFlags.WordBreak | TextFormatFlags.TextBoxControl; public Editor(Label RefLabel, string WordToMatch) { this.BorderStyle = BorderStyle.None; this.TextAlign = HorizontalAlignment.Left; this.Margin = new Padding(0); this.MatchWord = WordToMatch; this.MaxLength = WordToMatch.Length; this._Label = RefLabel; this.NormalFont = RefLabel.Font; this.UnderlineFont = new Font(RefLabel.Font, (RefLabel.Font.Style | FontStyle.Underline)); this.Font = this.UnderlineFont; this.Size = GetTextSize(WordToMatch); this.WordMask = CreateMask(this.Size.Width); this.Text = this.WordMask; this.BackColor = RefLabel.BackColor; this.ForeColor = RefLabel.ForeColor; this.KeyDown += this.KeyDownHandler; this.Enter += (sender, e) => { this.Font = this.UnderlineFont; this.SelectionStart = 0; this.SelectionLength = 0; }; this.Leave += (sender, e) => { CheckWordMatch(); }; } public string MatchWord { get; set; } private Label _Label { get; set; } public void KeyDownHandler(object sender, KeyEventArgs e) { int _start = this.SelectionStart; switch (e.KeyCode) { case Keys.Back: if (this.SelectionStart > 0) { this.AppendText(SubstChar); this.SelectionStart = 0; this.ScrollToCaret(); } this.SelectionStart = _start; break; case Keys.Delete: if (this.SelectionStart < this.Text.Length) { this.AppendText(SubstChar); this.SelectionStart = 0; this.ScrollToCaret(); } this.SelectionStart = _start; break; case Keys.Enter: e.SuppressKeyPress = true; CheckWordMatch(); break; case Keys.Escape: e.SuppressKeyPress = true; this.Text = this.WordMask; this.ForeColor = this._Label.ForeColor; break; default: if ((e.KeyCode >= (Keys)32 & e.KeyCode <= (Keys)127) && (e.KeyCode < (Keys)36 | e.KeyCode > (Keys)39)) { int _removeat = this.Text.LastIndexOf(SubstChar); if (_removeat > -1) this.Text = this.Text.Remove(_removeat, 1); this.SelectionStart = _start; } break; } } private void CheckWordMatch() { if (this.Text != this.WordMask) { this.Font = this.Text == this.MatchWord ? this.NormalFont : this.UnderlineFont; this.ForeColor = this.Text == this.MatchWord ? Color.Green : Color.Red; } else { this.ForeColor = this._Label.ForeColor; } } private Size GetTextSize(string _mask) { return TextRenderer.MeasureText(this._Label.CreateGraphics(), _mask, this._Label.Font, this._Label.Size, _flags); } private string CreateMask(int _EditorWidth) { string _TestMask = new StringBuilder().Insert(0, SubstCharLarge, this.MatchWord.Length).ToString(); SubstChar = (GetTextSize(_TestMask).Width <= _EditorWidth) ? SubstCharLarge : SubstCharSmall; return SubstChar == SubstCharLarge ? _TestMask : new StringBuilder().Insert(0, SubstChar, this.MatchWord.Length).ToString(); } }


Una opción es usar un cuadro de texto enmascarado.

En tu ejemplo, deberías configurar la máscara para:

"The quick brown fox jLLLed over the l/azy hound"

Que aparecería como:

"The quick brown fox j___ed over the lazy hound"

Y solo permita que se ingresen 3 caracteres (az y AZ) en el espacio. Y la máscara se puede cambiar fácilmente a través del código.

EDITAR: Por conveniencia ...

Aquí hay una lista y descripción de los caracteres de máscara.

(tomado de http://www.c-sharpcorner.com/uploadfile/mahesh/maskedtextbox-in-C-Sharp/ ).

0 - Digit, required. Value between 0 and 9. 9 - Digit or space, optional. # - Digit or space, optional. If this position is blank in the mask, it will be rendered as a space in the Text property. L - Letter, required. Restricts input to the ASCII letters a-z and A-Z. ? - Letter, optional. Restricts input to the ASCII letters a-z and A-Z. & - Character, required. C - Character, optional. Any non-control character. A - Alphanumeric, required. a - Alphanumeric, optional. . - Decimal placeholder. , - Thousands placeholder. : - Time separator. / - Date separator. $ - Currency symbol. < - Shift down. Converts all characters that follow to lowercase. > - Shift up. Converts all characters that follow to uppercase. | - Disable a previous shift up or shift down. / - Escape. Escapes a mask character, turning it into a literal. "//" is the escape sequence for a backslash.

Todos los demás personajes - Literales. Todos los elementos que no sean de máscara aparecerán como ellos mismos dentro de MaskedTextBox. Los literales siempre ocupan una posición estática en la máscara en el tiempo de ejecución, y el usuario no puede moverlos ni eliminarlos.


Averigüe en qué carácter se hizo clic, si era un guión bajo, entonces evalúe los guiones bajos a la derecha e izquierda y muestre un cuadro de texto encima de los guiones bajos.

Puede modificar este código, la etiqueta es en realidad un cuadro de texto de solo lectura para acceder a los métodos GetCharIndexFromPosition y GetPositionFromCharIndex .

namespace WindowsFormsApp1 { public partial class Form1 : Form { private System.Windows.Forms.TextBox txtGap; private System.Windows.Forms.Label label2; private System.Windows.Forms.Label lblClickedOn; private System.Windows.Forms.TextBox txtTarget; private void txtTarget_MouseDown(object sender, MouseEventArgs e) { int index = txtTarget.GetCharIndexFromPosition(e.Location); //Debugging help Point pt = txtTarget.GetPositionFromCharIndex(index); lblClickedOn.Text = index.ToString(); txtGap.Visible = false; if (txtTarget.Text[index] == (char)''_'') { //Work out the left co-ordinate for the textbox by checking the number of underscores prior int priorLetterToUnderscore = 0; for (int i = index - 1; i > -1; i--) { if (txtTarget.Text[i] != (char)''_'') { priorLetterToUnderscore = i + 1; break; } } int afterLetterToUnderscore = 0; for (int i = index + 1; i <= txtTarget.Text.Length; i++) { if (txtTarget.Text[i] != (char)''_'') { afterLetterToUnderscore = i; break; } } //Measure the characters width earlier than the priorLetterToUnderscore pt = txtTarget.GetPositionFromCharIndex(priorLetterToUnderscore); int left = pt.X + txtTarget.Left; pt = txtTarget.GetPositionFromCharIndex(afterLetterToUnderscore); int width = pt.X + txtTarget.Left - left; //Check the row/line we are on SizeF textSize = this.txtTarget.CreateGraphics().MeasureString("A", this.txtTarget.Font, this.txtTarget.Width); int line = pt.Y / (int)textSize.Height; txtGap.Location = new Point(left, txtTarget.Top + (line * (int)textSize.Height)); txtGap.Width = width; txtGap.Text = string.Empty; txtGap.Visible = true; } } private void Form1_Click(object sender, EventArgs e) { txtGap.Visible = false; } public Form1() { this.txtGap = new System.Windows.Forms.TextBox(); this.label2 = new System.Windows.Forms.Label(); this.lblClickedOn = new System.Windows.Forms.Label(); this.txtTarget = new System.Windows.Forms.TextBox(); this.SuspendLayout(); // // txtGap // this.txtGap.Font = new System.Drawing.Font("Microsoft Sans Serif", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.txtGap.Location = new System.Drawing.Point(206, 43); this.txtGap.Name = "txtGap"; this.txtGap.Size = new System.Drawing.Size(25, 20); this.txtGap.TabIndex = 1; this.txtGap.Text = "ump"; this.txtGap.Visible = false; // // label2 // this.label2.AutoSize = true; this.label2.Location = new System.Drawing.Point(22, 52); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(84, 13); this.label2.TabIndex = 2; this.label2.Text = "Char clicked on:"; // // lblClickedOn // this.lblClickedOn.AutoSize = true; this.lblClickedOn.Location = new System.Drawing.Point(113, 52); this.lblClickedOn.Name = "lblClickedOn"; this.lblClickedOn.Size = new System.Drawing.Size(13, 13); this.lblClickedOn.TabIndex = 3; this.lblClickedOn.Text = "_"; // // txtTarget // this.txtTarget.BackColor = System.Drawing.SystemColors.Menu; this.txtTarget.BorderStyle = System.Windows.Forms.BorderStyle.None; this.txtTarget.Font = new System.Drawing.Font("Microsoft Sans Serif", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.txtTarget.Location = new System.Drawing.Point(22, 21); this.txtTarget.Name = "txtTarget"; this.txtTarget.ReadOnly = true; this.txtTarget.Size = new System.Drawing.Size(317, 16); this.txtTarget.TabIndex = 4; this.txtTarget.Text = "The quick brown fox j___ed over the l__y hound"; this.txtTarget.MouseDown += new System.Windows.Forms.MouseEventHandler(this.txtTarget_MouseDown); // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(394, 95); this.Controls.Add(this.txtGap); this.Controls.Add(this.txtTarget); this.Controls.Add(this.lblClickedOn); this.Controls.Add(this.label2); this.Name = "Form1"; this.Text = "Form1"; this.Click += new System.EventHandler(this.Form1_Click); this.ResumeLayout(false); this.PerformLayout(); } } }

Para desactivar la selección del cuadro de texto (etiqueta falsa): https://.com/a/42391380/495455

Editar:

Lo hice funcionar para cuadros de texto multilínea:


He desarrollado una solución un poco más fácil de entender que podría ayudarlo a comenzar por lo menos (no tuve tiempo para jugar con varias entradas en la misma etiqueta, pero funcionó correctamente para 1).

private void Form1_Load() { for (var i = 0; i < 20; i++) { Label TemporaryLabel = new Label(); TemporaryLabel.AutoSize = false; TemporaryLabel.Size = new Size(flowLayoutPanel1.Width, 50); TemporaryLabel.Text = "This is a ______ message"; string SubText = ""; var StartIndex = TemporaryLabel.Text.IndexOf(''_''); var EndIndex = TemporaryLabel.Text.LastIndexOf(''_''); if ((StartIndex != -1 && EndIndex != -1) && EndIndex > StartIndex) { string SubString = TemporaryLabel.Text.Substring(StartIndex, EndIndex - StartIndex); SizeF nSize = Measure(SubString); TextBox TemporaryBox = new TextBox(); TemporaryBox.Size = new Size((int)nSize.Width, 50); TemporaryLabel.Controls.Add(TemporaryBox); TemporaryBox.Location = new Point(TemporaryBox.Location.X + (int)Measure(TemporaryLabel.Text.Substring(0, StartIndex - 2)).Width, TemporaryBox.Location.Y); } else continue; flowLayoutPanel1.Controls.Add(TemporaryLabel); } }

EDITAR: Olvidé el para incluir el método de "Medida":

private SizeF Measure(string Data) { using (var BMP = new Bitmap(1, 1)) { using (Graphics G = Graphics.FromImage(BMP)) { return G.MeasureString(Data, new Font("segoe ui", 11, FontStyle.Regular)); } } }

El resultado:

Entonces debería poder asignar controladores de eventos a los cuadros de texto individuales / nombrarlos para facilitar el acceso más adelante cuando el usuario interactúe con la entrada dada.


Para satisfacer este requisito, IMO es mejor usar aquellas funciones de Windows Forms que permiten la interoperabilidad con HTML o WPF y ElementHost un control WebBrowser o un ElementHost WPF para mostrar el contenido a los usuarios. Antes de leer esta respuesta, por favor considere:

  • Los usuarios no deben poder borrar los ____ campos. Si pueden eliminarlos, una vez que se hayan movido a otro espacio en blanco, perderán la capacidad de encontrar el campo despejado.
  • Es mejor permitir que los usuarios usen la tecla Tab para moverse entre ____ campos.
  • Como se menciona en la pregunta: Un MaskTextBox no funcionará ya que necesito soporte multilínea.
  • Como se menciona en la pregunta: habrá más de 300 cadenas, por lo que mezclar un montón de control de Windows Forms no es una buena idea.

Usar HTML como Vista de un modelo de C # y mostrarlo en el control WebBrowser

Aquí compartiré una respuesta simple basada en mostrar HTML en WebBrowser control WebBrowser . Como opción, puede usar un control WebBrowser y crear un html adecuado para mostrar en WebBrowser control WebBrowser usando una clase de modo.

La idea principal es crear una salida html basada en el modelo de prueba (incluido el texto original y el rango de espacios en blanco) y renderizar el modelo usando html y mostrarlo en un control WebBrowser .

Por ejemplo utilizando el siguiente modelo:

quiz = new Quiz(); quiz.Text = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; quiz.Ranges.Add(new SelectionRange(6, 5)); quiz.Ranges.Add(new SelectionRange(30, 7)); quiz.Ranges.Add(new SelectionRange(61, 2)); quiz.Ranges.Add(new SelectionRange(82, 6));

Se renderizará esta salida:

Luego, después de que el usuario ingresó los valores, se mostrará de esta manera:

Y por último, cuando haga clic en el botón Show Result , se mostrarán las respuestas correctas en color verde y las respuestas incorrectas en color rojo:

Código

Puede descargar el código fuente completo de trabajo, por ejemplo aquí:

La implementación es bastante simple:

public class Quiz { public Quiz() { Ranges = new List<SelectionRange>(); } public string Text { get; set; } public List<SelectionRange> Ranges { get; private set; } public string Render() { /* rendering logic*/ } }

Aquí está el código completo de la clase de Quiz :

public class Quiz { public Quiz() { Ranges = new List<SelectionRange>(); } public string Text { get; set; } public List<SelectionRange> Ranges { get; private set; } public string Render() { var content = new StringBuilder(Text); for (int i = Ranges.Count - 1; i >= 0; i--) { content.Remove(Ranges[i].Start, Ranges[i].Length); var length = Ranges[i].Length; var replacement = $@"<input id=""q{i}"" type=""text"" class=""editable"" maxlength=""{length}"" style=""width: {length*1.162}ch;"" />"; content.Insert(Ranges[i].Start, replacement); } var result = string.Format(Properties.Resources.Template, content); return result; } } public class SelectionRange { public SelectionRange(int start, int length) { Start = start; Length = length; } public int Start { get; set; } public int Length { get; set; } }

Y aquí está el contenido de la plantilla html:

<html> <head> <meta http-equiv="X-UA-Compatible" content="IE=11" /> <script> function setCorrect(id){{document.getElementById(id).className = ''editable correct'';}} function setWrong(id){{document.getElementById(id).className = ''editable wrong'';}} </script> <style> div {{ line-height: 1.5; font-family: calibri; }} .editable {{ border-width: 0px; border-bottom: 1px solid #cccccc; font-family: monospace; display: inline-block; outline: 0; color: #0000ff; font-size: 105%; }} .editable.correct {{ color: #00ff00; border-bottom: 1px solid #00ff00; }} .editable.wrong {{ color: #ff0000; border-bottom: 1px solid #ff0000; }} .editable::-ms-clear {{ width: 0; height: 0; }} </style> </head> <body> <div> {0} </div> </body> </html>


Probaría algo como esto (seguro que necesitará algunos ajustes de tamaño):

var indexOfCompletionString = label.Text.IndexOf("____"); var labelLeftPos = label.Left; var labelTopPos = label.Top; var completionStringMeasurments = this.CreateGraphics().MeasureString("____", label.Font); var substr = label.Text.Substring(0, indexOfCompletionString); var substrMeasurments = this.CreateGraphics().MeasureString(substr, label.Font); var tBox = new TextBox { Height = (int)completionStringMeasurments.Height, Width = (int)completionStringMeasurments.Width, Location = new Point(labelLeftPos + (int)substrMeasurments.Width, labelTopPos) }; tBox.BringToFront(); Controls.Add(tBox); Controls.SetChildIndex(tBox, 0);


Así es como lo abordaría. Divida con la expresión regular la cadena y cree etiquetas separadas para cada una de las cadenas secundarias. Coloque todas las etiquetas en un FlowLayoutPanel . Cuando haga clic en una etiqueta, elimínela y, en la misma posición, agregue el TextBox de edición. Cuando se pierde el enfoque (o se presiona Entrar), quite el Cuadro de texto y vuelva a colocar la Etiqueta; establezca el texto de la etiqueta al texto del TextBox.

Primero crea la costumbre UserControlcomo la siguiente

public partial class WordEditControl : UserControl { private readonly Regex underscoreRegex = new Regex("(__*)"); private List<EditableLabel> labels = new List<EditableLabel>(); public WordEditControl() { InitializeComponent(); } public void SetQuizText(string text) { contentPanel.Controls.Clear(); foreach (string item in underscoreRegex.Split(text)) { var label = new Label { FlatStyle = FlatStyle.System, Padding = new Padding(), Margin = new Padding(0, 3, 0, 0), TabIndex = 0, Text = item, BackColor = Color.White, TextAlign = ContentAlignment.TopCenter }; if (item.Contains("_")) { label.ForeColor = Color.Red; var edit = new TextBox { Margin = new Padding() }; labels.Add(new EditableLabel(label, edit)); } contentPanel.Controls.Add(label); using (Graphics g = label.CreateGraphics()) { SizeF textSize = g.MeasureString(item, label.Font); label.Size = new Size((int)textSize.Width - 4, (int)textSize.Height); } } } // Copied it from the .Designer file for the sake of completeness private void InitializeComponent() { this.contentPanel = new System.Windows.Forms.FlowLayoutPanel(); this.SuspendLayout(); // // contentPanel // this.contentPanel.Dock = System.Windows.Forms.DockStyle.Fill; this.contentPanel.Location = new System.Drawing.Point(0, 0); this.contentPanel.Name = "contentPanel"; this.contentPanel.Size = new System.Drawing.Size(150, 150); this.contentPanel.TabIndex = 0; // // WordEditControl // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.Controls.Add(this.contentPanel); this.Name = "WordEditControl"; this.ResumeLayout(false); } private System.Windows.Forms.FlowLayoutPanel contentPanel; }

Éste acepta el texto de la prueba, luego lo divide con expresiones regulares y crea las etiquetas y los cuadros de texto. Si está interesado en saber cómo hacer que Regex devuelva las coincidencias y no solo las subcadenas, eche un vistazo here

Luego, para encargarme de la transición entre la edición, creé una EditableLabelclase. Se parece a esto

class EditableLabel { private string originalText; private Label label; private TextBox editor; public EditableLabel(Label label, TextBox editor) { this.label = label ?? throw new ArgumentNullException(nameof(label)); this.editor = editor ?? throw new ArgumentNullException(nameof(editor)); originalText = label.Text; using (Graphics g = label.CreateGraphics()) { this.editor.Width = (int)g.MeasureString("M", this.editor.Font).Width * label.Text.Length; } editor.LostFocus += (s, e) => SetText(); editor.KeyUp += (s, e) => { if (e.KeyCode == Keys.Enter) { SetText(); } }; label.Click += (s, e) => { Swap(label, editor); this.editor.Focus(); }; } private void SetText() { Swap(editor, label); string editorText = editor.Text.Trim(); label.Text = editorText.Length == 0 ? originalText : editorText; using (Graphics g = label.CreateGraphics()) { SizeF textSize = g.MeasureString(label.Text, label.Font); label.Width = (int)textSize.Width - 4; } } private void Swap(Control original, Control replacement) { var panel = original.Parent; int index = panel.Controls.IndexOf(original); panel.Controls.Remove(original); panel.Controls.Add(replacement); panel.Controls.SetChildIndex(replacement, index); } }

Puede usar el UserControl personalizado arrastrándolo y soltándolo desde el diseñador (después de construirlo con éxito) o agregarlo de esta manera:

public partial class Form1 : Form { private WordEditControl wordEditControl1; public Form1() { InitializeComponent(); wordEditControl1 = new WordEditControl(); wordEditControl1.SetQuizText("The quick brown fox j___ed over the l__y hound"); Controls.Add(wordEditControl1) } }

El resultado final se verá así:

Pros y contras

Lo que considero bueno con esta solución:

  • Es flexible ya que puedes darle un tratamiento especial a la etiqueta editable. Puede cambiar su color como lo hice aquí, poner un menú contextual con acciones como "Borrar", "Evaluar", "Mostrar respuesta", etc.

  • Es casi multilínea. El panel de diseño de flujo se ocupa de la envoltura del componente y funcionará si hay cortes frecuentes en la cadena de prueba. De lo contrario, tendrá una etiqueta muy grande que no encajará en el panel. Sin embargo, puedes usar un truco para eludir eso y usarlo /npara romper cadenas largas. Puede manejarlo /nen el SetQuizText()pero lo dejaré para usted :) Tenga en cuenta que no se manejará con la etiqueta que hará la etiqueta y que no se unirá bien con el Panel de Flujo de Flujo.

  • TextBoxes pueden encajar mejor. El cuadro de texto de edición que se ajustará a 3 caracteres no tendrá lo mismo que la etiqueta con 3 caracteres. Con esta solución no tienes que preocuparte por eso. Una vez que la etiqueta editada es reemplazada por el cuadro de texto, los siguientes controles se desplazarán hacia la derecha para ajustarse al cuadro de texto. Una vez que la etiqueta vuelve, los otros controles pueden realinearse.

Sin embargo, lo que no me gusta es que todo esto tendrá un precio: tienes que alinear manualmente los controles. Es por eso que ves algunos números mágicos (que no me gustan y me esfuerzo por evitarlos). El cuadro de texto no tiene la misma altura que la etiqueta. Es por eso que he rellenado todas las etiquetas 3 píxeles en la parte superior . También por alguna razón, no tengo tiempo para investigar ahora, MeasureString()no devuelve el ancho exacto, es un poco más ancho. Con prueba y error me di cuenta de que eliminar 4 píxeles alinearía mejor las etiquetas

Ahora dices que habrá 300 cuerdas, así que supongo que te refieres a 300 "quizes". Si estos son tan pequeños como el zorro marrón rápido, creo que la forma en que se maneja la multilínea en mi solución no le causará ningún problema. Pero si el texto es más grande, sugeriría que vaya con una de las otras respuestas que funcionan con cuadros de texto de varias líneas.

Sin embargo, tenga en cuenta que si esto se vuelve más complejo, como por ejemplo los indicadores sofisticados de que el texto era correcto o incorrecto o si desea que el control responda a los cambios de tamaño, necesitará controles de texto que el marco no proporciona. Lamentablemente, la biblioteca de formularios de Windows ha permanecido estancada durante varios años, y es difícil encontrar soluciones elegantes en problemas como el suyo, al menos sin controles comerciales.

Espero que te ayude a empezar.


Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load Me.Controls.Add(New TestTextBox With {.Text = "The quick brown fox j___ed over the l__y hound", .Dock = DockStyle.Fill, .Multiline = True}) End Sub Public Class TestTextBox Inherits Windows.Forms.TextBox Protected Overrides Sub OnKeyDown(e As KeyEventArgs) Dim S = Me.SelectionStart Me.SelectionStart = ReplceOnlyWhatNeeded(Me.SelectionStart, (Chr(e.KeyCode))) e.SuppressKeyPress = True '' Block Evrything End Sub Public Overrides Property Text As String Get Return MyBase.Text End Get Set(value As String) ''List Of Editable Symbols ValidIndex.Clear() For x = 0 To value.Length - 1 If value(x) = DefaultMarker Then ValidIndex.Add(x) Next MyBase.Text = value Me.SelectionStart = Me.ValidIndex.First End Set End Property ''--------------------------------------- Private DefaultMarker As Char = "_" Private ValidIndex As New List(Of Integer) Private Function ReplceOnlyWhatNeeded(oPoz As Integer, oInputChar As Char) As Integer ''Replece one symbol in string at pozition, in case delete put default marker If Me.ValidIndex.Contains(Me.SelectionStart) And (Char.IsLetter(oInputChar) Or Char.IsNumber(oInputChar)) Then MyBase.Text = MyBase.Text.Insert(Me.SelectionStart, oInputChar).Remove(Me.SelectionStart + 1, 1) '' Replece in Output String new symbol ElseIf Me.ValidIndex.Contains(Me.SelectionStart) And Asc(oInputChar) = 8 Then MyBase.Text = MyBase.Text.Insert(Me.SelectionStart, DefaultMarker).Remove(Me.SelectionStart + 1, 1) '' Add Blank Symbol when backspace Else Return Me.ValidIndex.First ''Avrything else not allow End If ''Return Next Point to edit Dim Newpoz As Integer? = Nothing For Each x In Me.ValidIndex If x > oPoz Then Return x Exit For End If Next Return Me.ValidIndex.First End Function End Class

U No necesita etiqueta y cuadro de texto para esto, u puede hacerlo en cualquier pantalla, en cualquier control de cadena. Solo necesita la posición de entrada del usuario, la cadena que desea cambiar con símbolos como marcador de posición y carácter de entrada, se muestra en el cuadro de texto, en la entrada clave para que no se importe el número de controles. Para copia de cadena larga u puede siempre para cada char.