Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/sqlite/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何(优雅地)将文本框转换到字符串特定部分的标签上?_C#_.net_Vb.net_Winforms - Fatal编程技术网

C# 如何(优雅地)将文本框转换到字符串特定部分的标签上?

C# 如何(优雅地)将文本框转换到字符串特定部分的标签上?,c#,.net,vb.net,winforms,C#,.net,Vb.net,Winforms,我将向Windows窗体上的标签中输入大量字符串(我不经常使用这些字符串)。字符串将类似于以下内容: “那只敏捷的棕色狐狸越过了那只猎犬” 我想在标签中显示字符串,但在缺少的字母所在的位置覆盖一个文本框 将有300多个字符串,我正在寻找最简单、最优雅的方法来完成它 如何为每个字符串准确地重新定位文本框 编辑:屏蔽文本框无法工作,因为我需要多行支持。一个选项是使用屏蔽文本框 在您的示例中,您可以将遮罩设置为: "The quick brown fox jLLLed over the l\azy h

我将向Windows窗体上的标签中输入大量字符串(我不经常使用这些字符串)。字符串将类似于以下内容:

“那只敏捷的棕色狐狸越过了那只猎犬”

我想在标签中显示字符串,但在缺少的字母所在的位置覆盖一个文本框

将有300多个字符串,我正在寻找最简单、最优雅的方法来完成它

如何为每个字符串准确地重新定位文本框


编辑:屏蔽文本框无法工作,因为我需要多行支持。

一个选项是使用屏蔽文本框

在您的示例中,您可以将遮罩设置为:

"The quick brown fox jLLLed over the l\azy hound"
将显示为:

"The quick brown fox j___ed over the lazy hound"
并且只允许在间隙中输入3个字符(a-z和a-z)。 通过代码可以很容易地更改掩码

编辑: 为了方便起见

下面是掩蔽字符的列表和说明

(摘自)

0-数字,必填项。值介于0和9之间。
9位数字或空格,可选。
#-数字或空格,可选。如果此位置在遮罩中为空,则将在“文本”属性中将其渲染为空格。
L-字母,必填。将输入限制为ASCII字母a-z和a-z。
? - 字母,可选。将输入限制为ASCII字母a-z和a-z。
&-字符,必填。
C字符,可选。任何非控制字符。
A-字母数字,必填项。
a-字母数字,可选。
.  - 十进制占位符。
,-千占位符。
:-时间分隔符。
/-日期分隔符。
$-货币符号。
<-向下换档。将后面的所有字符转换为小写。
>-上档。将后面的所有字符转换为大写。
|-禁用上一次升档或降档。
\-逃跑。转义掩码字符,将其转换为文字。“\\”是反斜杠的转义序列。

所有其他字符-文字。所有非掩码元素将在MaskedTextBox中显示为它们自己。文字在运行时始终占据掩码中的静态位置,用户无法移动或删除。

这可能有些过分,具体取决于您希望的复杂程度,但winforms web浏览器控件(本质上是在winforms应用程序中运行的MSIE)可以用作编辑器,您可以在其中控制哪些部分是可编辑的

加载带有可编辑部件标记的内容,例如:


span.spEditable{背景色:#f0;}
那只敏捷的棕色狐狸从猎犬身上掠过

计算出点击的字符(如果是下划线),然后左右调整下划线的大小,并在下划线上方显示一个文本框

您可以调整此代码,标签实际上是一个只读文本框,用于访问
GetCharIndexFromPosition
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();
        }
    }        
}
namespace WindowsFormsApp1
{
公共部分类Form1:Form
{
private System.Windows.Forms.TextBox txtGap;
private System.Windows.Forms.label2;
private System.Windows.Forms.Label lblClickedOn;
private System.Windows.Forms.TextBox txtTarget;
私有void txtTarget_MouseDown(对象发送方,MouseEventArgs e)
{
int index=txtTarget.GetCharIndexFromPosition(e.Location);
//调试帮助
点pt=txtTarget.GetPositionFromCharIndex(索引);
lblClickedOn.Text=index.ToString();
txtGap.Visible=false;
if(txtTarget.Text[index]=(char)'
{
//通过事先检查下划线的数量,计算出文本框的左坐标
int priorletterto下划线=0;
对于(int i=索引-1;i>-1;i--)
{
if(txtTarget.Text[i]!=(char)382;)
{
priorletterto=i+1;
打破
}
}
int AFTERLETTOTOUNDERSCORE=0;

对于(inti=index+1;i考虑使用DataGridView和屏蔽单元格列的组合

在显示编辑控件时,您将更改该特定行的掩码

下面是一些代码使用示例,其中包括网格和每行的唯一掩码

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
以及网格柱,来源于:


这就是我的方法。使用正则表达式拆分字符串并为每个子字符串创建单独的标签。将所有标签放在一个列表中。单击标签时,将其删除并在相同位置添加编辑文本框。当焦点丢失(或按enter键)时移除文本框并放回标签;将标签文本设置为文本框文本

首先创建自定义
UserControl
,如下所示

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;
}
您可以通过从设计器中拖放自定义UserControl(成功构建后)或按如下方式添加自定义UserControl:

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)
    }
}
最终结果如下所示:

利弊 我认为这个解决方案很好:

  • 它很灵活,因为你可以对可编辑的标签进行特殊处理。你可以像我在这里做的那样改变它的颜色,放置一个带有“清除”、“评估”、“显示答案”等操作的上下文菜单

  • 它几乎是多行的。flow layout面板负责组件包装,如果测验字符串中有频繁的中断,它将工作。否则,您将有一个非常大的标签,无法放在面板中。您可以使用一个技巧来绕过它,并使用
    \n
    来中断长字符串。您可以处理
    \n
    SetQuizText()
    中,但我将把它留给您:)请记住,id您不处理它,标签就可以了,它不会与FlowLayoutPanel很好地绑定

  • 文本框可以更好地适应。适合3个字符的编辑文本框将不具有
    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;
    }
    
    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);
        }
    }
    
    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)
        }
    }
    
    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);
                }
    } 
    
    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));
            }
        }
    }
    
    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));
    
    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*/
        }
    }
    
    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; }
    }
    
    <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>
    
        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);
    
    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" } }));
    
    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;
            }
        }
    }
    
    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();
        }
    }
    
        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