使用WPF的C#中的矩阵样式雨

目录

介绍

背景

使用代码

1. MatrixRain

1、设定参数

2、开始动画

3、绘制新框架

MatrixRainWpfApp


介绍

本文适用于那些想了解在WPFDrawingVisual 是如何工作的人。

MSDN中所述,DrawingVisual 是一种轻量级的绘图类,用于呈现形状、图像或文本。此类被认为是轻量级的,因为它不提供布局、输入、焦点或事件处理,从而提高了其性能。

背景

在开始编码之前,我查阅了MSDN页面,以了解DrawingVisual ObjectsWPF Graphics Rendering Overview的基础

我们在WPF中通常使用许多元素/控制,如ButtonComboBoxShape,和其他等,具有以下特征:

  • 可以由多个元素组成,每个组成元素提供focus方法,事件处理和许多特征,这些特征使我们可以自由编程,但如果需要执行一些绘图,则有很多开销
  • 扩展不是针对特定目的而是针对通用服务进行了优化的通用对象。

DrawingVisual的范围是提出一种轻量级的对象绘制方法。

关于矩阵雨效应,我对如何从CodePen进行开发提出了一些想法,CodePen是一个在线社区,用于测试和展示用户创建的HTMLCSSJavaScript代码段。

我假设读者知道WPF调度程序。简要地说,当您执行WPF应用程序时,它会自动创建一个新Dispatcher对象并调用其Run方法。所有视觉元素都将由调度程序线程创建,并且所有对视觉元素所做的修改都必须在Dispatcher线程上执行。

使用代码

我的示例由两个项目组成:

1. MatrixRain

这是解决方案的核心。该项目实现了一个模拟Matrix数字雨效果的UserControlUserControl可以在任何窗口/页等中使用

1、设定参数

SetParameter方法允许设置一些动画参数:

...
public void SetParameter(int framePerSecond = 0, FontFamily fontFamily = null,
                         int fontSize = 0, Brush backgroundBrush = null, 
                         Brush textBrush = null, String characterToDisplay = "")
