说明

本项目参考了 https://github.com/yangzhongke/PhoneAsPrompter 项目来完成实现,并对其进行了一些修改完善。

完整代码可以到 https://github.com/PuZhiweizuishuai/PPT-Remote-controlhttps://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遥控器的更多相关文章

  1. C# 复制幻灯片(包括格式、背景、图片等)到同/另一个PPT文档

    C# 复制幻灯片(包括格式.背景.图片等)到同/另一个PPT文档 复制幻灯片是使用PowerPoint过程中的一个比较常见的操作,在复制一张幻灯片时一般有以下两种情况: 在同一个PPT文档内复制 从一 ...

  2. PPT 遥控器

    1. 下载 最新版本: 百度袋鼠输入: http://daishu.baidu.com/?from=pptweb 百度PPT遥控器:http://ppt.baidu.com/ 2. 安装过程忽略 3. ...

  3. 中秋时候做了一个ppt画图插件

    http://office.guanexcel.com/chart/chart.html PowerPoint里面简单的画图工具,输入数据选择图样即可插入到PPT中了

  4. 搞个小项目吧,做一个ppt播放器

    先来两个参考链接,接下来再进行实战 http://www.geek-workshop.com/forum.php?mod=viewthread&tid=1137 http://www.geek ...

  5. 怎么用一个ppt介绍一个项目

  6. 致敬学长!J20航模遥控器开源项目计划【开局篇】 | 先做一个开机界面 | MATLAB图像二值化 | Img2Lcd图片取模 | OLED显示图片

    我们的开源宗旨:自由 协调 开放 合作 共享 拥抱开源,丰富国内开源生态,开展多人运动,欢迎加入我们哈~ 和一群志同道合的人,做自己所热爱的事! 项目开源地址:https://github.com/C ...

  7. 怎样做出优秀的扁平化设计风格 PPT 或 Keynote 幻灯片演示文稿?(装)

    不知道你有没有想过,为什么很人多的扁平化 PPT 是这个样子: 或者是这样: 然而,还有一小撮人的扁平化 PPT 却拥有那么高颜值: 为什么会产生这么大的差距呢?丑逼 PPT 应该如何逆袭成为帅逼呢? ...

  8. PPT扁平化设计总结

    注:以下内容基本都来自知乎,由于已经不记得网址了,所以未能附上所有相关链接,抱歉. PPT扁平化设计原则一.亲密:意思相近的内容放在一起二.对齐:页面上的某两个元素之间总是围绕一条直线对齐三.对比:有 ...

  9. C#向PPT文档插入图片以及导出图片

    PowerPoint演示文稿是我们日常工作中常用的办公软件之一,而图片则是PowerPoint文档的重要组成部分,那么如何向幻灯片插入图片以及导出图片呢?本文我将给大家分享如何使用一个免费版Power ...

随机推荐

  1. MyBatis(十一):MyBatis架构流程浅析

    架构分层 我们将MyBatis架构分为三层,分别为接口层.数据处理层和框架支撑层 接口层:提供外部接口调用的API,使用端通过这些API来操作数据库,接口层收到请求后会调用数据处理层完成具体的数据处理 ...

  2. JAVA视频资料百度网盘分享

    1.javascript视频教程 链接: http://pan.baidu.com/s/1gd57FVH 密码: d9ei 2.JPA视频教程 链接: http://pan.baidu.com/s/1 ...

  3. 第一个win32程序

    vs2017下自动创建的窗口程序 // win_test.cpp : 定义应用程序的入口点. // #include "framework.h" #include "wi ...

  4. Kubernetes声明式API与编程范式

    声明式API vs 命令时API 计算机系统是分层的,也就是下层做一些支持的工作,暴露接口给上层用.注意:语言的本质是一种接口. 计算机的最下层是CPU指令,其本质就是用"变量定义+顺序执行 ...

  5. 第22 章 : 有状态应用编排 StatefulSet

    有状态应用编排 StatefulSet 本文将主要分享以下四方面的内容: "有状态"需求 用例解读 操作演示 架构设计 "有状态"需求 课程回顾 我们之前讲到过 ...

  6. Kafka 消息存储机制

    Kafka 消息以 Partition 作为存储单元,那么在 Partition 内消息是以什么样的格式存储的呢,如何处理 Partition 中的消息,又有哪些安全策略来保证消息不会丢失呢,这一篇我 ...

  7. Android创建自定义Application

    开发目的 创建一个自定义的MainApplication继承Application. 读取AndroidManifest.xml文件中Application节点的META-DATA数据.此处以ApiK ...

  8. Java代码格式化规范实践总结

    目标说明 统一良好的代码格式规范可以有效提升开发团队之间的「协作效率」,如果不同的开发团队或者开发人员采用不同的代码格式规范,那么每次Format代码都会导致大量的变化,在Code Review及Me ...

  9. python基础(补充):lambda匿名函数,用了的,都说好!

    lambda函数又叫做"匿名函数".当你完成一件小工作时,直接使用该函数可以让你的工作得心应手. lambda函数介绍 在Python中,定义函数使用的是def关键字,但是通过la ...

  10. 字符串函数的实现(三)之strcat

    C语言中的字符串函数有如下这些 获取字符串长度 strlen 长度不受限制的字符串函数 strcpy strcat strcmp 长度受限制的字符串函数 strncpy strncat strncmp ...