WPF实现类似ChatGPT的逐字打印效果

wpf,实现,类似,chatgpt,逐字,打印,效果 · 浏览次数 : 510

小编点评

## Summary of the Text Char Animation in WPF This document describes two implementation methods for simulating the character animation in WPF, focusing on the method that generates content by adding key frames to a Storyboard. **Method 1: Key Frame Animation** * This method directly binds the Text property of the TextBlock to the Storyboard. * It creates a Storyboard and a StringAnimationUsingKeyFrames object. * The KeyFrames list contains discrete strings representing each character of the text. * The Storyboard is set as the target property of the TextBlock's Text property. * The animation duration and key frame positions are set based on the text length. * Each key frame adds a StringAnimationKeyFrame to the Storyboard, specifying the character value and key time. * The Storyboard then starts an animation that gradually changes the TextBlock's Text property over the specified duration. **Method 2: TextEffect Animation** * This method sets the TextBlock's font color to be transparent. * It uses the TextEffect object to control the animation of the text's position. * The PositionStart and PositionCount properties specify the starting position and length of each key frame animation. * The ColorAnimation property changes the font color from transparent to the target color (black in this case). * This approach allows for smooth animation without explicitly updating the Text property. **Key Differences:** * Method 1 offers better animation quality but requires additional properties to be set. * Method 2 is simpler to implement but may not produce the same animation quality as Method 1. **Choosing the Right Method:** * Use **Method 1** for maximum animation quality and control over key frame positions. * Use **Method 2** for a simpler implementation that requires minimal additional setup. **Additional Notes:** * The Tag property is used as a temporary storage for the displayed text, as the TextBlock's Text property is bound to it. * The animation can be triggered when the Tag property value is changed.

正文

背景

前一段时间ChatGPT类的应用十分火爆,这类应用在回答用户的问题时逐字打印输出,像极了真人打字回复消息。出于对这个效果的兴趣,决定用WPF模拟这个效果。

真实的ChatGPT逐字输出效果涉及其语言生成模型原理以及服务端与前端通信机制,本文不做过多阐述,重点是如何用WPF模拟这个效果。

技术要点与实现

对于这个逐字输出的效果,我想到了两种实现方法:

  • 方法一:根据字符串长度n,添加n个关键帧DiscreteStringKeyFrame,第一帧的Value为字符串的第一个字符,紧接着的关键帧都比上一帧的Value多一个字符,直到最后一帧的Value是完整的目标字符串。实现效果如下所示:
  • 方法二:首先把TextBlock的字体颜色设置为透明,然后通过TextEffectPositionStartPositionCount属性控制应用动画效果的子字符串的起始位置以及长度,同时使用ColorAnimation设置TextEffectForeground属性由透明变为目标颜色(假定是黑色)。实现效果如下所示:
    image

由于方案二的思路与WPF实现跳动的字符效果中的效果实现思路非常类似,具体实现不再详述。接下来我们看一下方案一通过关键帧动画拼接字符串的具体实现。

public class TypingCharAnimationBehavior : Behavior<TextBlock>
{
    private Storyboard _storyboard;

    protected override void OnAttached()
    {
        base.OnAttached();

        this.AssociatedObject.Loaded += AssociatedObject_Loaded; ;
        this.AssociatedObject.Unloaded += AssociatedObject_Unloaded;
        BindingOperations.SetBinding(this, TypingCharAnimationBehavior.InternalTextProperty, new Binding("Tag") { Source = this.AssociatedObject });
    }

    private void AssociatedObject_Unloaded(object sender, RoutedEventArgs e)
    {
        StopEffect();
    }

    private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
    {
        if (IsEnabled)
            BeginEffect(InternalText);
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        this.AssociatedObject.Loaded -= AssociatedObject_Loaded;
        this.AssociatedObject.Unloaded -= AssociatedObject_Unloaded;
        this.ClearValue(TypingCharAnimationBehavior.InternalTextProperty);

        if (_storyboard != null)
        {
            _storyboard.Remove(this.AssociatedObject);
            _storyboard.Children.Clear();
        }
    }

