RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 632801
Accepted
Bulson
Bulson
Asked:2020-02-26 23:38:54 +0000 UTC2020-02-26 23:38:54 +0000 UTC 2020-02-26 23:38:54 +0000 UTC

如何实现多行文本的运行突出显示(选择)?

  • 772

已经有一个关于文本阴影的问题并且已经得到了 回答。

简而言之,解决方案的本质是FormattedText从内容中创建一个隐藏的TextBlock,然后从中创建一个几何体FormattedText,使用显示Path,然后使用动画在其上绘制。

当文本占一行时一切正常,但是当有几行时,结果就不是我们想要的了。

不正确的文本底纹示例

我不会重复 XAML,因为 它与上述答案中的完全相同。我的代码也没有太大区别,但为了清楚起见,我将给出它。

private List<Rect> _RectsForFill;   // прямоугольники с каждым символом
private double _LengthFillText;      // общая ширина рисованного текста

//анимация
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];
}

这是一种用黑色填充渲染文本的方法

/// <summary>
/// Закрашивание рисованного текста
/// </summary>
/// <param name="startPos">начальная позиция слова</param>
/// <param name="count">число закрашиваемых букв в слове</param>
public void FillTextPath(int startPos, int count)
{
    if (count == 0) throw new ArgumentException(nameof(count));

    //вычисляем индекс необходимого прямоугольника
    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();
} 
c#
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    VladD
    2020-02-27T06:48:02Z2020-02-27T06:48:02Z

    你走对了路,拆分成单独Path的 's 是个好主意。

    让我们执行到底。

    对于初学者来说,回放功能已经相当复杂,所以我们将把它移到一个单独的UserControl. 然后,让每个控件负责一行。Clip为了不按几何搜索碎片,我们简单地用'a.切断这条线。在输入端,UserControl我们将提交几何文本的解析结果(通过函数Create)。

    <UserControl x:Class="KaraokeText.SingleLine"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
        <Path Name="Target" Stroke="Black" StrokeThickness="0.5">
            <Path.Fill>
                <LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
                    <GradientStop Color="Black" Offset="0" x:Name="TargetFrom"/>
                    <GradientStop Color="White" Offset="0" x:Name="TargetTo" />
                </LinearGradientBrush>
            </Path.Fill>
            <Path.Resources>
                <Storyboard x:Key="AnimationStoryboard">
                    <DoubleAnimation Duration="00:00:00.25"
                                     Storyboard.TargetName="TargetFrom"
                                     Storyboard.TargetProperty="Offset">
                        <DoubleAnimation.EasingFunction>
                            <CubicEase EasingMode="EaseIn"/>
                        </DoubleAnimation.EasingFunction>
                    </DoubleAnimation>
                    <DoubleAnimation Duration="00:00:00.25"
                                     Storyboard.TargetName="TargetTo"
                                     Storyboard.TargetProperty="Offset">
                        <DoubleAnimation.EasingFunction>
                            <CubicEase EasingMode="EaseOut"/>
                        </DoubleAnimation.EasingFunction>
                    </DoubleAnimation>
                </Storyboard>
            </Path.Resources>
        </Path>
    </UserControl>
    

    代码隐藏中会有一个动画:

    public partial class SingleLine : UserControl
    {
        List<Rect> boundingBoxes;
        double extent;
    
        public SingleLine()
        {
            InitializeComponent();
        }
    
        public SingleLine(Geometry geo, List<Rect> boundingBoxes, double totalExtent) : this()
        {
            Target.Data = geo;
            extent = totalExtent;
            Rect clip = boundingBoxes.Aggregate(Rect.Union);
            Clip = new RectangleGeometry(clip);
            this.boundingBoxes = boundingBoxes;
        }
    
        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'a. 但是我们的比率计算(b.Left / extent等)需要占'a总宽度的百分比Path,而不是当前行的宽度!(回想一下,我们Path获取整条线的几何形状,也包括其余线。)因此,我们还必须传递总宽度。

    现在是主要代码。随着部分功能的分离,它变得更简单了。在其中,我们不能放一个 fixed Path,因为我们事先不知道行数。因此,我们将动态添加控件。

    主窗口看起来很简单:

    <Window x:Class="KaraokeText.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="Тест" Height="350" Width="525">
        <Grid HorizontalAlignment="Center" VerticalAlignment="Center" Name="Container">
            <TextBlock Name="Source" FontSize="24" Visibility="Hidden" TextWrapping="Wrap"
                       Text="А-а, в Африке реки вот такой ширины &#x000d;А-а, в Африке горы вот такой вышины"/>
        </Grid>
    </Window>
    

    和代码隐藏:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Loaded += (o, args) => Create(); // вначале запустим Create
            PreviewKeyDown += (o, args) => Play(); // а по нажатию клавиши - Play
        }
    
        // список контролов, отображающих строки
        List<SingleLine> lineControls = new List<SingleLine>();
    
        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<List<Rect>> boundingBoxesByLine = new List<List<Rect>>();
            List<Rect> currentLine = null;
            double lastRectBottom = double.NegativeInfinity;
            foreach (var rect in boundingBoxes)
            {
                // проверка на новую строку. если верх текущего прямоугольника там же,
                // где низ предыдущего прямоугольника, или ещё ниже - новая строка, иначе нет
                if (rect.Top >= lastRectBottom)
                {
                    // добавим старую строку в список строк
                    if (currentLine != null)
                        boundingBoxesByLine.Add(currentLine);
                    // новый пустой контейнер прямоугольников для новой строки
                    currentLine = new List<Rect>();
                }
                currentLine.Add(rect);
                lastRectBottom = rect.Bottom;
            }
            if (currentLine != null) // последнюю строку не теряем
                boundingBoxesByLine.Add(currentLine);
    
            // стащили геометрию у текста...
            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();
        }
    }
    

    一切!


    结果:

    名字叫娜塔莎的简单动画

    • 4

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    Python 3.6 - 安装 MySQL (Windows)

    • 1 个回答
  • Marko Smith

    C++ 编写程序“计算单个岛屿”。填充一个二维数组 12x12 0 和 1

    • 2 个回答
  • Marko Smith

    返回指针的函数

    • 1 个回答
  • Marko Smith

    我使用 django 管理面板添加图像,但它没有显示

    • 1 个回答
  • Marko Smith

    这些条目是什么意思,它们的完整等效项是什么样的

    • 2 个回答
  • Marko Smith

    浏览器仍然缓存文件数据

    • 1 个回答
  • Marko Smith

    在 Excel VBA 中激活工作表的问题

    • 3 个回答
  • Marko Smith

    为什么内置类型中包含复数而小数不包含?

    • 2 个回答
  • Marko Smith

    获得唯一途径

    • 3 个回答
  • Marko Smith

    告诉我一个像幻灯片一样创建滚动的库

    • 1 个回答
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Алексей Шиманский 如何以及通过什么方式来查找 Javascript 代码中的错误? 2020-08-03 00:21:37 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5