我会在Windows窗体上的标签中输入许多字符串(我不会经常使用这些字符串)。字符串将类似于以下内容:
"快速的棕色狐狸在l__y猎犬身上跑了#34;
我想在标签中显示字符串,但是将TextBox正好覆盖缺少字母的位置。
有300多个字符串,我正在寻找最简单,最优雅的方式。
如何为每个字符串准确地重新定位文本框?
编辑:由于需要多线支持,MaskTextBox无法正常工作。
答案 0 :(得分:16)
一种选择是使用蒙面文本框。
在您的示例中,您将掩码设置为:
"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)输入间隙。 并且可以通过代码轻松更改掩码。
编辑: 为方便起见......
以下是屏蔽字符的列表和说明
(取自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.
所有其他角色 - 文字。所有非掩码元素都将在MaskedTextBox中显示为自身。文字在运行时始终占据掩码中的静态位置,并且不能被用户移动或删除。
答案 1 :(得分:5)
确定点击了哪个角色,如果是下划线,则左下方的下划线放大,并在下划线顶部显示一个文本框。
您可以调整此代码,该标签实际上是一个只读文本框,可以访问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();
}
}
}
禁用选中的文本框(假标签):https://stackoverflow.com/a/42391380/495455
编辑:
我让它适用于多行文本框:
答案 2 :(得分:4)
这可能有点过分取决于你想要的复杂程度,但winforms网页浏览器控件(在Winforms应用程序中运行的MSIE本身)可以作为编辑器,你可以控制哪些部分是可编辑的。
使用标记为可编辑的部分加载您的内容,例如:
<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>
另一种选择,在内存/资源方面更多的代码工作,但在内存/资源方面更轻量级,将使用FlowLayoutPanel,将常规面板添加到FlowLayoutPanel,然后在这些面板上放置标签或文本框取决于面板是代表固定或可编辑的部分,并调整它们以匹配内容的长度。您可以使用MeasureString查找每个标签/文本框中内容的宽度,以便调整大小。
答案 3 :(得分:4)
为了满足此要求,IMO最好使用Windows窗体的这些功能,这些功能允许与HTML
或WPF
以及托管WebBrowser
控件或WPF ElementHost
的互操作性向用户显示内容。
在阅读本答案之前,请考虑:
____
字段。如果他们能够清除它们,一旦它们移动到另一个空白处,它们就会失去找到清除区域的能力。____
字段之间移动。 使用Html作为C#模型的视图并在WebBrowser控件中显示
在这里,我将基于在WebBrowser
控件中显示HTML来分享一个简单的答案。
作为选项,您可以使用WebBrowser
控件并使用模式类创建合适的html以在WebBrowser
控件中显示。
主要思想是根据测验模型创建一个html输出(包括原始文本和空白的ragnes),并使用html渲染模型并在WebBrowser
控件中显示它。
例如使用以下模型:
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));
它将呈现此输出:
然后在用户输入值后,它将以这种方式显示:
最后,当您点击Show Result
按钮时,它会以绿色显示正确答案,并以红色显示错误答案:
<强>代码强>
您可以下载完整的工作源代码,例如:
实施很简单:
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*/
}
}
以下是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; }
}
以下是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>
答案 4 :(得分:3)
另一个测验解决方案,使用从TextBox派生的类作为缺失字母的编辑器。
此代码的作用:
1)采用Label控件的文本,字符串(单词)列表和那些单词的子串,用作掩码隐藏单词中的一些字母
2)使用两个不同大小的Unicode空格字符(U+2007
和U+2002
)创建子字符串的掩码,以匹配要替换的字母大小 3)使用计算出的Editor
Textbox
,一个继承自Width
的类对象)的大小>和Height
(以像素为单位)子字符串。将TextBox.MaxLength
属性设置为子字符串的长度 4)计算多行标签文本中子字符串的位置,检查重复的模式,并覆盖Texbox对象(Editor
类)
此方法支持:
比例字体。仅支持Unicode字体
标签&#39;文字可以占用多行。
由于掩码字符,我使用固定大小的字体(Lucida Console)。
为了处理比例字体,使用两个不同的掩码字符,具体取决于字符宽度
(即不同宽度的不同掩码字符以匹配替换字符宽度)。
结果的直观表示:
TAB键用于从TextBox控件传递到下一个/上一个
ENTER键用于接受编辑。然后代码检查它是否匹配
ESC键重置文本并显示初始掩码。
初始化单词列表,指定一个完整的单词和一些用掩码替换的连续字符:=> jumped : umpe
和相关的标签控制
初始化Quiz
类时,它会自动使用TextBox掩码替换指定Label文本中的所有单词。
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" } }));
这是测验课:
它的工作是收集用于每个Label的所有编辑器(TextBoxes)并计算它们的位置,给定它们必须在每个Label文本中替换的字符串的位置。
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;
}
}
}
这是Editor类(继承自TextBox):
它构建并计算掩码字符的长度,并使用此值自动调整自身。
具有基本编辑功能。
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();
}
}
答案 5 :(得分:2)
考虑使用DataGridView和Masked Cell列的组合。
在编辑控件显示时,您将更改该特定行的掩码。
这里有一些代码用法示例,包括网格和每行的唯一屏蔽。
zip
网格列,源自此处:http://www.vb-tips.com/MaskedEditColumn.aspx
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
答案 6 :(得分:2)
这就是我接近它的方法。使用正则表达式拆分字符串,并为每个子字符串创建单独的标签。将所有标签放在FlowLayoutPanel中。单击标签后,将其删除并在同一位置 添加编辑TextBox。当焦点丢失(或按下输入)时,移除TextBox并将标签放回;将标签的文本设置为TextBox的文本。
首先创建自定义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;
}
这个接受测验文本然后用正则表达式分割它并创建标签和文本框。如果您有兴趣知道如何让正则表达式返回匹配项而不仅仅是子字符串看起来here
然后为了处理编辑之间的转换,我创建了一个EditableLabel
类。看起来像这样
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);
}
}
您可以通过从设计器中拖放它来自定义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)
}
}
最终结果如下:
我认为这个解决方案很好:
它非常灵活,因为您可以对可编辑标签进行特殊处理。您可以像我在这里更改颜色,将上下文菜单与&#34; Clear&#34;,&#34; Evaluate&#34;,&#34; Show Answer&#34;等
它几乎多行。流程布局面板负责组件包装,如果测验字符串中有频繁的中断,它将工作。否则你将拥有一个非常大的标签,不适合面板。你可以使用一个技巧来规避它并使用\n
来打破长字符串。您可以在\n
中处理SetQuizText()
,但我会将其留给您:)请记住您不会处理它的ID,标签会执行并且赢得&#39;与FlowLayoutPanel很好地结合。
TextBoxes可以更好地适应。适合3个字符的编辑文本框与3个字符的标签不同。有了这个解决方案,你不必为此烦恼。一旦编辑的标签被文本框替换,下一个控件将向右移动以适合文本框。标签返回后,其他控件可以重新排列。
我不喜欢的是,所有这些都是有代价的:你必须手动对齐控件。这就是为什么你会看到一些神奇的数字(我不喜欢并努力避免它们)。文本框与标签的高度不同。这就是为什么我在顶部 3个像素填充所有标签的原因。由于某些原因我现在没有时间进行调查,MeasureString()
没有返回确切的宽度,它的宽度稍微宽一些。经过反复试验,我意识到删除4个像素会更好地对齐标签
现在你说会有300个字符串所以我猜你的意思是300&#34; quizes&#34;。如果它们和快速棕色狐狸一样小,我认为在我的解决方案中处理多线的方式不会给你带来任何麻烦。但是如果文本更大,我建议你使用其他一个与多行文本框一起使用的答案。
请注意,如果这种情况变得更加复杂,例如文本指示正确或错误,或者您希望控件响应来调整大小,那么您将需要框架未提供的文本控件。不幸的是,Windows表单库已经停滞了好几年了,而且很难找到像你这样的问题的优雅解决方案,至少没有商业控制。
希望它可以帮助您入门。
答案 7 :(得分:1)
我已经开发了一个更简单的解决方案来理解它可能会帮助你起步至少(我没有时间在同一个标签上玩多个输入,但我得到了它的工作正确的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);
}
}
编辑:忘记包括&#34;措施&#34;方法:
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));
}
}
}
结果:
然后,您应该能够将事件处理程序分配给各个文本框/命名它们,以便以后在用户与给定输入交互时更方便地访问。
答案 8 :(得分:1)
我会尝试这样的事情(肯定需要调整一些尺寸):
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);
答案 9 :(得分:1)
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
你不需要标签和文本框,你可以在任何字符串控件中显示它。只有你需要用户输入位置,字符串你想用符号作为占位符和输入字符,在文本框上的样本,在键输入时所以你不导入多个控件。对于长字符串复制,您可以随时为每个字符复制。