说明

本项目参考了 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. python-socket和进程线程协程(代码展示)

    socket # 一.socket # TCP服务端 import socket # 导入socket tcp_sk = socket.socket() # 实例化一个服务器对象 tcp_sk.bin ...

  2. 白话解读 WebRTC 音频 NetEQ 及优化实践

    NetEQ 是 WebRTC 音视频核心技术之一,对于提高 VoIP 质量有明显的效果,本文将从更为宏观的视角,用通俗白话介绍 WebRTC 中音频 NetEQ 的相关概念背景和框架原理,以及相关的优 ...

  3. RichText实现动态输入关键字高亮颜色显示

    int a = 0; string[] kc = new string[40] { "private","protected","public&quo ...

  4. Fishing Master HDU - 6709

    题目链接:https://vjudge.net/problem/HDU-6709 题意:给出n条鱼的抓捕时间k和煮鱼时间a[i],你要在最短时间把所有鱼都煮了.(注意你只有一口锅) 思路:首先我们必须 ...

  5. P1739_表达式括号匹配(JAVA语言)

    思路:刚开始想用stack,遇到'('就push,遇到')'就pop,后来发现其实我们只需要用到栈里'('的个数,所以我们用一个变量统计'('的个数就好啦~ 题目描述 假设一个表达式有英文字母(小写) ...

  6. Java系列教程-SpringMVC教程

    SpringMVC教程 1.SpringMVC概述 1.回顾MVC 1.什么是MVC MVC是模型(Model).视图(View).控制器(Controller)的简写,是一种软件设计规范. 是将业务 ...

  7. c/s应用程序自动更新组件GeneralUpdate3.2.1发布

    一.组件简介 GeneralUpdate是基于.net standard 开发的一款(c/s应用)自动升级程序.该组件将更新的核心部分抽离出来方便应用于多种项目当中目前适用于wpf,控制台应用,win ...

  8. Python基础之:Python中的IO

    目录 简介 linux输入输出 格式化输出 f格式化 format格式化 repr和str %格式化方法 读写文件 文件对象的方法 使用json 简介 IO就是输入和输出,任何一个程序如果和外部希望有 ...

  9. Spring笔记(四)

    Spring JdbcTemplate 一.JdbcTemplate概念及使用 1. 什么是JdbcTemplate: Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库 ...

  10. springboot的yml中的bruid没有提示

    解决方案: springboot的yml中的bruid没有提示只要在maven加上:druid-spring-boot-starter(注意版本的统一,否则可能会冲突) 效果图: