Comment puis-je (élégamment) transposer textbox sur l'étiquette à une partie spécifique de la chaîne?
je vais alimenter un certain nombre de chaînes dans des étiquettes sur un formulaire Windows (Je ne les utilise pas beaucoup). Les cordes vont être semblable à la suivante:
"The quick brown fox j___ed sur le l__y hound"
je veux afficher la chaîne dans une étiquette mais superposer une boîte de texte exactement où sont les lettres manquantes.
il y aura plus de 300 cordes, et je cherche la façon la plus simple, la plus élégante de le faire.
comment repositionner le zone de texte avec précision pour chaque chaîne?
EDIT: une MaskTextBox ne fonctionnera pas car j'ai besoin d'un support multiligne.
10 réponses
pour satisfaire à cette exigence, IMO il est préférable d'utiliser les caractéristiques des formes Windows qui permettent l'interopérabilité avec HTML
ou WPF
et d'Accueil WebBrowser
contrôle ou WPF ElementHost
pour montrer le contenu aux utilisateurs.
Avant de lire cette réponse, veuillez considérer:
- les utilisateurs ne devraient pas pouvoir effacer le
____
champs. S'ils peuvent les effacer, une fois qu'ils sont passés à un autre vide, ils perdront la capacité de trouver le champ nettoyé. - il vaut mieux permettre aux utilisateurs d'utiliser Onglet pour passer entre
____
champs. - Comme il est mentionné dans la question: une MaskTextBox ne fonctionnera pas car j'ai besoin d'un support multiligne.
- Comme il est mentionné dans la question: il y aura plus de 300 cordes donc mélanger beaucoup de Windows Forms control n'est pas une bonne idée.
utilisant Html comme vue d'un modèle C# et l'afficher dans le contrôle WebBrowser
Ici Je vais partager une réponse simple basé sur montrant HTML WebBrowser
de contrôle.
En option vous pouvez utiliser un WebBrowser
contrôler et créer du html approprié à afficher dans WebBrowser
contrôle à l'aide d'une classe de mode.
l'idée principale est de créer une sortie html basée sur le modèle quiz (y compris le texte original et les ragnes de blancs) et de rendre le modèle en utilisant html et en le montrant dans un WebBrowser
de contrôle.
Par exemple utiliser le modèle suivant:
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));
Il va rendre cette sortie:
puis après les valeurs saisies par l'utilisateur, il affichera de cette façon:
et enfin, quand vous cliquez sur Show Result
bouton, il affiche les réponses correctes dans la couleur verte, et de mauvaises réponses en couleur rouge:
Code
vous pouvez télécharger le code source complet par exemple ici:
La mise en œuvre est assez 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*/
}
}
voici le code complet du Quiz
catégorie:
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; }
}
Et voici le contenu du gabarit 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>
une option est d'utiliser une boîte de texte masquée.
Dans votre exemple, vous réglez le masque de:
"The quick brown fox jLLLed over the l\azy hound"
qui apparaîtrait comme:
"The quick brown fox j___ed over the lazy hound"
Et permettent seulement de 3 caractères (a-z et A-Z) à être entré dans l'espace. Et le masque peut être facilement changé via le code.
modifier: Pour plus de commodité...
Voici une liste et une description des caractères de masquage
(prise 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.
tous les autres personnages - littéraux. Tous les éléments non masqués apparaîtront comme eux-mêmes dans MaskedTextBox. Les lettres occupent toujours une position statique dans le masque au moment de l'exécution, et ne peuvent pas être déplacées ou supprimées par l'utilisateur.
déterminez sur quel caractère vous avez cliqué, si c'était un underscore, puis faites la taille des underscores gauche et droite et montrez une boîte de texte au dessus des underscores.
vous pouvez modifier ce code, le label est en fait une boîte de texte en lecture seule pour avoir accès au GetCharIndexFromPosition
et GetPositionFromCharIndex
méthodes.
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();
}
}
}
pour désactiver la boîte de texte (fausse étiquette) de la sélection: https://stackoverflow.com/a/42391380/495455
Edit:
j'ai fait le travail pour les zones de texte multiligne:
cela peut être exagéré en fonction de la complexité que vous voulez que ce soit, mais un contrôle de navigateur Web winforms (qui est essentiellement MSIE tournant à l'intérieur de votre application Winforms) peut fonctionner comme un éditeur où vous contrôlez quelles pièces sont modifiables.
Chargez votre contenu avec les parties modifiables étiquetées comme telles, par exemple:
<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>
une autre option, Un peu plus de travail à coder mais plus légère en termes de mémoire / ressources, serait d'utiliser un FlowLayoutPanel, ajouter des panneaux normaux au panneau FlowLayoutPanel, puis mettre des étiquettes ou des boîtes de texte sur ces panneaux selon si un panneau représente une partie fixe ou modifiable, et les redimensionner pour correspondre à la longueur du contenu. Vous pouvez utiliser MeasureString pour connaître la largeur du contenu dans chaque étiquette/boîte de texte à des fins de redimensionnement.
un autre, en utilisant une simple commande TextBox.
EDIT1: Ajout du support pour les polices proportionnelles. Seules les polices Unicode sont supportées.
EDIT2: a abandonné le paramètre de soulignement, en utilisant maintenant 2 caractères unicode et le soulignement de police. Ajout du support IDisposable.
EDIT3: ajout d'un support multiligne
Quel est ce code:
1) prend une liste de chaînes (mots) et sous-chaînes de ceux mots et le texte d'un contrôle D'étiquette
2) crée un masque des substrats en utilisant deux caractères D'Espace Unicode (U+2007 et U+2002) de taille différente, pour correspondre à la taille des lettres à remplacer
3) Taille vers le haut D'une zone de texte sans bordure (un objet de classe qui hérite de la zone de texte) en utilisant la largeur et la hauteur calculées (en pixels) du substrat. Définit sa propriété MaxLength à la longueur de la substring.
4) calcule la position des sous-couches à l'intérieur d'un texte D'étiquette multiligne, vérifie les motifs dupliqués, et superpose les objets Texbox (Classe Editor)
j'ai utilisé une police de taille fixe (Console Lucida) à cause du masque char.
Pour gérer les polices proportionnelles, deux caractères de masque différents sont utilisés, en fonction de la largeur des caractères
(c'est-à-dire différentes barres de masque de largeur différente pour correspondre à la largeur des caractères substitués).
Une représentation visuelle des résultats:
La touche TAB est utilisée pour passer de la commande TextBox à la commande next/previous.
La touche entrée est utilisée pour accepter l'édition. Alors le code vérifie s'il correspond.
La touche ESC réinitialise le texte et affiche le masque initial.
Une liste de mots est initialisé spécifier un mot complet et un nombre de caractères contigus à remplacer par un masque:=> jumped : umpe
et le contrôle Label.
Lorsqu'un Quiz
la classe est initialisée, elle substitue automatiquement tous les mots dans le texte D'étiquette spécifié avec un masque de TextBox.
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" } }));
C'est le Quiz de la classe:
Son travail est de collecter tous les éditeurs (boîtes de textes) qui sont utilisés pour chaque étiquette et de calculer leur emplacement, compte tenu de la position de la chaîne qu'ils doivent remplacer dans chaque texte D'étiquette.
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;
}
}
}
c'est la classe Editor (hérite de TextBox):
Il construit et calcule la longueur des barres de masque et se positionne lui-même en utilisant cette valeur.
possède des capacités d'édition de base.
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();
}
}
envisagez d'utiliser une combinaison de DataGridView et d'une colonne de cellules masquées.
sur L'affichage de contrôle D'édition, vous changeriez le masque de cette ligne particulière.
voici un exemple d'utilisation du code qui inclut la grille et le masquage unique pour chaque ligne.
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
et la colonne de grille, provenant d'ici: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
Voici comment je l'aborderais. Séparez avec l'expression régulière la chaîne et créez des étiquettes séparées pour chacune des sous-chaînes. Mettre toutes les étiquettes dans un FlowLayoutPanel. Lorsqu'une étiquette est cliqué, l'enlever et sur la même position ajouter la zone d'édition. Lorsque le focus est perdu (ou enter est pressé), supprimez la zone de texte et remettez l'étiquette en place; Réglez le texte de l'étiquette sur le texte de la zone de texte.
tout d'Abord créer custom UserControl
comme la suivant
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;
}
celui-ci accepte le texte du quiz puis le divise avec regex et crée les étiquettes et les zones de texte. Si vous êtes intéressé pour savoir comment faire le retour Regex les allumettes et non seulement les substrings ont un look ici
alors pour prendre soin de la transition entre l'édition, j'ai créé un EditableLabel
classe. Il ressemble à ceci
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);
}
}
vous pouvez utiliser le custom UserControl en le faisant glisser et le laisser tomber de la concepteur (après que vous générez avec succès) ou de l'ajouter comme ceci:
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)
}
}
Le résultat final ressemblera à ceci:
pour et contre
Ce que je considère bon avec cette solution:
c'est flexible puisque vous pouvez donner un traitement spécial à l'étiquette modifiable. Vous pouvez changer sa couleur Comme je l'ai fait ici, mettre un menu contextuel avec des actions comme "clair", "évaluer", " afficher la réponse" etc.
presque multiligne. Le panneau de configuration de flux prend soin de l'emballage du composant et il fonctionnera si il y a des pauses fréquentes dans la chaîne du quiz. Sinon, vous aurez une très grande étiquette qui ne tiendra pas dans le panneau. Vous pouvez cependant utiliser un truc pour contourner cela et utiliser
\n
pour briser de longues cordes. Vous pouvez gérer\n
dans leSetQuizText()
mais je vous laisse cela :) ayez à l'esprit que si vous ne vous en occupez pas le label fera l'affaire et cela ne se liera pas bien avec le FlowLayoutPanel.les boîtes à textes conviennent mieux. La zone d'édition de texte qui s'adaptera à 3 caractères n'aura pas la même chose que l'étiquette avec 3 caractères. Avec cette solution, vous n'avez pas à s'embêter avec ça. Une fois que l'étiquette éditée est remplacée par la zone de texte, les contrôles suivants se déplacent vers la droite pour s'adapter à la zone de texte. Une fois que l'étiquette revient, les autres contrôles peuvent réaligner.
mais ce que je n'aime pas, c'est que tout cela aura un prix: il faut aligner manuellement les commandes. C'est pourquoi vous voyez des nombres magiques (que je n'aime pas et j'essaie de les éviter). Zone de texte n'ont pas la même hauteur que l'étiquette. C'est pourquoi j'ai rembourré toutes les étiquettes 3 pixels en haut. Aussi pour une raison que je n'ai pas le temps d'étudier maintenant, MeasureString()
ne renvoie pas la largeur exacte, elle est légèrement plus large. Avec d'essai et d'erreur, j'ai réalisé que Supprimer 4 pixels permettra de mieux aligner les étiquettes
Maintenant vous dites qu'il y aura 300 cordes donc je suppose que vous voulez dire 300 "quizes". Si ceux-ci sont aussi petits que le renard brun rapide, je pense que la façon multiligne est manipulée dans ma solution ne vous causera aucun problème. Mais si le texte sera plus grand, je vous suggère d'aller avec l'un des autres réponses que travailler avec du texte multiligne boîtes.
ayez à l'esprit cependant que si cela se développe plus complexe, comme par exemple des indicateurs fantaisistes que le texte était bon ou mauvais ou si vous voulez que le contrôle soit responsive pour les changements de taille, alors vous aurez besoin de contrôles texte qui ne sont pas fournis par le framework. Windows forms library est malheureusement resté stagnant depuis plusieurs années maintenant, et des solutions élégantes dans des problèmes tels que le vôtre sont difficiles à trouver, au moins sans contrôles commerciaux.
J'espère que cela vous aidera à vous lancer.
j'ai trouvé une solution un peu plus facile à comprendre qui pourrait vous aider à démarrer à tout le moins (je n'ai pas eu le temps de jouer avec plusieurs entrées dans le même label, mais je l'ai fait fonctionner correctement pour 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);
}
}
EDIT: oublié D'inclure la méthode" Measure":
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));
}
}
}
Le résultat:
Alors vous devriez être en mesure d'attribuer des gestionnaires d'événements pour l'individu zones de texte/nom pour un accès plus facile plus tard, lorsque l'utilisateur interagit avec l'entrée donnée.
je voudrais essayer quelque chose comme ceci (vous aurez besoin de certaines tailles ajustements):
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);
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 N'a pas besoin D'étiquette et de zone de texte pour cela, u peut le faire dans n'importe quel affichage il dans n'importe quel contrôle de chaîne. Seul u a besoin de la position d'entrée de l'utilisateur, chaîne ce que u veut changer avec des symboles comme le support de place et le caractère d'entrée, est l'échantillon sur la boîte de texte, à l'entrée de clé afin u nombre de contrôles ne sont pas importés. Pour la copie longue chaîne u peut toujours pour chaque char.