Click here to Skip to main content
15,887,027 members
Articles / Programming Languages / C#

Talking Clipboard

Rate me:
Please Sign up or sign in to vote.
4.62/5 (23 votes)
14 Jan 2008CPOL4 min read 75.7K   2.4K   52   16
An application that automatically speaks the contents of the Windows clipboard

Introduction

TalkingClipboard is a little .NET app that speaks text that has been copied to the Windows clipboard, or that is manually typed into the application. The application demonstrates how to listen for Windows clipboard events and how to use the SpeechSynthesizer class that's new in .NET 3.0. This article was inspired in part by this Lounge post. I hope you have as much fun using TalkingClipboard as I had writing it!

The Talking Clipboard

How to Use TalkingClipboard

TalkingClipboard is easy to use - just start it and copy some text to the Windows clipboard. The application displays the contents of the clipboard and starts speaking it. If you copy new text into the clipboard, TalkingClipboard will interrupt its monolog and respond with the clipboard's new contents.

To stop the current speech, click "Stop". You can also directly type into the text box and click "Speak" to have text read aloud to you. Click "Pause/Resume" to pause and resume speaking. Check the "Stop monitoring clipboard" box to temporarily prevent TalkingClipboard from grabbing the clipboard's text. To save the speech to a .WAV file, click the "Save As .WAV File..." button.

If you have more than one voice installed on your system, you can select a voice by clicking the "Voice" selector. The "Speed" trackbar allows you to select a speed between -10 and +10, zero being the default.

How It Works

There are two aspects to TalkingClipboard's operation:

  • listening to the Windows clipboard
  • the TTS (text to speech) engine
Listening to the Windows Clipboard

Listening to the Windows clipboard is done using a few Win32 APIs:

  • SetClipboardViewer()
    This API registers the application's main window with the Windows clipboard. Once registered, Windows sends our main window a WM_DRAWCLIPBOARD message when the contents of the clipboard changes.
  • ChangeClipboardChain()
    This API is used to set the clipboard viewer chain (i.e. the list of windows registered with the Windows clipboard) back to the state before our application became a viewer. This is done when our application exits.
  • We also use the ubiquitous SendMessage() API to delegate the handling of changes to the clipboard viewer chain (sent as WM_CHANGECBCHAIN notifications) to other windows.

These APIs are made available to us via .NET's interop services located in the System.Runtime.InteropServices namespace. Perhaps the most important (and commonly used) functionality provided by this namespace is the DllImport attribute that lets us define a managed equivalent of a Win32 API. Invoking a Windows platform API is usually referred to as P/Invoke (for "platform invoke"). The site www.pinvoke.net contains a large collection of predefined P/Invoke signatures. For our purposes, we use the following declarations:

C#
[DllImport("user32.dll")]
static extern IntPtr SetClipboardViewer (IntPtr hWndNewViewer);

[DllImport("user32.dll")]
static extern bool ChangeClipboardChain (IntPtr hWndRemove, IntPtr hWndNewNext);

[DllImport("user32.dll")]
public static extern int SendMessage 
	(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);

In order to process notifications sent by Windows, we override Form.WndProc() and handle the appropriate messages. The notifications that interest us are WM_DRAWCLIPBOARD and WM_CHANGECBCHAIN.

C#
const int WM_DRAWCLIPBOARD = 0x0308;
const int WM_CHANGECBCHAIN = 0x030D;

protected override void WndProc
    (ref Message m)
{
  base.WndProc (ref m);
  
  switch (m.Msg) {
    case WM_DRAWCLIPBOARD:
      ...
      break;

    case WM_CHANGECBCHAIN:
      ...
      break;
  }
}

When Windows sends us a WM_DRAWCLIPBOARD message to inform us that the contents of the clipboard have changed, we first check if the new contents are a blob of text. If so, we obtain the text and place it in the text box and request the TTS engine to speak it, as shown below:

C#
IDataObject dataObj = Clipboard.GetDataObject();
if (dataObj.GetDataPresent (DataFormats.Text)) {
    string clipboardText = dataObj.GetData (DataFormats.Text) as string;
    _editText.Text = clipboardText;
    if (_synth.State == SynthesizerState.Speaking)
        _synth.SpeakAsyncCancelAll();
    _btnSpeak.PerformClick();
}

The application receives the WM_CHANGECBCHAIN notification when the chain of clipboard listeners has changed. If the next window in the clipboard chain has changed, we keep track of the fact. For other cases of WM_CHANGECBCHAIN, we delegate the handling to the next window in the chain by forwarding it the message.

C#
if (m.WParam == _chainedWnd)
    _chainedWnd = m.LParam;
else
    SendMessage (_chainedWnd, m.Msg, m.WParam, m.LParam);
The TTS (Text to Speech) Engine

