上一篇分析了WPF元素中布局系统的MeasureOverride()和ArrangeOverride()方法。本节将进一步深入分析和研究元素如何渲染它们自身。

大多数WPF元素通过组合方式创建可视化外观。元素通过其他更基础的元素进行构建。比如,使用标记定义用户控件的组合元素,处理标记方式与自定义窗口中的XAML相同。使用控件模板为自定义控件提供可视化树。并且当创建自定义面板时,根本不必定义任何可视化细节。组合元素由控件使用者提供,并添加到Children集合中。

​ 接下来就是绘制内容,在WPF中,一些累需要负责绘制内容。在WPF中,这些类位于元素树的底层。在典型窗口中,是通过单独的文本、形状以及位图执行渲染的,而不是通过高级元素。

OnRender()方法

为了执行自定义渲染,元素必须重写OnRender()方法,该方法继承自UIElement基类。一些控件使用OnRender()方法绘制可视化细节并使用组合在其上叠加其他元素。Border和Panel类是两个例子,Border类在OnRender()方法中绘制边框,Panel类在OnRender()方法中绘制背景。Border和Panel类都支持子内容,并且这些子内容在自定义的绘图细节之上进行渲染。

OnRender()方法接收一个DrawingCntext对象,该对象为绘制内容提供了一套很有用的方法。在OnRender()方法中执行绘图的主要区别是不能显式的创建和关闭DrawingContext对象。这是因为几个不同的OnRender()方法可能使用相同的DrawingContext对象。例如派生的元素可以执行一些自定义绘图操作并调用基类中的OnRender()方法来绘制其他内容。这种方法是可行的,因为当开始这一过程时,WPF会自动创建DrawingContext对象,并且当不再需要时关闭该对象。

OnRender()方法实际上没有将内容绘制到屏幕上,而是绘制到DrawingContext对象上,然后WPF缓存这些信息。WPF决定元素何时需要重新绘制并绘制使用DrawingContext对象创建的内容。这是WPF保留模式图形系统的本质--由开发人员定义内容,WPF无缝的管理绘制和刷新过程。

关于WPF渲染,大多数类是通过其他更简单的类构建的,并且对于典型的控件,为了找到实际重写OnRender()方法的类,需要进入到控件元素树种非常深的层次。下面是一些重写了OnRender()方法的类:

  • TextBlock类 无论在何处放置文本,都会有TextBlock对象使用使用OnRender()方法绘制文本。
  • Image类。Image类重写OnRender()方法,使用DrawingContext.DrawImage()方法绘制图形内容。
  • MediaElement类。如果正在使用该类播放视频文件,该类会重写OnRender()方法以绘制视频帧。
  • 各种形状类。Shape基类重写了OnRender()方法,通过使用DrawingContext.DrawGeometry()方法,绘制在其内部存储的Geometry对象。根据Shape类的特定派生类,Geometry对象可以表示椭圆、矩形、或更复杂的由直线和曲线构成的路径。许多元素使用形状绘制小的可视化细节。
  • 各种修饰类。比如ButtonChrome和ListBoxChrome绘制通用控件的外侧外观,并在具体指定的内部放置内容。其他许多继承自Decorator的类,如Border类,都重写了OnRender()方法。
  • 各种面板类。尽管面板的内容是由其子元素提供的,但是OnRender()方法绘制具有背景色(假设设置了Background属性)的矩形。

重写OnRender()方法不是渲染内容并且将其添加到用户界面的唯一方法。也可以创建DrawingVisual对象,并使用AddVisualChild()方法为UIElement对象添加该可视化对象,然后调用DrawingVisual.RenderOpen()方法为DrawingVisual对象检索DrawingContext对象,并使用返回的DrawingContext对象渲染DrawingVisual对象的内容。

在WPF种,一些元素使用这种策略在其他元素内容之上现实一些图形细节。例如在拖放指示器、错误提示器以及焦点框种可以看到这种情况。在所有这些情况种,DrawingVisual类允许元素在其他内容之上绘制内容,而不是在其他内容之下绘制内容。但对于大部分情况,是在专门的OnRender()方法种进行渲染。

写了这么多是不是不好理解?多看几遍,这里我除了比较啰嗦的引跑题的内容,其他的基本上原封不动的抄了过来,或者等看完下面的内容,在回来上面从新读一遍,上面的内容主要是讲应用场景,我自认为我总结的没有他的好《编程宝典》,就全拿过来了。

请注意,可能看到这里就发现这些东西也不常用,为啥要放到这个入门的系列里。因为在某些场景下,这种OnRender()更适用。因为前段时间熬了半个月的夜,写一个通过Stylus写字时字体美化的效果,主要逻辑就是OnRender()这些相关的内容,所以我觉得在客户端开发中,会遇到这种使用OnRender()能更好更快速解决问题的场景,现在开始本章的学习。

什么场合合适使用较低级的OnRender()方法。

大多数自定义元素不需要自定义渲染。但是当属性发生变化或执行特定操作时,需要渲染复杂的变化又特别大的可视化外观,此时使用自定义的渲染方法可能更加简单并且更便捷。

我们通过一段代码来演示一个简单的效果。我们在用户移动鼠标时,显示一个跟随鼠标的光圈。

我们创建名为CustomDrawnElement.cs的类,继承自FrameworkElement类,该类只提供一个可以设置的属性渐变的背景色(前景色被硬编码为白色)。

使用Propdp=>2次tab创建依赖项属性BackgroundColor。注意这里的Metadata被修改为FrameworkPropertyMetadata,并且设置了AffectsRender,F12跳转过去,提示更改此依赖属性的值会影响呈现或布局组合的某一方面(不是测量或排列过程)。因此,无论何时改变了背景色,WPF都会自动调用OnRender()方法。当鼠标移动时,也需要确保调用了OnRender()方法。通过在合适的位置使用InvalidateVisual()方法来实现。

  public class CustomDrawnElement : FrameworkElement
{
public Color BackgroundColor
{
get { return (Color)GetValue(BackgroundColorProperty); }
set { SetValue(BackgroundColorProperty, value); }
} // Using a DependencyProperty as the backing store for BackgroundColor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BackgroundColorProperty = DependencyProperty.Register("BackgroundColor", typeof(Color), typeof(CustomDrawnElement), new FrameworkPropertyMetadata(Colors.Yellow, FrameworkPropertyMetadataOptions.AffectsRender));
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
this.InvalidateVisual();
} protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
this.InvalidateVisual();
}
}

当这些都做完时,剩下就是我们需要重写的OnRender()方法了。我们通过这个方法绘制元素背景。ActualWidth和ActualHeight属性指示控件最终的渲染尺寸。为了保证能在当前鼠标正确的位置来渲染,我们需要一个方法来计算当前鼠标位置和渲染的中心点。

  protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
Rect bounds = new Rect(0, 0, base.ActualWidth, base.ActualHeight);
drawingContext.DrawRectangle(GetForegroundBrush(), null, bounds); } private Brush GetForegroundBrush()
{
if (!IsMouseOver)
{
return new SolidColorBrush(Color.FromRgb(0x7D, 0x7D, 0xFF));
}
else
{
RadialGradientBrush brush = new RadialGradientBrush(Color.FromRgb(0xE0, 0xE0,0xE0), Color.FromRgb(0x7D, 0x7D, 0xFF));
brush.RadiusX = 0.9;
brush.RadiusY = 0.9;
Point absoluteGradientOrigin = Mouse.GetPosition(this);
Point relativeGradientOrigin = new Point(absoluteGradientOrigin.X / base.ActualWidth, absoluteGradientOrigin.Y / base.ActualHeight);
brush.GradientOrigin = relativeGradientOrigin;
brush.Center = relativeGradientOrigin;
return brush;
}
}

在主窗体中添加对该元素的使用:

<Window x:Class="CustomOnRender.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CustomOnRender"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:CustomDrawnElement Width="400" Height="300"/>
<StackPanel Margin="100">
<TextBlock Text="测试TextBlock" Width="100" />
<Button Width="120" Content="fffff"/>
</StackPanel>
</Grid>
</Window>

但是如果这么实现的话,就会出现一个和之前学习内容矛盾的问题,如果在控件中使用自定义绘图的话,我们硬编码了绘图逻辑,控件的可视化外观就不能通过模板进行定制了。

更好的办法是设计单独的绘制自定义内容的元素,然后再控件的默认模板内部使用自定义元素。

自定义绘图元素通常扮演两个角色:

  • 它们绘制一些小的图形细节,(滚动按钮上的箭头)。
  • 它们再另一个元素周围提供更加详细的背景或边框。

我们使用自定义装饰元素。通过修改上面的例子来完成。我们新建一个CustomDrawnDecorator类继承自Decorator类;