    private string InternalText
    {
        get { return (string)GetValue(InternalTextProperty); }
        set { SetValue(InternalTextProperty, value); }
    }

    private static readonly DependencyProperty InternalTextProperty =
    DependencyProperty.Register("InternalText", typeof(string), typeof(TypingCharAnimationBehavior),
    new PropertyMetadata(OnInternalTextChanged));

    private static void OnInternalTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var source = d as TypingCharAnimationBehavior;
        if (source._storyboard != null)
        {
            source._storyboard.Stop(source.AssociatedObject);
            source._storyboard.Children.Clear();
        }
        source.SetEffect(e.NewValue == null ? string.Empty : e.NewValue.ToString());
    }

    public bool IsEnabled
    {
        get { return (bool)GetValue(IsEnabledProperty); }
        set { SetValue(IsEnabledProperty, value); }
    }

    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.Register("IsEnabled", typeof(bool), typeof(TypingCharAnimationBehavior), new PropertyMetadata(true, (d, e) =>
        {
            bool b = (bool)e.NewValue;
            var source = d as TypingCharAnimationBehavior;
            source.SetEffect(source.InternalText);
        }));

    private void SetEffect(string text)
    {
        if (string.IsNullOrEmpty(text) || this.AssociatedObject.IsLoaded == false)
        {
            StopEffect();
            return;
        }

        BeginEffect(text);

    }

    private void StopEffect()
    {
        if (_storyboard != null)
        {
            _storyboard.Stop(this.AssociatedObject);
        }
    }

    private void BeginEffect(string text)
    {
        StopEffect();

        int textLength = text.Length;
        if (textLength < 1  || IsEnabled == false) return;

        if (_storyboard == null)
            _storyboard = new Storyboard();
        double duration = 0.15d;

        StringAnimationUsingKeyFrames frames = new StringAnimationUsingKeyFrames();

        Storyboard.SetTargetProperty(frames, new PropertyPath(TextBlock.TextProperty));

        frames.Duration = TimeSpan.FromSeconds(textLength * duration);

        for(int i=0;i<textLength;i++)
        {
            frames.KeyFrames.Add(new DiscreteStringKeyFrame()
            {
                Value = text.Substring(0,i+1),
                KeyTime = TimeSpan.FromSeconds(i * duration),
            });
        }

        _storyboard.Children.Add(frames);
        _storyboard.Begin(this.AssociatedObject, true);
    }
}

由于每一帧都在修改TextBlockText属性的值,如果TypingCharAnimationBehavior直接绑定TextBlockText属性,当Text属性的数据源发生变化时,无法判断是关键帧动画修改的,还是外部数据源变化导致Text的值被修改。因此这里用TextBlockTag属性暂存要显示的字符串内容。调用的时候只需要把需要显示的字符串变量绑定到Tag,并在TextBlock添加Behavior即可,代码如下:

<TextBlock x:Name="source"
            IsEnabled="True"
            Tag="{Binding TypingText, ElementName=self}"
            TextWrapping="Wrap">
    <i:Interaction.Behaviors>
        <local:TypingCharAnimationBehavior IsEnabled="True" />
    </i:Interaction.Behaviors>
</TextBlock>

小结

两种方案各有利弊:

  • 关键帧动画拼接字符串这个方法的优点是最大程度还原了逐字输出的过程,缺点是需要额外的属性来辅助,另外遇到英文单词换行时,会出现单词从上一行行尾跳到下一行行首的问题;
  • 通过TextEffect设置字体颜色这个方法则相反,不需要额外的属性辅助,并且不会出现单词在输入过程中从行尾跳到下一行行首的问题,开篇中两种实现方法效果图中能看出这一细微差异。但是一开始就把文字都渲染到界面上,只是通过透明的字体颜色骗过用户的眼睛,逐字改变字体颜色模拟逐字打印的效果。

