Friday, June 10, 2005 1:32 AM bart

PowerPoint presentation clock plug-in

Now that I'm in the middle of a add-in development wave, let's explain how to create a presentation clock for Microsoft PowerPoint by using C#.

First, perform the same steps as two posts ago but now with PowerPoint as the add-in host application:

  1. Open Visual Studio .NET 2003 and create a new project. In the "New Project" dialog choose Other Projects, Extensibility Projects, Shared Add-in and give the project a meaningful name.
  2. In the Add-in Wizard click Next. Choose to "Create an Add-in using Visual C#" and click Next. In the next step, uncheck every checkbox in the list except for "Microsoft PowerPoint" and click Next again. Now, give a name to the plug-in and (optionally) a description. Click Next. Check both checkboxes in the next step, i.e. to load the Add-in when the host app (in this case PowerPoint) starts and to install the plug-in for everyone on the target computer. Click Next and Finish.
  3. Now you'll end up with a Connect.cs file containing the plug-in class with all the add-in related plumbing in place.
  4. Import the code from the previous blog post in this class. This includes the Win32 function imports, the struct for IPC messages and the methods VarPtr, SendMSNMessage and RemoveMSNMessage.
  5. Now it's time for the real (PowerPoint-related) work.

First, add some references:

  1. Using Add Reference, tab COM, add the Microsoft PowerPoint 11.0 Object Library.
  2. Using Add Reference, tab .NET, add a reference to the System.Windows.Forms assembly.

Open up the Connect.cs file that was created by the wizard:

  1. Start by importing some namespaces:

    using PowerPoint = Microsoft.Office.Interop.PowerPoint;
    using System.Windows.Forms;

  2. Add the following attribute

    private PowerPoint.Application powerpnt;

  3. Change the OnConnection method as follows:

    public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)
    {
         applicationObject = application;
         addInInstance = addInInst;

         powerpnt = (PowerPoint.Application) application;
         powerpnt.SlideShowBegin +=
    new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowBeginEventHandler(powerpnt_SlideShowBegin);
         powerpnt.SlideShowEnd +=
    new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowEndEventHandler(powerpnt_SlideShowEnd);
         powerpnt.SlideShowNextSlide +=
    new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowNextSlideEventHandler(powerpnt_SlideShowNextSlide);
         
        
    if(connectMode != Extensibility.ext_ConnectMode.ext_cm_Startup)
              OnStartupComplete(
    ref custom);
    }

  4. Add three other private attributes to the class:

    private Clock clock;
    private bool reappear;
    private bool silentClose;

    We will create the Clock class in a minute.

  5. Now, implement the three events for the SlideShowXYZ events:

    private void powerpnt_SlideShowBegin(Microsoft.Office.Interop.PowerPoint.SlideShowWindow Wn)
    {
         reappear =
    true;
         silentClose =
    false;
         ShowClock(Wn);
    }

    private void powerpnt_SlideShowEnd(Microsoft.Office.Interop.PowerPoint.Presentation Pres)
    {
         silentClose =
    true;
         clock.Close();
    }

    private void powerpnt_SlideShowNextSlide(Microsoft.Office.Interop.PowerPoint.SlideShowWindow Wn)
    {
         if (clock == null && reappear)
              ShowClock(Wn);
    }

  6. These methods rely on the method ShowClock to display the Clock form. The steps to create the Clock form are displayed a little further. You can do these first (if you want to rely on IntelliSense) or you can continue over here as you like.

  7. The ShowClock method looks like this:

    private void ShowClock(Microsoft.Office.Interop.PowerPoint.SlideShowWindow Wn)
    {
         clock =
    new Clock(Wn);
         clock.Show();
         clock.Closing +=
    new System.ComponentModel.CancelEventHandler(clock_Closing);
         clock.Closed +=
    new EventHandler(clock_Closed);
         Wn.Activate();
    }

    The Wn.Activate() call is used to give away the focus of the form so that the keyboard input is sent to the active slide show instead of the form.

  8. In order to ask the user whether he/she wants to permanently close the presentation clock or to have it reappear (cf. the attribute to keep track of this) on the next slide, we implement an event handler for the Closing and Closed events of the form:

    private void clock_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
         if (!silentClose)
         {
              DialogResult res = MessageBox.Show("Do you want to close the presentation clock permantently for this slide show?", "Presentation clock", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
              if (res == DialogResult.Cancel)
              {
                   e.Cancel =
    true;
                   return;
              }

              reappear = res == DialogResult.No;
         }
    }

    private void clock_Closed(object sender, EventArgs e)
    {
         clock =
    null;
    }


    silentClose is used to hide the message when the slide show ends and the clock needs to be closed always.

We're done with the Connect class. Now we need to create the Clock form which will display the time:

  1. Create a new form and call it Clock.cs.
  2. Set the Text of the form to "Presentation clock".
  3. Change the FormBorderStyle property to "FixedToolWindow".
  4. Modify the size and add a label (label1) to the form. Adjust the size of the label, change the font to size 14 and choose a ForeColor (e.g. Navy). Center the label on the form (horizontally and vertically) and set TextAlign to MiddleCenter.
  5. Add a timer (timer1) to the form and set Enabled to true. Keep the interval of 100 ms.
  6. Double-click the timer to generate the code for the Tick event and add this code to the handler:

    private void timer1_Tick(object sender, System.EventArgs e)
    {
         label1.Text = DateTime.Now.ToString("HH:mm tt");
    }

    This will display something like "12:34 AM" on the label.

  7. Now that you're in the code for the form, add the following attributes:

    private static double opacity = 0.8;
    private Microsoft.Office.Interop.PowerPoint.SlideShowWindow Wn;

  8. Change the constructor to accept a SlideShowWindow instance and copy it to the attribute:

    public Clock(Microsoft.Office.Interop.PowerPoint.SlideShowWindow Wn)
    {
         InitializeComponent();
         this.Wn = Wn;
    }

  9. Go back to the designer and doubleclick the form (be sure not to doubleclick on the the label!). This should generate an empty handler for the Load event, which you have to change as follows:

    private void Clock_Load(object sender, System.EventArgs e)
    {
         this.Opacity = opacity;
         this.Left = Screen.GetWorkingArea(this).Width - this.Width - 10;
         this.Top = 10;
    }

    This code will put the label on the right position on the screen, in the upper right-hand corner, 10 pixels away from the border. This sample code was not tested for computers with multiple monitors; note however that the class SlideShowWindow of the PowerPoint interop can be used to determine the screen of the presentation and to do better positioning of the clock on the screen.

  10. Implement some other events for the form in order to give the user the ability to adjust the opacity of the clock and to give away focus in favor for the slide show window when the mouse leaves the form. The following code shows the handlers, but you'll need to add the events through the Designer first in order to establish the connection between event and handler (use the "lightning" icon in the Properties window of the IDE to access the list of events for the selected form or control):

    private void Clock_MouseLeave(object sender, System.EventArgs e)
    {
         Wn.Activate();
    }

    private void label1_MouseLeave(object sender, System.EventArgs e)
    {
         Wn.Activate();
    }

    private
    void label1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
    {
         if (e.Button == MouseButtons.Left)
         {
              if (this.Opacity < 0.9)
                   opacity =
    this.Opacity += 0.1;
         }
        
    else
         {
             
    if (this.Opacity > 0.2)
                   opacity =
    this.Opacity -= 0.1;
         }
    }

    By using the left button of the mouse, the user can make the clock brighter. By using the right button, it can be made darker (and more transparent). Note that by using a static attribute to hold the opacity value, this value will be remembered for the next Clock instance that is created and loaded (cf. Load method code to adjust the opacity property).

Compile using CTRL-SHIFT-B and restart PowerPoint. When you start a slide show, you should see the clock popping up in the right-hand corner. You can download a compiled version over here (including an MSI installer): http://www.bartdesmet.net/download/PresentationClock.zip.