...

 

  • framePerSecond:每秒刷新一次(此参数影响下雨的速度
  • fontFamily:使用的字体系列
  • fontSize:使用的字体尺寸
  • backgroundBrush:用于背景的画笔
  • textBrush:用于文本的笔刷
  • characterToDisplay:从string中随机选择用于下雨的字符 

2、开始动画

StartStop方法允许启动和停止动画:

public void Start() {
    _DispatcherTimer.Start();
}
public void Stop() {
    _DispatcherTimer.Stop();
}
...

动画是通过System.Timers.Timer控制的。与System.Windows.Threading.DispatcherTimer相比,我更喜欢此解决方案,因为在每个Dispatcher循环的顶部都会重新评估DispatcherTimer,并且不能保证计时器在发生时间间隔时准确执行。

每次滴答时,都会调用_DispatcherTimerTick(object sender, EventArgs e)方法。此方法不在Dispatcher线程上执行,因此第一件事是同步Dispatcher线程上的调用,因为我们需要使用只能由主线程访问的某些资源。

...

private void _DispatcherTimerTick(object sender, EventArgs e)
{
    if (!Dispatcher.CheckAccess()) {
        //synchronize on main thread
        System.Timers.ElapsedEventHandler dt = _DispatcherTimerTick;
        Dispatcher.Invoke(dt,sender,e);
        return;
    }
    ....
}

3、绘制新框架

一旦来自计时器的调用位于调度程序线程上,它将执行两个操作:

  • 1、设计新框架

框架是通过_RenderDrops()方法创建的。这是一个新对象DrawingVisual,它的DrawingContext被创建来绘制对象。绘图上下文允许绘制线条、椭圆、几何形状、图像等等。

DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();

首先,该方法会创建不透明度为10%的黑色背景(我将在后面解释为什么我将不透明度设为10%)。

之后,我们滚动浏览一个名为_Drops的数组。

该数组代表绘制字母所沿的列(请参见图像中的红色列)。数组的值表示必须绘制新字母的行(请参见图像中的蓝色圆圈)。当墨滴的值达到图像的底部时,墨滴会立即或在一系列循环后随机从顶部重新开始。

...
//looping over drops
for (var i = 0; i < _Drops.Length; i++) {
    // new drop position
    double x = _BaselineOrigin.X + _LetterAdvanceWidth * i;
    double y = _BaselineOrigin.Y + _LetterAdvanceHeight * _Drops[i];

    // check if new letter does not goes outside the image
    if (y + _LetterAdvanceHeight < _CanvasRect.Height) {
        // add new letter to the drawing
        var glyphIndex = _GlyphTypeface.CharacterToGlyphMap[_AvaiableLetterChars[
                         _CryptoRandom.Next(0, _AvaiableLetterChars.Length - 1)]];
        glyphIndices.Add(glyphIndex);
        advancedWidths.Add(0);
        glyphOffsets.Add(new Point(x, -y));
    }

    //sending the drop back to the top randomly after it has crossed the image
    //adding a randomness to the reset to make the drops scattered on the Y axis
    if (_Drops[i] * _LetterAdvanceHeight > _CanvasRect.Height && 
                                           _CryptoRandom.NextDouble() > 0.775) {
        _Drops[i] = 0;
    }
    //incrementing Y coordinate
    _Drops[i]++;
}
// add glyph on drawing context
if (glyphIndices.Count > 0) {
    GlyphRun glyphRun = new GlyphRun(_GlyphTypeface,0,false,_RenderingEmSize,
                        glyphIndices,_BaselineOrigin,advancedWidths,glyphOffsets,
                        null,null,null,null,null);
    drawingContext.DrawGlyphRun(_TextBrush, glyphRun);
}
...

总结一下方法,_RenderDrops()生成包含不透明背景和新的下落字母的DrawingVisual例如:


1


2


3


4

  • 2、将新框架复制到上一帧(frame)

如前所述,新帧仅生成字母,但是我们如何淡出以前的字母呢?

这是由具有10%不透明度的黑色框架背景执行的。当我们在前一帧上复制新帧时,融合就可以了。如以下示例所示,“copy over”削弱了先前字母的亮度:


最终帧1 =黑色背景+1


最终帧2 =最终帧1 +2


最终帧3 =最终帧2 +3


最终帧4 =最终帧3 +4

PS:我在RenderTargetBitmap上以可视方式绘图。我可以将其直接应用到我的图像上:

_MyImage.Source = _RenderTargetBitmap

该解决方案的问题在于,在每个周期,此操作在每个周期分配大量内存。为了解决这个问题,我使用WriteableBitmap,它在初始化代码中仅在内存中分配一次。

...
_WriteableBitmap.Lock();
_RenderTargetBitmap.CopyPixels(new Int32Rect(0, 0, _RenderTargetBitmap.PixelWidth,
                                            _RenderTargetBitmap.PixelHeight), 
                               _WriteableBitmap.BackBuffer, 
                               _WriteableBitmap.BackBufferStride * 
                               _WriteableBitmap.PixelHeight,
                               _WriteableBitmap.BackBufferStride);
_WriteableBitmap.AddDirtyRect(new Int32Rect(0, 0, _RenderTargetBitmap.PixelWidth, 
                                            _RenderTargetBitmap.PixelHeight));
_WriteableBitmap.Unlock();
...

MatrixRainWpfApp

该项目参考MatrixRain并展示了MatrixRain用户控件的潜力。该代码未注释,因为它非常简单,不需要注释。

1、在MainWindow.xaml,将一个MatrixRain控件添加到窗口:

...
xmlns:MatrixRain="clr-namespace:MatrixRain;assembly=MatrixRain"
...
<MatrixRain:MatrixRain x:Name="mRain" HorizontalAlignment="Left" Height="524" 

                       Margin="10,35,0,0" VerticalAlignment="Top" Width="1172"/>
...

2、在初始化期间,我从嵌入式资源中读取了一种特殊字体,并将其传递给MatrixRain控件:

FontFamily rfam = new FontFamily(new Uri("pack://application:,,,"), 
                                 "./font/#Matrix Code NFI");
mRain.SetParameter(fontFamily: rfam);

请注意字体。这是我找到它的链接:https://www.1001fonts.com/matrix-code-nfi-font.html这是免费的,仅供个人使用。

3、两个按钮:StartStop命令动画:

private void _StartButtonClick(object sender, RoutedEventArgs e)
{
    mRain.Start();
}

private void _StopButtonClick(object sender, RoutedEventArgs e)
{
    mRain.Stop();
}

4、两个按钮:Set1Set2命令文本颜色:

private void _ChangeColorButtonClick(object sender, RoutedEventArgs e)
{
    mRain.SetParameter(textBrush: ((Button)sender).Background);
}

PS:为了随机生成字母,我使用了个人的CryptoRandom类(包括源代码)而不是规范的Random方法。这是因为Random方法会生成伪随机数。如果您想深入了解,请点击链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值