与WPF实现类似ChatGPT的逐字打印效果相似的内容:

WPF实现类似ChatGPT的逐字打印效果

###背景 前一段时间ChatGPT类的应用十分火爆,这类应用在回答用户的问题时逐字打印输出,像极了真人打字回复消息。出于对这个效果的兴趣,决定用WPF模拟这个效果。 >真实的ChatGPT逐字输出效果涉及其语言生成模型原理以及服务端与前端通信机制,本文不做过多阐述,重点是如何用WPF模拟这个效果。

WPF实现Element UI风格的日期时间选择器

### 背景 业务开发过程中遇到一个日期范围选择的需求,和Element UI的DateTimePicker组件比较类似,由两个日历控件组成,联动选择起始时间和结束时间。 ### 问题 WPF中提供了一个`DatePicker`的控件,主要由`DatePickerTextBox`、`Button`和

WPF使用TextBlock实现查找结果高亮显示

在应用开发过程中,经常遇到这样的需求:通过关键字查找数据,把带有关键字的数据显示出来,同时在结果中高亮显示关键字。在web开发中,只需在关键字上加一层标签,然后设置标签样式就可以轻松实现。 在WPF中显示文本内容通常采用`TextBlock`控件,也可以采用类似的方式,通过内联流内容元素`Run`达

WPF实现跳动的字符效果

本文将介绍一个好玩但实际作用可能不太大的动画效果:跳动的字符。为了提高动画效果的可重用性以及调用的灵活性,通过Behavior实现跳动的字符动画。先看下效果: ![image](https://img2023.cnblogs.com/blog/3056716/202308/3056716-20230

循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(4) -- 实现DataGrid数据的导入和导出操作

在我们设计软件的很多地方,都看到需要对表格数据进行导入和导出的操作,主要是方便客户进行快速的数据处理和分享的功能,本篇随笔介绍基于WPF实现DataGrid数据的导入和导出操作。

【WPF】单例软件实现自重启

原文地址 https://www.cnblogs.com/younShieh/p/17749694.html ❤如果本文对你有所帮助,不妨点个关注和推荐呀,这是对笔者最大的支持~❤ 在WPF应用程序中,想要实现软件重启,可以再Start一次该软件的exe程序。 但是有些时候我们想要这个程序是唯一运行

如何让WPF中的ValidationRule实现参数绑定

###背景 应用开发过程中,常常会对用户输入内容进行验证,通常是基于类型、范围、格式或者特定的要求进行验证,以确保输入符合预期。例如邮箱输入框校验输入内容是否符合邮箱格式。在WPF中,数据模型允许将`ValidationRules`与`Binding`对象关联,可以通过继承`ValidationRu

[WPF]使用HLSL实现百叶窗动效

百叶窗动画是制作PPT时常用的动画之一,本文将通过实现百叶窗动画效果的例子介绍在WPF中如何使用ShaderEffect。ShaderEffect使用高级着色器语言(High Level Shading Language,HLSL)事先制作好并且已经编译过的效果。先看下百叶窗动画实现效果: ![im

WPF中以MVVM方式,实现RTSP视频播放

前言视频播放在上位机开发中经常会遇到,基本上是两种常见的解决方案 1.采用厂家提供的sdk和前端控件进行展示,常见的海康/大华都提供了相关sdk及文档 2.开启相机onvif协议,捅过rtsp视频流进行播放,前端可以采用web方式,或者wpf中的视频控件进行展示。 项目需求,决定了最终采用开启相机o

WPF使用Shape实现复杂线条动画

看到巧用 CSS/SVG 实现复杂线条光效动画的文章,便也想尝试用WPF的Shape配合动画实现同样的效果。ChokCoco大佬的文章中介绍了基于SVG的线条动画效果和通过角向渐变配合 MASK 实现渐变线条两种方式。WPF中的Shape与SVG非常相似,因此这种方式也很容易实现。但WPF中仅有的两