使用C#实现一个PPT遥控器
说明
本项目参考了 https://github.com/yangzhongke/PhoneAsPrompter 项目来完成实现,并对其进行了一些修改完善。
完整代码可以到 https://github.com/PuZhiweizuishuai/PPT-Remote-control 与 https://gitee.com/puzhiweizuishuai/PPT-Remote-control 查看。
软件下载地址: https://gitee.com/puzhiweizuishuai/PPT-Remote-control/releases/v1.0.0
另外,由于程序启动后会创建一个WEB服务器,用来显示PPT的操控界面,所以某些安全软件可能会报毒。但是程序本身是没有问题的。
截图

具体实现
通过在Win Form项目中内嵌一个Kestrel Web服务器,我们就可以通过浏览器向web服务器发送请求来接收远程操作指令。之后通过Late Binding的方式去操作PPT。
1、在 Win Form项目中内嵌HTTP服务器
在Form窗口启动时,我们新建一个Kestrel服务器
this.webHost = new WebHostBuilder()
.UseKestrel()
.Configure(ConfigureWebApp)
.UseUrls("http://*:" + port)
.Build();
// 异步运行服务器
this.webHost.RunAsync();
然后对其进行配置
private void ConfigureWebApp(IApplicationBuilder app)
{
app.UseDefaultFiles();
app.UseStaticFiles();
app.Run(async (context) =>
{
// 处理非静态请求
var request = context.Request;
var response = context.Response;
string path = request.Path.Value;
response.ContentType = "application/json; charset=UTF-8";
bool hasRun = true;
if (path == "/report")
{
string value = request.Query["value"];
this.BeginInvoke(new Action(() => {
this.PageLabel.Text = value;
}));
response.StatusCode = 200;
await response.WriteAsync("ok");
}
else
{
response.StatusCode = 404;
}
});
}
操作PPT
首先,由于涉及到了COM编程,我们需要注意内存回收与释放,所以需要用到COMReferenceTracker类进行应用管理。
每一步用到COM的地方,都要用T方法进行资源回收。
private dynamic T(dynamic comObj)
{
return this.comReference.T(comObj);
}
以下操作使用dynamic进行操作,所有操作需要去查询VBA文档了解具体用法,以下仅演示部分操作
打开一个PPT的操作实现
private void button1_Click(object sender, EventArgs e)
{
// 文件选择框
openFileDialog.Filter = "ppt文件|*.ppt;*.pptx;*.pptm";
if (openFileDialog.ShowDialog() != DialogResult.OK)
{
return;
}
string filename = openFileDialog.FileName;
this.ClearComRefs();
// 创建 PPT 对象
dynamic pptApp = T(PowerPointHelper.CreatePowerPointApplication());
// 显示 PPT
pptApp.Visible = true;
dynamic presentations = T(pptApp.Presentations);
// 打开 PPT
this.presentation = T(presentations.Open(filename));
// 全屏显示
T(this.presentation.SlideShowSettings).Run();
}
PPT上一个动画操作实现
T(T(presentation.SlideShowWindow).View).Previous();
下一步,与上一个操作类似,只需更换Previous()方法为Next()即可。
获取注释
首先我们需要一个方法去解析注释
private string GetInnerText(dynamic part)
{
StringBuilder sb = new StringBuilder();
dynamic shapes = T(T(part).Shapes);
int shapesCount = shapes.Count;
for (int i = 0; i < shapesCount; i++)
{
dynamic shape = T(shapes[i + 1]);
var textFrame = T(shape.TextFrame);
// MsoTriState.msoTrue==-1
if (textFrame.HasText == -1)
{
string text = T(textFrame.TextRange).Text;
sb.AppendLine(text);
}
sb.AppendLine();
}
return sb.ToString();
}
之后通过
dynamic notesPage = T(T(T(T(presentation.SlideShowWindow).View).Slide).NotesPage);
string notesText = GetInnerText(notesPage);
我们就可以获取具体每页的注释信息。
完善服务器
了解了以上的PPT操作之后,我们就需要去完善我们的Web服务器端配置。
用户访问相应的地址,然后去执行上面PPT操作部分的代码即可。
else if (path == "/getNote")
{
string notesText = null;
this.Invoke(new Action(() => {
if (this.presentation == null)
{
return;
}
try
{
dynamic notesPage = T(T(T(T(presentation.SlideShowWindow).View).Slide).NotesPage);
notesText = GetInnerText(notesPage);
}
catch (COMException ex)
{
notesText = "";
}
}));
await response.WriteAsync(notesText);
}
else if (path == "/next")
{
response.StatusCode = 200;
this.Invoke(new Action(() => {
if (this.presentation == null)
{
return;
}
try
{
T(T(this.presentation.SlideShowWindow).View).Next();
hasRun = true;
} catch (COMException e)
{
hasRun = false;
}
}));
if (hasRun)
{
await response.WriteAsync("OK");
}
else
{
await response.WriteAsync("NO");
}
}
else if (path == "/previous")
{
response.StatusCode = 200;
this.Invoke(new Action(() => {
if (this.presentation == null)
{
return;
}
try
{
T(T(this.presentation.SlideShowWindow).View).Previous();
hasRun = true;
}
catch (COMException e)
{
hasRun = false;
}
}));
if (hasRun)
{
await response.WriteAsync("OK");
}
else
{
await response.WriteAsync("NO");
}
完成前端
通过轮询的方式,不断的向服务端发送请求,获取最新的消息,这样我们就可以实现通过浏览器去操作PPT了。
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="renderer" content="webkit" />
<title>操作你的PPT</title>
<link rel="icon" href="/logo.ico">
<style>
div {
font-size: 25px
}
</style>
</head>
<body>
<div id="main" style="width:100vw;height:100vh;">
<p id="note"></p>
</div>
<script src="hammer.min.js"></script>
<script>
function httpGet(url, cb) {
fetch(url, {
headers: {
'Content-Type': 'application/json; charset=UTF-8'
},
method: 'GET'
}).then(response => response.text())
.then(text => {
cb(text)
})
.catch(e => {
return null
})
}
const note = document.querySelector("#note");
let hasRun = true
let getNotes = setInterval(() => {
httpGet('/getNote', (text) => {
note.innerText = text
})
}, 500)
function nextPage() {
httpGet('/next', (text) => {
if (text == 'NO') {
clearInterval(getNotes)
note.innerText = "幻灯片播放完毕!"
hasRun = false
} else {
if (!hasRun) {
getNotes = setInterval(() => {
httpGet('/getNote', (text) => {
note.innerText = text
})
}, 500)
hasRun = true
}
}
})
}
function previousPage() {
httpGet('/previous', (text) => {
if (text == 'NO') {
clearInterval(getNotes)
note.innerText = "幻灯片播放完毕!"
hasRun = false
} else {
if (!hasRun) {
getNotes = setInterval(() => {
httpGet('/getNote', (text) => {
note.innerText = text
})
}, 500)
hasRun = true
}
}
})
}
var hammer = new Hammer(document.querySelector("#main"));
hammer.on("swipeleft", function () {
nextPage();
});
hammer.on("swiperight", function () {
previousPage();
});
</script>
</body>
</html>
使用C#实现一个PPT遥控器的更多相关文章
- C# 复制幻灯片(包括格式、背景、图片等)到同/另一个PPT文档
C# 复制幻灯片(包括格式.背景.图片等)到同/另一个PPT文档 复制幻灯片是使用PowerPoint过程中的一个比较常见的操作,在复制一张幻灯片时一般有以下两种情况: 在同一个PPT文档内复制 从一 ...
- PPT 遥控器
1. 下载 最新版本: 百度袋鼠输入: http://daishu.baidu.com/?from=pptweb 百度PPT遥控器:http://ppt.baidu.com/ 2. 安装过程忽略 3. ...
- 中秋时候做了一个ppt画图插件
http://office.guanexcel.com/chart/chart.html PowerPoint里面简单的画图工具,输入数据选择图样即可插入到PPT中了
- 搞个小项目吧,做一个ppt播放器
先来两个参考链接,接下来再进行实战 http://www.geek-workshop.com/forum.php?mod=viewthread&tid=1137 http://www.geek ...
- 怎么用一个ppt介绍一个项目
- 致敬学长!J20航模遥控器开源项目计划【开局篇】 | 先做一个开机界面 | MATLAB图像二值化 | Img2Lcd图片取模 | OLED显示图片
我们的开源宗旨:自由 协调 开放 合作 共享 拥抱开源,丰富国内开源生态,开展多人运动,欢迎加入我们哈~ 和一群志同道合的人,做自己所热爱的事! 项目开源地址:https://github.com/C ...
- 怎样做出优秀的扁平化设计风格 PPT 或 Keynote 幻灯片演示文稿?(装)
不知道你有没有想过,为什么很人多的扁平化 PPT 是这个样子: 或者是这样: 然而,还有一小撮人的扁平化 PPT 却拥有那么高颜值: 为什么会产生这么大的差距呢?丑逼 PPT 应该如何逆袭成为帅逼呢? ...
- PPT扁平化设计总结
注:以下内容基本都来自知乎,由于已经不记得网址了,所以未能附上所有相关链接,抱歉. PPT扁平化设计原则一.亲密:意思相近的内容放在一起二.对齐:页面上的某两个元素之间总是围绕一条直线对齐三.对比:有 ...
- C#向PPT文档插入图片以及导出图片
PowerPoint演示文稿是我们日常工作中常用的办公软件之一,而图片则是PowerPoint文档的重要组成部分,那么如何向幻灯片插入图片以及导出图片呢?本文我将给大家分享如何使用一个免费版Power ...
随机推荐
- HiveHA机制源码分析
hive让大数据飞了起来,不再需要专人写MR.平常我们都可以用基于thrift的任意语言来调用hive. 不过爱恨各半,hive的thrift不稳定也是出了名的.很容易就出问题,让人无计可施.唯一的办 ...
- MySQL深入研究--学习总结(5)
前言 接上文,继续学习后续章节.细心的同学已经发现,我整理的并不一定是作者讲的内容,更多是结合自己的理解,加以阐述,所以建议结合原文一起理解. 第20章<幻读是什么,幻读有什么问题?> 先 ...
- 这个Bug的排查之路,真的太有趣了。
这是why哥的第 92 篇原创文章 在<深入理解Java虚拟机>一书中有这样一段代码: public class VolatileTest { public static volat ...
- IPFS挖矿靠谱吗?
IPFS是一个旨在创建持久且分布式存储和共享文件的网络传输协议,前景广阔且实用意义大,为区块链行业提供了一种新的可能.而IPFS挖矿挖出的FIL,则是在IPFS技术的基础上,对维护IPFS网络的用户的 ...
- 得分(JAVA语言)
package 第三章习题; /* * 给出一个由O和X组成的串(长度为1~80),统计得分. * 每个O得分为目前连续出现的O的个数,X得分为0. * 例如,OOXXOXXOOO的得分为 * ...
- Tomcat详解系列(3) - 源码分析准备和分析入口
Tomcat - 源码分析准备和分析入口 上文我们介绍了Tomcat的架构设计,接下来我们便可以下载源码以及寻找源码入口了.@pdai 源代码下载和编译 首先是去官网下载Tomcat的源代码和二进制安 ...
- 【linux】命令-网络相关
目录 前言 1. ifconfig 1.1 语法 1.2 参数说明 1.3 例程 2. iw 2.1 扫描可用无线网络 2.2 WiFi连接步骤(教程A) 2.2.1 查看可以用无线设备信息 2.2. ...
- CodeForces571A. Lengthening Sticks(组合数学-容斥)
题目大意: a,b,c三根木棍可以增加三个不同的数字,aa,bb,cc,且aa+bb+cc<=L,问能构成三角形的木棒有多少种方案 题目思路: 如果我们直接考虑把L分配给aa,bb,cc好像不好 ...
- CI/CD版本回滚Jenkins解决方案
一.创建项目 填写项目名,关系到项目路径对应请谨慎命名 二.项目配置 1.配置字符串参数和选项参数 2.代码仓库配置 3.构建环境 4.构筑脚本配置 5.点击左下方的保存或者应用 三.使用方法 1.发 ...
- 总结traefik 在k8s 环境中的配置文件
总结traefik 在k8s 环境中的配置文件 traefik.toml配置文件引用 [www@localhost traefik-ingress]$ more * :::::::::::::: co ...