Здесь уже был вопрос про закраску текста и на него был дан ответ
Вкратце суть решения состоит в создании FormattedText из содержимого скрытого TextBlock, а затем создается геометрия из FormattedText, которая отображается с помощью Path, который в свою очередь закрашивается с помощью анимации на нем.
И все прекрасно работает пока текст занимает одну строку, а вот когда строк несколько, получается не то, что хотелось бы.
XAML я повторять не буду, т.к. он полностью совпадает с таковым в выше указанном ответе. Мой код тоже мало чем отличается, но я его приведу для ясности вопроса.
private List
//анимация
Storyboard _Storyboard;
DoubleAnimation _FromAnimation;
DoubleAnimation _ToAnimation;
private void CreateFillText()
{
//получаем текстовое содержимое
TextBlock tb = this.textBlockHidden;
var text = tb.Text;
//создаем экземпляр форматированного текста
FormattedText formattedText = new FormattedText(
text,
CultureInfo.GetCultureInfo("en-US"),
FlowDirection.LeftToRight,
new Typeface(
tb.FontFamily,
tb.FontStyle,
tb.FontWeight,
tb.FontStretch),
tb.FontSize,
Brushes.Black // конкретная кисть нам не важна, мы используем только геометрию
);
//берем переносы строк у эталонного текстблока
formattedText.MaxTextWidth = this.textBlockHidden.Width;
formattedText.MaxTextHeight = this.textBlockHidden.Height;
// стащили геометрию у текста...
var geo = formattedText.BuildGeometry(new Point());
// ...и отдали её Path'у
Target.Data = geo;
//вычислим прямоугольники для заполнения
GetRectsForFill(text, formattedText);
}
private void GetRectsForFill(string text, FormattedText formattedText)
{
var bb = formattedText.BuildHighlightGeometry(new Point());
_LengthFillText = bb.Bounds.Width; // общая ширина
//заполняем коллекцию побуквенных боксов
_RectsForFill = Enumerable.Range(0, text.Length)
.Select(k => formattedText.BuildHighlightGeometry(new Point(), k, 1)
.Bounds)
.ToList();
//ссылки на анимацию для дальнейшей работы с ней
_Storyboard = (Storyboard)Target.Resources["AnimationStoryboard"];
_FromAnimation = (DoubleAnimation)_Storyboard.Children[0];
_ToAnimation = (DoubleAnimation)_Storyboard.Children[1];
}
Вот метод, заполняет черным отрисованный текст
///
//вычисляем индекс необходимого прямоугольника
int index = startPos + count;
if (index >= _RectsForFill.Count) index = _RectsForFill.Count - 1;
//необходимый прямоугольник
Rect box = _RectsForFill[index];
//закрашиваем
_FromAnimation.From = box.Left / _LengthFillText;
_FromAnimation.To = box.Right / _LengthFillText;
_ToAnimation.From = box.Left / _LengthFillText;
_ToAnimation.To = box.Right / _LengthFillText;
_Storyboard.Begin();
}
Ответ
Вы пошли правильным путём, разбить на отдельные Path'ы — хорошая идея.
Давайте её реализуем до конца.
Для начала, функциональность проигрывания уже достаточно сложна, так что вынесем её в отдельный UserControl. Затем, каждый контрол пусть отвечает за одну строку. Чтобы не искать куски по геометрии, просто отрежем эту строку при помощи Clip'а. На вход в UserControl будем подавать результаты разбора текста на геометрию (функцией Create).
В code-behind будет анимация:
public partial class SingleLine : UserControl
{
List
public SingleLine()
{
InitializeComponent();
}
public SingleLine(Geometry geo, List
public async Task Play()
{
var storyboard = (Storyboard)Target.Resources["AnimationStoryboard"];
var fromAnimation = (DoubleAnimation)storyboard.Children[0];
var toAnimation = (DoubleAnimation)storyboard.Children[1];
foreach (var b in boundingBoxes)
{
await Task.Delay(250); // перерыв между буквами
fromAnimation.From = b.Left / extent;
fromAnimation.To = b.Right / extent;
toAnimation.From = b.Left / extent;
toAnimation.To = b.Right / extent;
storyboard.Begin();
await Task.Delay(250); // дождёмся конца анимации
}
}
}
Зачем нам нужна такая сложность с Clip и totalExtent? К сожалению, я не нашёл метода выкусить только нужную часть геометрии. Поэтому мы даём на вход всю геометрию, а хотим показывать только текущую строку. Для этого мы вычисляем прямоугольник, соответствующий нужной части геометрии (текущей строке), и отрезаем отображение остального при помощи Clip'а. Но наши вычисления коэффициентов (b.Left / extent и т. д.) требуют процента от общей ширины Path'а, а не ширины текущей строки! (Напомню, наш Path получает геометрию всей строки, включая остальные строки тоже.) Поэтому приходится передавать ещё и общую ширину.
Теперь основной код. Он стал проще, так как часть функциональности отделилась. В нём мы не можем положить один, фиксированный Path, так как у нас количество строк не известно заранее. Поэтому контролы будем добавлять динамически.
Главное окно выглядит просто:
И code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += (o, args) => Create(); // вначале запустим Create
PreviewKeyDown += (o, args) => Play(); // а по нажатию клавиши - Play
}
// список контролов, отображающих строки
List
void Create() // https://msdn.microsoft.com/en-us/library/ms745816(v=vs.110).aspx
{
TextBlock tb = Source;
var text = tb.Text;
FormattedText formattedText = new FormattedText(
text,
CultureInfo.GetCultureInfo("en-US"),
FlowDirection.LeftToRight,
new Typeface(
tb.FontFamily,
tb.FontStyle,
tb.FontWeight,
tb.FontStretch),
tb.FontSize,
Brushes.Black);
// установили максимальную ширину, чтобы текст был разбит на части
formattedText.MaxTextWidth = Source.ActualWidth;
var boundingBoxes = // побуквенная ширина и позиции
Enumerable.Range(0, text.Length)
.Where(k => !char.IsWhiteSpace(text[k]))
.Select(k => formattedText.BuildHighlightGeometry(new Point(), k, 1)
.Bounds)
.ToList();
// вычисляем охватывающий прямоугольник всех прямоугольников
var totalBb = boundingBoxes.Aggregate(Rect.Union);
var totalExtent = totalBb.Width;
List> boundingBoxesByLine = new List
>();
List
// стащили геометрию у текста...
var geo = formattedText.BuildGeometry(new Point());
// строим по контролу для каждой строки:
foreach (var line in boundingBoxesByLine)
{
// ... отдавая ему геометрию:
var lineControl = new SingleLine(geo, line, totalExtent);
Container.Children.Add(lineControl);
lineControls.Add(lineControl);
}
}
async void Play()
{
// проигрываем просто построчно
foreach (var line in lineControls)
await line.Play();
}
}
Всё!
Результат:
Комментариев нет:
Отправить комментарий