本文将从 OpenXML 方面聊 PPT 的动画框架,本文是属于编程方面而不是 PPT 动画制作教程
开始之前,还请掌握一些基础知识,如阅读以下博客
本文不讨论 Slide Master 和 Slide Layout 的动画,关于这两个请参阅 dotnet OpenXML 的 Slide Master 和 Slide Layout 是什么
本文只讨论 Slide 页面里面的动画
元素主序列动画
在 OpenXML 中,如果一个动画是依靠翻页或点击页面进行触发的,那么这些动画有顺序的触发,这部分就是主序列动画,也叫 主动画序列 在 OpenXML 的 PPTX 文件里面的存放大概如下
<p:timing>
<p:tnLst>
<p:par>
<p:cTn id="1" dur="indefinite" restart="never" nodeType="tmRoot">
<p:childTnLst>
<p:seq concurrent="1" nextAc="seek">
<p:cTn id="2" dur="indefinite" nodeType="mainSeq">
</p:cTn>
</p:seq>
</p:childTnLst>
</p:cTn>
</p:par>
</p:tnLst>
</p:timing>
动画是存放在 Slide 页面里面的 Timing 属性里面,通过 OpenXML SDK 获取方法如下
using var presentationDocument =
DocumentFormat.OpenXml.Packaging.PresentationDocument.Open("Test.pptx", false);
var presentationPart = presentationDocument.PresentationPart;
var slidePart = presentationPart!.SlideParts.First();
var slide = slidePart.Slide;
var timing = slide.Timing;
默认的动画将会放在 NodeType 为 TmingRoot 的 cTn 也就是 CommonTimeNode 里面,获取代码如下
var slide = slidePart.Slide;
var timing = slide.Timing;
// 第一级里面默认只有一项
var commonTimeNode = timing?.TimeNodeList?.ParallelTimeNode?.CommonTimeNode;
if (commonTimeNode?.NodeType?.Value == TimeNodeValues.TmingRoot)
{
// 这是符合约定
// nodeType="tmRoot"
}
按照约定,页面里面的动画将放在 TmingRoot 的里层,而元素的主序列动画也属于页面里面的动画,因此也就放在 TmingRoot 的里层
如上面代码就是 nodeType="mainSeq"
主序列动画的定义,获取主序列动画的代码如下
// <p:timing>
// <p:tnLst>
// <p:par>
// <p:cTn id="1" dur="indefinite" restart="never" nodeType="tmRoot">
// 第一级里面默认只有一项
var commonTimeNode = timing?.TimeNodeList?.ParallelTimeNode?.CommonTimeNode;
if (commonTimeNode?.NodeType?.Value == TimeNodeValues.TmingRoot)
{
// 这是符合约定
// nodeType="tmRoot"
}
if (commonTimeNode?.ChildTimeNodeList == null) return;
// <p:childTnLst>
// <p:seq concurrent="1" nextAc="seek">
// 理论上只有一项,而且一定是 SequenceTimeNode 类型
var sequenceTimeNode = commonTimeNode.ChildTimeNodeList.GetFirstChild<SequenceTimeNode>();
// <p:cTn id="2" dur="indefinite" nodeType="mainSeq">
var mainSequenceTimeNode = sequenceTimeNode.CommonTimeNode;
if (mainSequenceTimeNode?.NodeType?.Value == TimeNodeValues.MainSequence)
接下来讨论的就是放在主序列动画里面的动画的存储方式,以上代码放在 github 和 gitee 欢迎访问
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 2c06ddf74e45c31ad7842dd06dc09bcc67b6142e
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
获取代码之后,进入 PptxDemo 文件夹
单个主序列动画
放在主序列动画里面的单个动画,创建方式如新建一个 PPT 文件,然后拖入一个形状,点击一下飞入动画。此时的飞入动画就是属于放在主动画序列的一个动画,当然飞入动画在类型上属于进入动画。在 PPT 里面,有 进入动画、强调动画、退出动画等类型
以下是单个飞入动画的主序列动画的 OpenXML 文档的例子
<p:timing>
<p:tnLst>
<p:par>
<p:cTn id="1" dur="indefinite" restart="never" nodeType="tmRoot">
<p:childTnLst>
<p:seq concurrent="1" nextAc="seek">
<p:cTn id="2" dur="indefinite" nodeType="mainSeq">
<p:childTnLst>
<p:par>
<p:cTn id="3" fill="hold">
<p:stCondLst>
<p:cond delay="indefinite" />
</p:stCondLst>
<p:childTnLst>
<p:par>
<p:cTn id="4" fill="hold">
<p:stCondLst>
<p:cond delay="0" />
</p:stCondLst>
<p:childTnLst>
<p:par>
<p:cTn id="5" presetID="2" presetClass="entr" presetSubtype="4" fill="hold" grpId="0" nodeType="clickEffect">
<!-- 飞入动画 -->
</p:cTn>
</p:par>
</p:childTnLst>
</p:cTn>
</p:par>
</p:childTnLst>
</p:cTn>
</p:par>
</p:childTnLst>
</p:cTn>
</p:seq>
</p:childTnLst>
</p:cTn>
</p:par>
</p:tnLst>
</p:timing>
可以看到单个动画放在单个主序列动画的两层 cTn 里面
如上面的内容,大概可以理解存放的方式了,只是在 PPT 里面,有多个 ParallelTimeNode 和 CommonTimeNode 的嵌套。从 mainSeq 也就是 MainSequence 主动画序列以下,获取到的实际的进入动画,是经过了如下路径才能获取
cTn (mainSeq) -> childTnLst -> par -> cTn (id="3") -> childTnLst -> par -> cTn (id="4") -> childTnLst -> par -> cTn (id="5" presetClass="entr" 飞入动画)
代码的获取方式如下
// <p:cTn id="2" dur="indefinite" nodeType="mainSeq">
var mainSequenceTimeNode = sequenceTimeNode.CommonTimeNode;
if (mainSequenceTimeNode?.NodeType?.Value == TimeNodeValues.MainSequence)
{
// [TimeLine 对象 (PowerPoint) | Microsoft Docs](https://docs.microsoft.com/zh-cn/office/vba/api/PowerPoint.TimeLine )
// MainSequence 主动画序列
var mainParallelTimeNode = mainSequenceTimeNode.ChildTimeNodeList;
foreach (var openXmlElement in mainParallelTimeNode)
{
// 并行关系的
if (openXmlElement is ParallelTimeNode parallelTimeNode)
{
var timeNode = parallelTimeNode.CommonTimeNode.ChildTimeNodeList
.GetFirstChild<ParallelTimeNode>().CommonTimeNode.ChildTimeNodeList
.GetFirstChild<ParallelTimeNode>().CommonTimeNode;
switch (timeNode.PresetClass.Value)
{
case TimeNodePresetClassValues.Entrance:
// 进入动画
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
以上测试文件和测试代码 放在 github 和 gitee 可以通过以下命令获取
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin d47f1aec803bfd7adb32e82fb61916308d317fcd
除了进入动画之外,还有强调和退出动画,详细请看 dotnet OpenXML 读取 PPT 动画进入退出强调动画类型
主序列顺序动画
新建 PPT 课件,添加一个元素,然后分别设置元素的进入强调和退出动画,然后设置强调和退出动画是从上一项之后开始,如下图
根据上文描述,可以了解到此时元素的进入和强调和退出类型动画都放在主序列动画里面,如下图
<p:cTn id="2" dur="indefinite" nodeType="mainSeq">
<p:childTnLst>
<p:par>
<p:cTn id="3" fill="hold">
<p:stCondLst>
<p:cond delay="indefinite" />
</p:stCondLst>
<p:childTnLst>
<p:par>
<p:cTn id="4" fill="hold">
<p:stCondLst>
<p:cond delay="0" />