重新修改代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media; namespace CustomOnRender
{
public class CustomDrawnElementDecorator : Decorator
{
public Color BackgroundColor
{
get { return (Color)GetValue(BackgroundColorProperty); }
set { SetValue(BackgroundColorProperty, value); }
} // Using a DependencyProperty as the backing store for BackgroundColor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BackgroundColorProperty = DependencyProperty.Register("BackgroundColor", typeof(Color), typeof(CustomDrawnElementDecorator), new FrameworkPropertyMetadata(Colors.Yellow, FrameworkPropertyMetadataOptions.AffectsRender)); protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
this.InvalidateVisual();
} protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
this.InvalidateVisual();
} protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
Rect bounds = new Rect(0, 0, base.ActualWidth, base.ActualHeight);
drawingContext.DrawRectangle(GetForegroundBrush(), null, bounds); } private Brush GetForegroundBrush()
{
if (!IsMouseOver)
{
return new SolidColorBrush(Color.FromRgb(0x7D, 0x7D, 0xFF));
}
else
{
RadialGradientBrush brush = new RadialGradientBrush(Color.FromRgb(0xE0, 0xE0, 0xE0), Color.FromRgb(0x7D, 0x7D, 0xFF));
brush.RadiusX = 0.9;
brush.RadiusY = 0.9;
Point absoluteGradientOrigin = Mouse.GetPosition(this);
Point relativeGradientOrigin = new Point(absoluteGradientOrigin.X / base.ActualWidth, absoluteGradientOrigin.Y / base.ActualHeight);
brush.GradientOrigin = relativeGradientOrigin;
brush.Center = relativeGradientOrigin;
return brush;
}
}
protected override Size MeasureOverride(Size constraint)
{
//return base.MeasureOverride(constraint);
UIElement child = this.Child;
if (child != null)
{
child.Measure(constraint);
return child.DesiredSize;
}
else
{
return new Size();
}
}
}
}
<Window x:Class="CustomOnRender.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CustomOnRender"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ControlTemplate x:Key="WithCustomChrome" >
<local:CustomDrawnElementDecorator BackgroundColor="LightGray">
<ContentPresenter Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
Content="{TemplateBinding ContentControl.Content}" RecognizesAccessKey="True"/>
</local:CustomDrawnElementDecorator>
</ControlTemplate>
</Window.Resources> <Page Template="{StaticResource WithCustomChrome}">
<StackPanel Margin="100">
<TextBlock Text="测试TextBlock" Width="100" />
<Button Width="120" Content="fffff"/>
</StackPanel>
</Page>
<!-- <local:CustomDrawnElement Width="400" Height="300"/>-->
</Window>

这篇主要内容就是如何使用OnRender()方法进行重绘。目前就这么多拉。

最新文章

  1. HCP查询配置
  2. 给备战NOIP 2014 的战友们的10条建议
  3. Caffe 抽取CNN网络特征 Python
  4. ios 使用xib时,在UIScrollView中添建内容view时,使用约束的注意
  5. Android 热补丁和热修复
  6. STL 库中的陷阱----一个难以察觉的 bug
  7. HybridTime - Accessible Global Consistency with High Clock Uncertainty
  8. dos快速通道
  9. javascript 函数式编程
  10. ADB Server Didn’t ACK ,failed to Start Daemon 解决方法
  11. (转载)在Delphi中利用MSDASC来配置数据库链接
  12. QT5下获取本机IP地址、计算机名、网络连接名、MAC地址、子网掩码、广播地址
  13. libev中timer时间事件监控器
  14. vim全局替换命令
  15. TFS线上生成环境发布历程
  16. 微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)
  17. Success Rate CodeForces - 807C (数学+二分)
  18. Hadoop Streaming开发要点
  19. JAVA实现调用微信js-sdk扫一扫
  20. HashMap.put()和get()原理

热门文章

  1. Hadoop MapReduce 一文详解MapReduce及工作机制
  2. 在react中使用redux并实现计数器案例
  3. GO学习-(8) Go语言基础之数组
  4. ZooKeeper学习笔记三:使用ZooKeeper实现一个简单的配置中心
  5. ML Pipelines管道
  6. 深入理解java虚拟机笔记补充-JVM常见参数设置
  7. 【UG二次开发】 UF_OBJ_ask_name 获取对象名字
  8. React开发中react-route-dom使用BrowserRouter部署到服务器上刷新时报404的问题
  9. python通过字典实现购物车案例-用户端
  10. Linux命令大全之挂载命令