This is the result:

Happy coding!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:

Comments

# re: PowerPoint presentation clock plug-in

Monday, June 13, 2005 1:12 PM by bart

Must say, I like my clock much better... has been seen on numerous MSDN Presentations, but it soo inconspicuous that almost no-one ever noticed it.

Add this (note, it is VB code!):
Sub DrawTime(ByVal g As Graphics)
Dim clientRectangle As New Rectangle(0, 0, Me.ClientSize.Width, Me.ClientSize.Height) ' Me.Width, Me.Height)

' Calculate text to display
Dim myFont As New Font("Microsoft Sans Serif", 10.0F, System.Drawing.FontStyle.Bold)
Dim t As String = Display()
Dim stringSize As SizeF = g.MeasureString(t, myFont)
Dim y As Single = (Me.ClientRectangle.Height - stringSize.Height) / 2
Dim x As Single = (Me.ClientRectangle.Width - stringSize.Width) / 2


' Calculate the Form shape
Dim gp As GraphicsPath = New GraphicsPath
gp.FillMode = FillMode.Winding
If Me.PictureBox1.Visible = True Then
gp.AddRectangle(New Rectangle(Me.PictureBox1.Location, Me.PictureBox1.Size)) '
End If
gp.AddString(t, myFont.FontFamily, myFont.Style, myFont.Size + 4, New Rectangle(x, y, stringSize.Width, stringSize.Height), StringFormat.GenericDefault)
'gp.AddRectangle(New Rectangle(x + 2, y, stringSize.Width, stringSize.Height))
gp.AddEllipse(Me.ClientRectangle)

Me.Region = New Region(gp)
gp.Dispose()

' Fill the from background
Dim startColor As Color = System.Drawing.Color.FromArgb(248, 248, 248) ' Color.White
Dim endColor As Color = System.Drawing.Color.FromArgb(148, 148, 148) ' Color.gray
Dim myBrush As New LinearGradientBrush(Me.ClientRectangle, startColor, endColor, _
LinearGradientMode.ForwardDiagonal.Vertical)
g.FillRectangle(myBrush, clientRectangle)
myBrush.Dispose()

' Draw the text
Dim fontBrush As New LinearGradientBrush(Me.ClientRectangle, Color.White, Color.Black, _
LinearGradientMode.ForwardDiagonal.Vertical) 'New SolidBrush(Color.Black)
g.DrawString(t, myFont, Brushes.Black, x - 1, y - 1)
g.DrawString(t, myFont, Brushes.White, x + 1, y + 1)
g.DrawString(t, myFont, fontBrush, x, y)
fontBrush.Dispose()

myFont.Dispose()

End Sub


Good job explaining this little gem though!

# re: PowerPoint presentation clock plug-in

Monday, June 20, 2005 4:58 AM by bart

you also need to set TopMost=True for the form.

# re: PowerPoint presentation clock plug-in

Monday, June 20, 2005 1:49 PM by bart

Right Laurant,

I forgot to mention that property in particular although it's out there in my code (cf. download).

Thanks for the feedback!

# re: PowerPoint presentation clock plug-in

Monday, August 01, 2005 4:44 AM by bart

But I must say, Bart.

Laurent's code is much more easier to read then yours.

# re: PowerPoint presentation clock plug-in

Monday, August 01, 2005 9:17 PM by bart

Hi izzyp,

Thanks for the feedback; I appreciate people who read my blog :-). First of all, which code do you mean by "Laurent's code"? With all respect, but the only line he pointed me to over here in his comment is "TopMost=True". Maybe you mean Rudi's code which is in VB.NET? In my opinion it all comes down to coding styles/guidelines. I guess you're talking about constructs like

opacity = this.Opacity -= 0.1;
reappear = res == DialogResult.No;

I agree that these are waiting for discussions to come up :-). Additional parentheses might help although:

opacity = (this.Opacity -= 0.1);
reappear = (res == DialogResult.No);

Please keep sending me those comments as I like to hear other's opinions.