Among the new bits in .NET 3.0 is the SpeechSynthesizer class that exposes SAPI's functionality to managed code without having to resort to P/Invoke. SpeechSynthesizer is located in the System.Speech.Synthesis namespace. To speak text, we simply call Speak() or SpeakAsync() with a string containing the text to be spoken. To terminate the speech, we call SpeakAsyncCancelAll().

C#
SpeechSynthesizer ss = new SpeechSynthesizer();
ss.SpeakAsync ("Hello, world");
...
ss.SpeakAsyncCancelAll();

The speech is spoken using the object's current properties of Voice, Rate and Volume. Voice is the name of an installed voice, Rate is an integer between -10 and 10, and Volume is an integer between 0 and 100 and represents the percentage of the audio device's main volume. The list of installed voices is obtained by calling GetInstalledVoices() as shown below:

C#
ReadOnlyCollection<InstalledVoice> voices = ss.GetInstalledVoices();
if (voices.Count > 0)
    ss.Voice = voices[0];

Saving the spoken text as a .WAV file is easily done by directing the synthesizer's output to the file, speaking the text, and redirecting the output to the default audio device.

C#
// Save speech as .WAV file
ss.SetOutputToWaveFile ("C:\\hello.wav");
ss.Speak ("Hello, world");
ss.SetOutputToDefaultAudioDevice();

Conclusion

There you have it - a simple yet useful little app that was fun to write and hopefully fun to learn from.

Revision History

  • 14 Jan 2008
    • Added checkbox to suspend monitoring the clipboard
    • Added Pause/Resume speech functionality
  • 14 Jan 2008
    • Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
Canada Canada
Ravi Bhavnani is an ardent fan of Microsoft technologies who loves building Windows apps, especially PIMs, system utilities, and things that go bump on the Internet. During his career, Ravi has developed expert systems, desktop imaging apps, marketing automation software, EDA tools, a platform to help people find, analyze and understand information, trading software for institutional investors and advanced data visualization solutions. He currently works for a company that provides enterprise workforce management solutions to large clients.

His interests include the .NET framework, reasoning systems, financial analysis and algorithmic trading, NLP, HCI and UI design. Ravi holds a BS in Physics and Math and an MS in Computer Science and was a Microsoft MVP (C++ and C# in 2006 and 2007). He is also the co-inventor of 3 patents on software security and generating data visualization dashboards. His claim to fame is that he crafted CodeProject's "joke" forum post icon.

Ravi's biggest fear is that one day he might actually get a life, although the chances of that happening seem extremely remote.

Comments and Discussions

 
Generalvideo in clipboard Pin
salam ellayan2-Nov-08 23:10
salam ellayan2-Nov-08 23:10 
Generalsyntax highlighting Pin
algarnims31-Aug-08 23:08
algarnims31-Aug-08 23:08 
GeneralRe: syntax highlighting Pin
Ravi Bhavnani1-Sep-08 6:03
professionalRavi Bhavnani1-Sep-08 6:03 
GeneralRe: syntax highlighting Pin
algarnims2-Sep-08 2:49
algarnims2-Sep-08 2:49 
GeneralRe: syntax highlighting Pin
Ravi Bhavnani2-Sep-08 3:02
professionalRavi Bhavnani2-Sep-08 3:02 
GeneralRe: syntax highlighting Pin
algarnims2-Sep-08 3:00
algarnims2-Sep-08 3:00 
GeneralVery Interesting! Pin
georani28-Jan-08 3:50
georani28-Jan-08 3:50 
GeneralRe: Very Interesting! Pin
Ravi Bhavnani28-Jan-08 7:00
professionalRavi Bhavnani28-Jan-08 7:00 
Questioncan not run? Pin
tsenfuk24-Jan-08 20:44
tsenfuk24-Jan-08 20:44 
AnswerRe: can not run? Pin
Ravi Bhavnani24-Jan-08 23:59
professionalRavi Bhavnani24-Jan-08 23:59 
GeneralCool! Pin
Marc Clifton14-Jan-08 2:56
mvaMarc Clifton14-Jan-08 2:56 
GeneralRe: Cool! Pin
Ravi Bhavnani14-Jan-08 5:48
professionalRavi Bhavnani14-Jan-08 5:48 
AnswerRe: Cool! Pin
Ravi Bhavnani14-Jan-08 6:36
professionalRavi Bhavnani14-Jan-08 6:36 
GeneralRe: Cool! Pin
Bassam Abdul-Baki22-Jan-08 5:33
professionalBassam Abdul-Baki22-Jan-08 5:33 
Generalnice Stuff Pin
Abhijit Jana14-Jan-08 0:06
professionalAbhijit Jana14-Jan-08 0:06 
GeneralRe: nice Stuff Pin
Ravi Bhavnani14-Jan-08 6:37
professionalRavi Bhavnani14-Jan-08 6:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.