目录
介绍
背景
使用代码
1. MatrixRain
1、设定参数
2、开始动画
3、绘制新框架
MatrixRainWpfApp
- 下载应用程序-40.1 KB
本文适用于那些想了解在WPF中DrawingVisual 是如何工作的人。
如MSDN中所述,DrawingVisual 是一种轻量级的绘图类,用于呈现形状、图像或文本。此类被认为是轻量级的,因为它不提供布局、输入、焦点或事件处理,从而提高了其性能。
在开始编码之前,我查阅了MSDN页面,以了解DrawingVisual Objects和WPF Graphics Rendering Overview的基础。
我们在WPF中通常使用许多元素/控制,如Button,ComboBox,Shape,和其他等,具有以下特征:
- 可以由多个元素组成,每个组成元素提供focus方法,事件处理和许多特征,这些特征使我们可以自由编程,但如果需要执行一些绘图,则有很多“开销”。
- 扩展不是针对特定目的而是针对通用服务进行了优化的通用对象。
DrawingVisual的范围是提出一种轻量级的对象绘制方法。
关于矩阵雨效应,我对如何从CodePen进行开发提出了一些想法,CodePen是一个在线社区,用于测试和展示用户创建的HTML,CSS和JavaScript代码段。
我假设读者知道WPF调度程序。简要地说,当您执行WPF应用程序时,它会自动创建一个新Dispatcher对象并调用其Run方法。所有视觉元素都将由调度程序线程创建,并且所有对视觉元素所做的修改都必须在Dispatcher线程上执行。
使用代码我的示例由两个项目组成:
1. MatrixRain这是解决方案的核心。该项目实现了一个模拟Matrix数字雨效果的UserControl。UserControl可以在任何窗口/页等中使用
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中随机选择用于下雨的字符
Start和Stop方法允许启动和停止动画:
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"
...
...
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、两个按钮:Start和Stop; 命令动画:
private void _StartButtonClick(object sender, RoutedEventArgs e)
{
mRain.Start();
}
private void _StopButtonClick(object sender, RoutedEventArgs e)
{
mRain.Stop();
}
4、两个按钮:Set1和Set2; 命令文本颜色:
private void _ChangeColorButtonClick(object sender, RoutedEventArgs e)
{
mRain.SetParameter(textBrush: ((Button)sender).Background);
}
PS:为了随机生成字母,我使用了个人的“CryptoRandom”类(包括源代码)而不是规范的Random方法。这是因为Random方法会生成“伪随机”数。如果您想深入了解,请点击此链接。