Sono stato incaricato di creare un RichTextBox
parzialmente modificabile. Ho visto suggerimenti in Xaml aggiungendo gli elementi TextBlock
per le sezioni ReadOnly
, tuttavia questo ha l'indesiderabile effetto visivo di non essere avvolto piacevolmente. (Dovrebbe apparire come un unico blocco di testo continuo.)Come impedire a RichTextBox Inline di essere eliminato in TextChanged?
ho patchato insieme un prototipo funzionante utilizzando alcuni reverse string formatting per limitare/consentire modifiche ed accoppiati che, con la creazione dinamica di inline Run
elements per scopi di visualizzazione. Utilizzando un dizionario per memorizzare i valori correnti delle sezioni di testo modificabili, aggiorno gli elementi Run
di conseguenza su qualsiasi evento trigger TextChanged
- con l'idea che se il testo di una sezione modificabile viene completamente eliminato, verrà sostituito nuovamente al suo valore predefinito.
Nella stringa: "Ciao NOME, benvenuto nel campo SPORT"., solo NAME e SPORT sono modificabili.
╔═══════╦════════╗ ╔═══════╦════════╗
Default values: ║ Key ║ Value ║ Edited values: ║ Key ║ Value ║
╠═══════╬════════╣ ╠═══════╬════════╣
║ NAME ║ NAME ║ ║ NAME ║ John ║
║ SPORT ║ SPORT ║ ║ SPORT ║ Tennis ║
╚═══════╩════════╝ ╚═══════╩════════╝
"Hi NAME, welcome to SPORT camp." "Hi John, welcome to Tennis camp."
Problema:
Cancellare l'intero valore di testo in un particolare periodo di rimuovere quella corsa (e la successiva corsa) dal RichTextBox Document
. Anche se li aggiungo tutti, non vengono più visualizzati correttamente sullo schermo. Ad esempio, utilizzando la stringa modificata dal setup sopra:
utente mette in evidenza il testo "John" e fa clic Elimina, invece di salvare il valore vuoto, deve essere sostituito con il testo predefinito di "NAME". Internamente questo succede Il dizionario ottiene il valore corretto, lo
Run.Text
ha il valore, loDocument
contiene tutti gli elementiRun
corretti. Ma lo schermo visualizza:- Previsto: "Ciao NOME, benvenuto nel campo di tennis".
- Effettivo: "Ciao campo NAMETennis."
Nota a margine: Questo comportamento perdita-di-Run-elemento può anche essere duplicati quando si incolla. Evidenziare "SPORT" e incollare "Tennis" e la sequenza contenente il campo "". è perso.
Domanda:
Come faccio a tenere ogni elemento Run
visibile anche attraverso azioni distruttive, una volta che sono stati sostituiti?
Codice:
ho cercato di smontare il codice a un esempio minimo, così ho rimosso:
- Ogni
DependencyProperty
ed associato vincolante nel xaml - Logic ricalcolo posizione di inserimento (scusa)
- Rifattorizzato i metodi di estensione della formattazione della stringa collegata dal primo collegamento a un singolo metodo conteneva all'interno della classe. (Nota: questo metodo funzionerà con semplici formati di stringa di esempio.È stato escluso il mio codice per una formattazione più solida. Quindi, attenersi all'esempio fornito per questi scopi di test.)
- Rende le sezioni modificabili chiaramente visibili, non importa la combinazione di colori .
Per eseguire il test, rilasciare la classe nella cartella delle risorse del progetto WPF, correggere lo spazio dei nomi e aggiungere il controllo a una vista.
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
namespace WPFTest.Resources
{
public class MyRichTextBox : RichTextBox
{
public MyRichTextBox()
{
this.TextChanged += MyRichTextBox_TextChanged;
this.Background = Brushes.LightGray;
this.Parameters = new Dictionary<string, string>();
this.Parameters.Add("NAME", "NAME");
this.Parameters.Add("SPORT", "SPORT");
this.Format = "Hi {0}, welcome to {1} camp.";
this.Text = string.Format(this.Format, this.Parameters.Values.ToArray<string>());
this.Runs = new List<Run>()
{
new Run() { Background = Brushes.LightGray, Tag = "Hi " },
new Run() { Background = Brushes.Black, Foreground = Brushes.White, Tag = "NAME" },
new Run() { Background = Brushes.LightGray, Tag = ", welcome to " },
new Run() { Background = Brushes.Black, Foreground = Brushes.White, Tag = "SPORT" },
new Run() { Background = Brushes.LightGray, Tag = " camp." },
};
this.UpdateRuns();
}
public Dictionary<string, string> Parameters { get; set; }
public List<Run> Runs { get; set; }
public string Text { get; set; }
public string Format { get; set; }
private void MyRichTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
string richText = new TextRange(this.Document.Blocks.FirstBlock.ContentStart, this.Document.Blocks.FirstBlock.ContentEnd).Text;
string[] oldValues = this.Parameters.Values.ToArray<string>();
string[] newValues = null;
bool extracted = this.TryParseExact(richText, this.Format, out newValues);
if (extracted)
{
var changed = newValues.Select((x, i) => new { NewVal = x, Index = i }).Where(x => x.NewVal != oldValues[x.Index]).FirstOrDefault();
string key = this.Parameters.Keys.ElementAt(changed.Index);
this.Parameters[key] = string.IsNullOrWhiteSpace(newValues[changed.Index]) ? key : newValues[changed.Index];
this.Text = richText;
}
else
{
e.Handled = true;
}
this.UpdateRuns();
}
private void UpdateRuns()
{
this.TextChanged -= this.MyRichTextBox_TextChanged;
foreach (Run run in this.Runs)
{
string value = run.Tag.ToString();
if (this.Parameters.ContainsKey(value))
{
run.Text = this.Parameters[value];
}
else
{
run.Text = value;
}
}
Paragraph p = this.Document.Blocks.FirstBlock as Paragraph;
p.Inlines.Clear();
p.Inlines.AddRange(this.Runs);
this.TextChanged += this.MyRichTextBox_TextChanged;
}
public bool TryParseExact(string data, string format, out string[] values)
{
int tokenCount = 0;
format = Regex.Escape(format).Replace("\\{", "{");
format = string.Format("^{0}$", format);
while (true)
{
string token = string.Format("{{{0}}}", tokenCount);
if (!format.Contains(token))
{
break;
}
format = format.Replace(token, string.Format("(?'group{0}'.*)", tokenCount++));
}
RegexOptions options = RegexOptions.None;
Match match = new Regex(format, options).Match(data);
if (tokenCount != (match.Groups.Count - 1))
{
values = new string[] { };
return false;
}
else
{
values = new string[tokenCount];
for (int index = 0; index < tokenCount; index++)
{
values[index] = match.Groups[string.Format("group{0}", index)].Value;
}
return true;
}
}
}
}
'RichTextBox' fornisce molto più di un semplice editor di testo tramite la classe' FlowDocument'. Questo lo rende un comodo controllo per la visualizzazione di testo formattato, immagini, tabelle e persino 'UIElements'. Tuttavia tutti questi vengono con il costo della complessità. Pertanto, trattare con 'RichTextBox' può essere piuttosto complicato. C'è qualche possibilità che tu possa passare a un controllo più conveniente come 'AvalonEdit' che è più adatto per l'elaborazione del testo? –
Da quello che posso vedere il problema è che 'TryParseExact (richText' non riesce. È ragionevole iniziare a risolverlo? –
@ qqww2 Non ho familiarità con' AvalonEdit' oi suoi requisiti di licenza, ma lo prenderò in considerazione – OhBeWise