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

介绍 (Introduction)

As described by MSDN, DrawingVisual is a lightweight drawing class that is used to render shapes, images, or text. This class is considered lightweight because it does not provide layout, input, focus, or event handling, which improves its performance.

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

使用WPF的C#中的矩阵样式雨_第1张图片

背景 (Background)

Before I started coding, I consulted MSDN page to understand the basic of DrawingVisual Objects and WPF Graphics Rendering Overview.

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

Many of the elements/controls that we commonly use in WPF like Button, ComboBox, Shape, and others have these characteristics:

我们通常在WPF中使用的许多元素/控件(如ButtonComboBoxShape )以及其他具有以下特征的控件:

  • Can be composed by multiple elements, each of the composing elements provide focus method, event handling and many features which allow us to have a lot of freedom of programming but with a lot of "overhead" if we just need to perform some drawing.

    可以由多个元素组成,每个组成元素提供焦点方法,事件处理和许多功能,这些功能使我们可以自由编程,但如果需要执行一些绘制,则有很多“开销”。
  • Extend common objects which are not optimized for a specific purpose but for generic service.

    扩展不是针对特定目的而是针对通用服务而优化的公共对象。

The scope of DrawingVisual is to propose a lightweight approach to object drawing.

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

Regarding the matrix rain effect, I take some ideas on how to develop it from CodePen, which is an online community for testing and showcasing user-created HTML, CSS and JavaScript code snippets.

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

I assume the reader knows WPF dispatcher. Briefly, when you execute a WPF application, it automatically creates a new Dispatcher object and calls its Run method. All the visual elements will be created by the dispatcher thread and all the modification to visual elements must be executed on Dispatcher thread.

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

使用代码 (Using the Code)

My sample is made up of two projects:

我的样本由两个项目组成:

1. MatrixRain (1. MatrixRain)

This is the core of the solution. This project implements a UserControl that simulates the Matrix digital rain effect. The UserControl can be used in any Window/Page, etc.

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

  1. Set up parameter.

    设置参数。

    The SetParameter method allows to set up some animation parameter:

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

    • framePerSecond: Frame per second refresh (this parameter affect the "speed" of the rain)

      framePerSecond :每秒刷新的帧(此参数影响下雨的“速度”)

    • fontFamily: Font family used

      fontFamily :使用的字体家族

    • fontSize: Dimension of the font used

      fontSize :使用的字体的尺寸

    • backgroundBrush: Brush used for the background

      backgroundBrush :用于backgroundBrush画笔

    • textBrush: Brush used for the text

      textBrush :用于文本的笔刷

    • characterToDisplay: The character used for the rain will be randomly chosen from this string

      characterToDisplay :用于下雨的字符将从此string随机选择

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

    开始动画。

    The Start and Stop methods allow to start and stop the animation:

    StartStop方法允许开始和停止动画:

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

    The animation is controlled through System.Timers.Timer. I prefer this solution over System.Windows.Threading.DispatcherTimer because the DispatcherTimer is re-evaluated at the top of every Dispatcher loop and the timer is not guaranteed to execute exactly when the time interval occurs.

    动画是通过System.Timers.Timer控制的。 我喜欢此溶液System.Windows.Threading.DispatcherTimer因为DispatcherTimer是重新评估在每个分派器循环的顶部和计时器不能保证当的时间间隔发生准确地执行。

    Every tick, the method _DispatcherTimerTick(object sender, EventArgs e) is called. This method is not executed on the Dispatcher thread so the first thing is to sync the call on the Dispatcher thread because we need to work with some resources accessible only by the main thread.

    每次滴答时,都会_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. Draw the new frame.

    绘制新框架。

    Once the call from the timer is on the dispatcher thread, it performs two operations:

    一旦计时器调用在调度程序线程上,它将执行两个操作:

    1. Design the new frame

      设计新框架

      The frame is created by the method _RenderDrops(). Here is a new DrawingVisual and its DrawingContext are created to draw objects. The drawing context allows drawing line, ellipse, geometry, images and many more.

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

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

      First, the method creates a black background with a 10% of opacity (I will explain later why I put 10% opacity).

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

      After this, we scroll through an array called _Drops.

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

      使用WPF的C#中的矩阵样式雨_第2张图片

      This array represents the column along which the letters are drawn (see the red column in the image). The value of the array represents the row (see the blue circle in the image) where a new letter must be drawn. When the value of the drop reaches the 'bottom' of the image, the drop re-starts from the top immediately or randomly after a series of cycle.

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

      ...
      //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);
      }
      ...
      
      		

      To recap the method, _RenderDrops() generates DrawingVisual that contains a background with opacity and the new drops letters. For example:

      回顾一下该方法, _RenderDrops()生成DrawingVisual包含与不透明度和新的滴字母的背景。 例如:

      使用WPF的C#中的矩阵样式雨_第3张图片 Frame1
      使用WPF的C#中的矩阵样式雨_第4张图片 Frame 3
      使用WPF的C#中的矩阵样式雨_第5张图片 框架1
      使用WPF的C#中的矩阵样式雨_第6张图片 框架3
    2. Copy the new frame over the previous one

      将新框架复制到上一个框架

      As seen before, the new frame only generates the "new" letter, but how can we fade away the previous letters?

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

      This is performed by the background of the frame which is black with 10% opacity. When we copy a new frame over the previous frame, the blending makes the trick. The "copy over" weakens the previous letters luminance as shown in this example:

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

      使用WPF的C#中的矩阵样式雨_第7张图片 Final Frame1 = Black background + Frame1
      使用WPF的C#中的矩阵样式雨_第8张图片 Final Frame 3 = Final Frame2 + Frame3
      使用WPF的C#中的矩阵样式雨_第9张图片 最终帧1 =黑色背景+帧1
      使用WPF的C#中的矩阵样式雨_第10张图片 最终框架3 =最终框架2 +框架3

      P.S.: I render the Drawing Visual on a RenderTargetBitmap. I could apply this directly on my image:

      PS :我在RenderTargetBitmap上绘制图形的视觉效果。 我可以直接在图片上应用:

      _MyImage.Source = _RenderTargetBitmap
      
      		

      The problem with this solution is that at every cycle, this operation allocates a lot of memory at every cycle. To overlap this problem, I use WriteableBitmap which is allocated in memory only once in the initialization code.

      该解决方案的问题在于,在每个周期,该操作在每个周期分配大量内存。 为了解决这个问题,我使用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 (MatrixRainWpfApp)

This project references MatrixRain and showcases the potentiality of MatrixRain user control. The code is not commented, because it is so simple that does not need to be.

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

  1. In the MainWindow.xaml, a MatrixRain control is added to the window:

    MainWindow.xaml中 ,将MatrixRain控件添加到窗口中:

    ...
    xmlns:MatrixRain="clr-namespace:MatrixRain;assembly=MatrixRain"
    ...
    
    ...
    	
  2. During Initialization, I read a special font from the embedded resources and pass it to MatrixRain control:

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

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

    Please pay attention to the font. This is the link where I found it: https://www.1001fonts.com/matrix-code-nfi-font.html. This is free to use only for personal purposes.

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

  3. Two buttons: Start and Stop; command the animation:

    两个按钮:“ Start和“ Stop ; 命令动画:

    private void _StartButtonClick(object sender, RoutedEventArgs e)
    {
        mRain.Start();
    }
    
    private void _StopButtonClick(object sender, RoutedEventArgs e)
    {
        mRain.Stop();
    }
    	
  4. Two buttons: Set1 and Set2; command the text color:

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

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

兴趣点 (Points of Interest)

Quote:
引用:

This is your last chance. After this, there is no turning back. You take the blue pill - the story ends, you wake up in your bed and believe whatever you want to believe. You take the red pill - you stay in Wonderland, and I show you how deep the rabbit hole goes. Remember: all I'm offering is the truth. Nothing more.

这是您最后的机会。 此后,就没有回头路了。 您服用蓝色药丸-故事结束了,您在床上醒来,相信了您想相信的一切。 您服用红色药丸-您留在仙境,我告诉您兔子洞有多深。 记住:我所提供的只是事实。 而已。

The red pill is the DrawingVisual, so make your choice.

红色药丸是DrawingVisual ,因此请选择。

P.S.: To generate the letter random, I used a personal "CryptoRandom" class (source included) instead of the canonical Random method. This because Random method generates a 'pseudo random' number. Follow this link if you want to dig in.

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

If you have any suggestions for modifications, please don't hesitate to contact me.

如果您有任何修改建议,请随时与我联系。

翻译自: https://www.codeproject.com/Articles/5164199/Matrix-Style-Rain-in-Csharp-with-WPF

你可能感兴趣的:(java,python,javascript,css,js,ViewUI)