说明

本项目参考了 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>

最新文章

  1. java中定时器的四种方法
  2. iOS中几种常用的数据存储方式
  3. 【腾讯Bugly干货分享】微信读书iOS性能优化
  4. iOS-数据持久化-SQlite3
  5. 【Unity】12.5 Navmesh Obstacle组件
  6. 似是而非的k=sqrt(n)
  7. PHP中关于 basename、dirname、pathinfo 详解
  8. objectARX获取当前图层所有文字样式
  9. Oracle数据迁移至MySQL
  10. cmd 进入不同的驱动盘及上下级目录
  11. html5 文件拖拽上传
  12. 贴近浏览器窗口右侧的jqueryui dialog快速从左侧调整大小时对话框大小设置不准确的问题
  13. Java ByteArrayOutputStream中buf 的大小增长问题
  14. hdu 4882 ZCC Loves Codefires(数学题+贪心)
  15. NGUI中的Tween的委托使用
  16. Codeforces 768A Oath of the Night&#39;s Watch
  17. 论MVC中的传值
  18. java设计模式(2)---六大原则
  19. 比NGINX更快:nginx-1.15.5 vs mongols-1.2.3
  20. 剑指offer面试题27:二叉搜索树与双向链表

热门文章

  1. Beego框架学习---layout的使用
  2. C#无边框窗体拖动代码
  3. python-6-1
  4. 《逆向工程核心原理》——API HOOK
  5. 微信小程序--简约风博客小程序(基于云开发 - 全开源)
  6. Distributed | Raft
  7. Class类文件结构--访问标志
  8. 使用 shell 做 tcp 协议模拟
  9. dubbo负载均衡策略和集群容错策略都有哪些?动态代理策略呢?
  10. 自动化kolla-ansible部署ubuntu20.04+openstack-victoria单机