为PowerPoint制作倒计时计时器加载项 - 第 2 部分
一个逐步指导,用于创建PowerPoint的VSTO倒计时计时器加载项
下载
引言
最近,我写了一篇文章,标题为“为PowerPoint制作倒计时计时器加载项 - 第 1 部分”。在第 1 部分中,我仅使用 VBA 创建了加载项。现在在第 2 部分中,我将使用 C# 创建 PowerPoint 的 VSTO 加载项。
背景
Visual Studio Tools for Office (VSTO) 是一组开发工具,以 Visual Studio 加载项(项目模板)和运行时形式提供。它大大简化了 Office 加载项的开发过程。我现在将使用 Visual Studio 2019 构建相同的倒计时计时器加载项。
Using the Code
- 首先让我们创建一个新项目
请选择“Powerpoint VSTO 加载项”项目模板,以及C#,点击“下一步”。
- 将项目名称键入“
CountDown
”,保持其余默认设置,然后点击“创建”。 - 下面是系统创建的骨架
- 添加Ribbon(视觉设计器)
在解决方案资源管理器窗格中选择“CountDown”项目,右键单击鼠标,在弹出菜单中,选择“添加\新建项”。
选择添加Ribbon(视觉设计器)并点击添加。
注意:或者,您也可以选择添加Ribbon(XML)并点击添加,XML 具有更多可玩的功能,但是 XML 没有 GUI,我个人更喜欢视觉设计器。
- 将 8 个按钮插入到 Ribbon 中
- 自定义 8 个按钮
- 最终,所有 8 个按钮应如下所示
- 添加 AboutBox
- 根据以下内容自定义 AboutBox
using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Reflection; using System.Threading.Tasks; using System.Windows.Forms; namespace CountDown { partial class frmAboutBox : Form { public frmAboutBox() { InitializeComponent(); this.Text = String.Format("About {0}", AssemblyTitle); this.labelProductName.Text = AssemblyProduct; this.labelVersion.Text = String.Format("Version {0}", AssemblyVersion); this.labelCopyright.Text = AssemblyCopyright; this.labelCompanyName.Text = AssemblyCompany; this.textBoxDescription.Text = "This Utility is for user to add \"CountDown Timers\" in PPT slides.\n" + "It allows users to add any number of timers with different preset duration.\n" + "How to use:\n" + " 1. Find \"CountDown Tab\", then click on \"Install CountDown\"\n" + " 2. Select a slide and click on \"Add Timer\"\n" + " 3. To play the timer, in \"Slide Show\" mode, click on the Timer, it will start to count down, click again it reset.\n" + " 4. To change the preset duration & TextEffect, select a Timer on a slide, then click on \"Edit Timer\"\n" + " 5. To delete a timer, select a Timer on a slide, then click on \"Del Timer\""; } } }
- 添加
frmDuration
- 根据以下内容自定义
frmDuration
using Microsoft.VisualBasic; using System; using System.IO; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace CountDown { public partial class frmDuration : Form { private int nDuration; public int Duration { get { return nDuration; } set { nDuration = value; cboDuration.Text = value.ToString(); } } private int nTextEffectIdx; public int TextEffectIdx { get { return nTextEffectIdx; } set { nTextEffectIdx = value; cboTextEffect.Text = value.ToString(); } } private string sSoundEffect; public string SoundEffect { get { return sSoundEffect; } set { sSoundEffect = value; cboSoundEffect.Text = value; } } public frmDuration() { InitializeComponent(); ResetComboBox(cboDuration); ResetComboBox(cboSoundEffect); ResetComboBox(cboTextEffect); } private void ResetComboBox(ComboBox oComboBox) { if (oComboBox.Name == "cboDuration") { oComboBox.Items.Clear(); oComboBox.Items.Add("1"); oComboBox.Items.Add("2"); oComboBox.Items.Add("3"); oComboBox.Items.Add("4"); oComboBox.Items.Add("5"); oComboBox.Items.Add("10"); oComboBox.Items.Add("15"); oComboBox.Items.Add("30"); oComboBox.Items.Add("45"); oComboBox.Items.Add("60"); oComboBox.Items.Add("90"); oComboBox.Items.Add("120"); oComboBox.Items.Add("150"); oComboBox.Items.Add("180"); oComboBox.Items.Add("210"); oComboBox.Items.Add("240"); oComboBox.Items.Add("300"); oComboBox.SelectedIndex = 4; } else if (oComboBox.Name == "cboSoundEffect") { oComboBox.Items.Clear(); oComboBox.Items.Add("None"); string sFileName; string sExt; string sFolderPath = "c:\\Windows\\Media\\"; foreach (string sPath in Directory.GetFiles(sFolderPath)) { sFileName = Path.GetFileName(sPath); sExt = Path.GetExtension(sPath).ToLower(); if (sExt == ".wav" || sExt == ".mid" || sExt == ".mp3") { if (Strings.InStr(sFileName, "Windows") == 0) { oComboBox.Items.Add(sFileName); } } } oComboBox.SelectedIndex = 0; } else if (oComboBox.Name == "cboTextEffect") { int i; oComboBox.Items.Clear(); for (i = 0; i <= 49; i++) oComboBox.Items.Add(Strings.Format(i, "00")); oComboBox.SelectedIndex = 29; nTextEffectIdx = 29; } } private void btnOK_Click(object sender, EventArgs e) { bool bIsDurationValid = false; try { int nNum =int.Parse(cboDuration.Text); bIsDurationValid = true; } catch (Exception) { bIsDurationValid = false; } if (bIsDurationValid & cboSoundEffect.Text != "" & cboTextEffect.Text != "") { nDuration = int.Parse(cboDuration.Text); sSoundEffect = cboSoundEffect.Text; nTextEffectIdx = int.Parse(cboTextEffect.Text); this.DialogResult = DialogResult.OK; this.Hide(); } else { string sErrMsg = ""; if (!bIsDurationValid) { sErrMsg += "Please select a valid duration" + Constants.vbCrLf; } if (cboSoundEffect.Text == "") { sErrMsg += "Please select a SoundEffect" + Constants.vbCrLf; } if (cboTextEffect.Text == "") { sErrMsg += "Please select a TextEffect" + Constants.vbCrLf; } Interaction.MsgBox(sErrMsg); } } private void cboDuration_TextChanged(object sender, EventArgs e) { int nValue; if (int.TryParse(cboDuration.Text, out nValue)) { nDuration = nValue; } } private void cboSoundEffect_SelectionChangeCommitted (object sender, EventArgs e) { if (this.cboSoundEffect.Text != "None") { Utilities.ReloadMediaFile(this.cboSoundEffect.Text); Utilities.StartPlayingMediaFile(); Interaction.MsgBox("Click to OK to stop playing sound effect"); Utilities.ReloadMediaFile(sSoundEffect); } } private void cboTextEffect_SelectedIndexChanged (object sender, EventArgs e) { int nIdx = int.Parse(cboTextEffect.Text); Image oImage = ImageList1.Images[nIdx]; picDisplay.Image = oImage; } } }
- 添加实用工具
static
类将所有实用工具函数添加到这个类中作为
static
,这样它们可以在无需声明的情况下使用。下面是
Utilities
类的一些代码片段,请参阅源代码以获取完整版本。using Microsoft.Office.Interop.PowerPoint; using Microsoft.Vbe.Interop; using Microsoft.VisualBasic; using System; using System.IO; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; namespace CountDown { public static class Utilities { public const string m_sCountDownShapeName = "CountDown"; public const string m_sCountDownInstPrjName = "CountDownAddinInstPrj"; public const string sCountDownShapeName = "CountDown"; public const string sCountDownSymbolShapeName = "CountDownSymbol"; public const string sCountDownGroupName = "grpCountDown"; public const string sCountDownInstPrjName = "CountDownAddinInstPrj"; public const string sCountDownFontName = "Amasis MT Pro Black"; public const string sCountDownSymbolFontName = "Segoe UI Emoji"; //"WingDings" [DllImport("winmm.dll", EntryPoint = "mciSendStringA")] private static extern long mciSendString (string lpstrCommand, string lpstrReturnString, long uReturnLength, long hwndCallback); private static bool bSoundIsPlaying; public static bool IsAccess2VBOMTrusted() { bool bIsTrusted; string sName; try { sName = Globals.ThisAddIn.Application. ActivePresentation.VBProject.Name; bIsTrusted = true; } catch (Exception) { bIsTrusted = false; } return bIsTrusted; } public static bool IsCountDownInstalled() { return IsComponentExist("modCountDown"); } public static bool IsProjectProtected() { bool bIsProtected = false; if (Globals.ThisAddIn.Application.VBE. ActiveVBProject.Protection == vbext_ProjectProtection.vbext_pp_locked) { bIsProtected = true; } return bIsProtected; } public static bool IsComponentExist(string sModuleName) { bool bExist = false; foreach (VBComponent oComponent in Globals.ThisAddIn. Application.VBE.ActiveVBProject.VBComponents) { if (oComponent.Name == sModuleName) { bExist = true; break; } } return bExist; } } }
- 添加
AddInUtilities
类用于 COM 接口using Microsoft.Office.Interop.PowerPoint; using System.Runtime.InteropServices; namespace CountDown { [ComVisible(true)] public interface IAddInUtilities { void ToggleSoundEx(Shape oShapeSymbol); void CountDownEx(Shape oShape); } [ComVisible(true)] [ClassInterface(ClassInterfaceType.None)] public class AddInUtilities: IAddInUtilities { public void ToggleSoundEx(Shape oShapeSymbol) { Utilities.ToggleSoundEx(oShapeSymbol); } public void CountDownEx(Shape oShape) { Utilities.CountDownEx(oShape); } } }
- 将最后一部分 COM 接口代码片段添加到
ThisAddin
类中using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Linq; using PowerPoint = Microsoft.Office.Interop.PowerPoint; using Office = Microsoft.Office.Core; namespace CountDown { public partial class ThisAddIn { private AddInUtilities utilities; protected override object RequestComAddInAutomationService() { if (utilities == null) utilities = new AddInUtilities(); return utilities; } private void ThisAddIn_Startup(object sender, System.EventArgs e) { } private void ThisAddIn_Shutdown(object sender, System.EventArgs e) { } #region VSTO generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InternalStartup() { this.Startup += new System.EventHandler(ThisAddIn_Startup); this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown); } #endregion } }
关注点
VSTO 加载项与 VBA 加载项有很大不同,除了第 1 部分的知识之外,以下是在第 2 部分中的一些额外学习内容。
- PPT 中的 VBA 调用 VSTO 加载项中的函数
“
CountDown
”是插入到 PPT 中的一个sub
,而“CountDownEx
”是在 VSTO 加载项中定义的sub
。“
ToggleSound
”是插入到 PPT 中的一个sub
,而“ToggleSoundEx
”是在 VSTO 加载项中定义的sub
。Public Sub ToggleSound(oShapeSymbol As Shape) Dim oAddIn As COMAddIn Dim oAddinUtility As Object Set oAddIn = Application.COMAddIns("CountDown") Set oAddinUtility = oAddIn.Object oAddinUtility.ToggleSoundEx oShapeSymbol Set oAddinUtility = Nothing Set oAddIn = Nothing End Sub Public Sub CountDown(oShape As Shape) Dim oAddIn As COMAddIn Dim oAddinUtility As Object Set oAddIn = Application.COMAddIns("CountDown") Set oAddinUtility = oAddIn.Object oAddinUtility.CountDownEx oShape Set oAddinUtility = Nothing Set oAddIn = Nothing End Sub
- C# 中使用的 Windows API
[DllImport("winmm.dll", EntryPoint = "mciSendStringA")] private static extern long mciSendString(string lpstrCommand, string lpstrReturnString, long uReturnLength, long hwndCallback);
CreateObject
C# 版本public static string GetBase64FromBytes(byte[] varBytes) { Type DomDocType = Type.GetTypeFromProgID("MSXML2.DomDocument"); dynamic DomDocInst = Activator.CreateInstance(DomDocType); DomDocInst.DataType = "bin.base64"; DomDocInst.nodeTypedValue = varBytes; return Strings.Replace (DomDocInst.Text, Constants.vbLf, Constants.vbCrLf); } public static byte[] GetBytesFromBase64(string varStr) { Type DomDocType = Type.GetTypeFromProgID("MSXML2.DomDocument"); dynamic DomDocInst = Activator.CreateInstance(DomDocType); dynamic Elm = DomDocInst.createElement("b64"); Elm.DataType = "bin.base64"; Elm.Text = varStr; return Elm.nodeTypedValue; }
- 从 PPT 菜单调用函数(原始函数)
// Show ComAddinsDialog private void btnComAddIns_Click(object sender, RibbonControlEventArgs e) { Globals.ThisAddIn.Application.CommandBars.ExecuteMso("ComAddInsDialog"); } // Show VisualBasic Editor private void btnVisualBasic_Click(object sender, RibbonControlEventArgs e) { Globals.ThisAddIn.Application.CommandBars.ExecuteMso("VisualBasic"); } // Show MacroSecurity Dialog Globals.ThisAddIn.Application.CommandBars.ExecuteMso("MacroSecurity");
- 要使用 VBA
MsgBox
using Microsoft.VisualBasic; Interaction.MsgBox (...)
历史
- 2022 年 10 月 11 日:初始版本