上一篇分析了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()如何使用的更多相关文章

  1. webpack4 系列教程(十四):Clean Plugin and Watch Mode

    作者按:因为教程所示图片使用的是 github 仓库图片,网速过慢的朋友请移步<webpack4 系列教程(十四):Clean Plugin and Watch Mode>原文地址.更欢迎 ...

  2. 无废话ExtJs 入门教程十四[文本编辑器:Editor]

    无废话ExtJs 入门教程十四[文本编辑器:Editor] extjs技术交流,欢迎加群(201926085) ExtJs自带的编辑器没有图片上传的功能,大部分时候能够满足我们的需要. 但有时候这个功 ...

  3. RabbitMQ入门教程(十四):RabbitMQ单机集群搭建

    原文:RabbitMQ入门教程(十四):RabbitMQ单机集群搭建 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://b ...

  4. WebGL简易教程(十四):阴影

    目录 1. 概述 2. 示例 2.1. 着色器部分 2.1.1. 帧缓存着色器 2.1.2. 颜色缓存着色器 2.2. 绘制部分 2.2.1. 整体结构 2.2.2. 具体改动 3. 结果 4. 参考 ...

  5. WPF教程十二:了解自定义控件的基础和自定义无外观控件

    这一篇本来想先写风格主题,主题切换.自定义配套的样式.但是最近加班.搬家.新租的房子打扫卫生,我家宝宝6月中旬要出生协调各种的事情,导致了最近精神状态不是很好,又没有看到我比较喜欢的主题风格去模仿的, ...

  6. Redis教程(十四):内存优化介绍

    转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/142.html 一.特殊编码: 自从Redis 2.2之后,很多数据类型都 ...

  7. Unity3D脚本中文系列教程(十四)

    http://dong2008hong.blog.163.com/blog/static/469688272014032134394/ WWWFrom 类Unity3D脚本中文系列教程(十三)辅助类. ...

  8. WPF教程十五:数据模板的使用(重发)

    数据模板 数据模板是一段如何显示绑定在VM对象的XAML代码.数据模板可以包含任意元素的组合,基于Binding来显示不同的信息. 在实际的开发中数据模板的应用场景很多,同样一个控件可以根据不同的绑定 ...

  9. WPF教程十:如何使用Style和Behavior在WPF中规范视觉样式

    在使用WPF编写客户端代码时,我们会在VM下解耦业务逻辑,而剩下与功能无关的内容比如动画.视觉效果,布局切换等等在数量和复杂性上都超过了业务代码.而如何更好的简化这些编码,WPF设计人员使用了Styl ...

随机推荐

  1. MyBatisPlus详细总结记录

    本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com 小 Hub 领读: 一篇写得非常详细的文章,增删改查,各种插件,让你测底熟悉 mybatis plus. 作者:yo ...

  2. 【Web前端HTML5&CSS3】11-定位

    笔记来源:尚硅谷Web前端HTML5&CSS3初学者零基础入门全套完整版 目录 定位的简介 1. 相对定位 偏移量(offset) 相对定位的特点 2. 绝对定位 绝对定位的特点 包含块(co ...

  3. ffmpeg安装之linux编译安装

    转发自白狼栈:查看原文 关于ffmpeg的安装,有的人可能要折腾很久,甚至折腾一个礼拜,究其原因,基本都是编译安装惹的祸. 我们提供4种安装方式,最复杂的莫过于centos7上的编译安装. ffmpe ...

  4. 数据库原理 第七章 数据库设计和ER模型

    第七章讲述一个E-R设计如何转换成一个关系模式的集合以及如何在该设计中找到某些约束. 1.概念设计定义了数据库中表示的实体.实体的属性.实体之间的联系,以及实体和联系上的约束 在设计一个数据库模型的时 ...

  5. TSR交通标志检测与识别

    TSR交通标志检测与识别 说明: 传统图像处理算法的TSR集成在在ARM+DSP上运行,深度学习开发的TSR集成到FPGA上运行. 输入输出接口 Input: (1)图像视频分辨率(整型int) (2 ...

  6. 3DPytorch-API NVIDIA Kaolin

    3DPytorch-API NVIDIA Kaolin NVIDIA Kaolin library provides a PyTorch API for working with a variety ...

  7. 剑指 Offer 04. 二维数组中的查找

    链接:https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/ 标签:数组.双指针.二分 题目 在一个 n * m ...

  8. pandas的数据结构介绍(一)—— Series

    pandas两个主要数据结构之一--Series 类似于一维数组,由一组数据和与其相关的一组索引组成 obj = Series([4, 7, -5, 3], index=['d', 'b', 'a', ...

  9. 前端 JavaScript 复制粘贴的奥义——Clipboard 对象概述

    前言 作为一名资深搬砖工,你要问我用得最熟练的技能是什么,那我敢肯定且自豪的告诉你:是 Ctrl+C !是 Ctrl+V! 不信?你来看看我键盘上的 Ctrl.C 和 V 键,那油光发亮的包浆程度,不 ...

  10. Spring Data JPA的Audit功能,审计数据库的变更

    我最新最全的文章都在南瓜慢说 www.pkslow.com,欢迎大家来喝茶! 1 数据库审计 数据库审计是指当数据库有记录变更时,可以记录数据库的变更时间和变更人等,这样以后出问题回溯问责也比较方便. ...