WPF教程十四:了解元素的渲染OnRender()如何使用
上一篇分析了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()方法进行重绘。目前就这么多拉。
WPF教程十四:了解元素的渲染OnRender()如何使用的更多相关文章
- webpack4 系列教程(十四):Clean Plugin and Watch Mode
作者按:因为教程所示图片使用的是 github 仓库图片,网速过慢的朋友请移步<webpack4 系列教程(十四):Clean Plugin and Watch Mode>原文地址.更欢迎 ...
- 无废话ExtJs 入门教程十四[文本编辑器:Editor]
无废话ExtJs 入门教程十四[文本编辑器:Editor] extjs技术交流,欢迎加群(201926085) ExtJs自带的编辑器没有图片上传的功能,大部分时候能够满足我们的需要. 但有时候这个功 ...
- RabbitMQ入门教程(十四):RabbitMQ单机集群搭建
原文:RabbitMQ入门教程(十四):RabbitMQ单机集群搭建 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://b ...
- WebGL简易教程(十四):阴影
目录 1. 概述 2. 示例 2.1. 着色器部分 2.1.1. 帧缓存着色器 2.1.2. 颜色缓存着色器 2.2. 绘制部分 2.2.1. 整体结构 2.2.2. 具体改动 3. 结果 4. 参考 ...
- WPF教程十二:了解自定义控件的基础和自定义无外观控件
这一篇本来想先写风格主题,主题切换.自定义配套的样式.但是最近加班.搬家.新租的房子打扫卫生,我家宝宝6月中旬要出生协调各种的事情,导致了最近精神状态不是很好,又没有看到我比较喜欢的主题风格去模仿的, ...
- Redis教程(十四):内存优化介绍
转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/142.html 一.特殊编码: 自从Redis 2.2之后,很多数据类型都 ...
- Unity3D脚本中文系列教程(十四)
http://dong2008hong.blog.163.com/blog/static/469688272014032134394/ WWWFrom 类Unity3D脚本中文系列教程(十三)辅助类. ...
- WPF教程十五:数据模板的使用(重发)
数据模板 数据模板是一段如何显示绑定在VM对象的XAML代码.数据模板可以包含任意元素的组合,基于Binding来显示不同的信息. 在实际的开发中数据模板的应用场景很多,同样一个控件可以根据不同的绑定 ...
- WPF教程十:如何使用Style和Behavior在WPF中规范视觉样式
在使用WPF编写客户端代码时,我们会在VM下解耦业务逻辑,而剩下与功能无关的内容比如动画.视觉效果,布局切换等等在数量和复杂性上都超过了业务代码.而如何更好的简化这些编码,WPF设计人员使用了Styl ...
随机推荐
- 微服务架构(Microservices) ——Martin Flower
不知不觉到达了Sring Boot的学习中了,在学习之前,了解微服务架构是很有必要的,对于自己提升今后面试的软实力有很大帮助,在此写下. 让我们接下来看下Martin Flower 如何解释微服务架构 ...
- Python3 url解码与参数解析
Python3 url解码与参数解析 有些子节点名字直接就是编码后的url,就像下面这行一样: url='dubbo%3A%2F%2F10.4.5.3%3A20880%2Fcom.welab.auth ...
- GO学习-(19) Go语言基础之网络编程
Go语言基础之网络编程 现在我们几乎每天都在使用互联网,我们前面已经学习了如何编写Go语言程序,但是如何才能让我们的程序通过网络互相通信呢?本章我们就一起来学习下Go语言中的网络编程. 关于网络编程其 ...
- AI算子列表
AI算子列表 概述 目前只有部分算子可在一个库中同时运行在MLU220和MLU270平台.也就是用户使用 ./build_cnplugin.sh --mlu270 命令编译生成的 libcnplugi ...
- Tengine MLOps概述
Tengine MLOps概述 大幅提高产业应用从云向边缘迁移的效率 MLOps Cloud Native 聚焦于提升云端的运营过程效率 MLOps Edge Native 聚焦于解决边缘应用开发及异 ...
- 用Microsoft DirectX光线跟踪改善渲染质量
用Microsoft DirectX光线跟踪改善渲染质量 Implementing Stochastic Levels of Detail with Microsoft DirectX Raytrac ...
- 十一、diff和patch打补丁
diff制作补丁文件的原理:告诉我们怎么修改第一个文件后能得到第二个文件. diff命令常用选项: -u 输出统一内容的头部信息(打补丁使用),计算机知道是哪个文件需要修改 -r 递归对比目录中 ...
- 将Winform和wpf的界面转换为CPF代码用来实现跨平台
CPF的设计器里带界面代码转换功能,将运行中的Winform或者wpf的程序界面转换为cpf代码,主要转换控件类型和布局,默认支持的是常用的原生控件.不支持Netcore,只支持.Netframewo ...
- docker2-镜像原理及创建新的镜像
1,镜像是什么 镜像是一种轻量级.可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码.运行时.库.环境变量和配置文件 在docker中所有应用 ...
- 对volatile的理解--从JMM以及单例模式剖析
请谈谈你对volatile的理解 1.volitale是Java虚拟机提供的一种轻量级的同步机制 三大特性1.1保证可见性 1.2不保证原子性 1.3禁止指令重排 首先保证可见性 1.1 可见性 概念 ...