When meeting with other people at Microsoft, my computer is also
my phone. I rely on Office Communicator 2007 R2 more than many
other applications.
Family members wandering into my home office can't really tell
I'm on a call unless I happen to be talking right then. I normally
have my headphones on anyway, and I use my Samson mic for voice.
Usually my wife stands out of camera range and mimes "Are you on
the phone?". My kids just wander in regardless, often half-dressed,
which makes for some pretty funny meetings ;)
So, I thought it would be neat to create a little application
that could show "On-Air" or similar when parked on a small monitor
(think a USB MIMO display) facing towards the door. When I build my
new office in the next room, I'll actually stick this outside the
door so people can see before wandering in.
I haven't purchased the MIMO yet, as I wanted to do a little
proof-of-concept project first. The app described in this post is
that proof-of-concept.
Project Setup
Start up a new WPF application. I named mine
"PeteBrown.CommunicatorDisplay". Next, add a reference to the
communicator API.
To integrate with communicator, you'll want to download the Microsoft Office Communicator 2007 SDK. Once
installed, you can browse to the DLLs inside the Add Reference
dialog for your project.
CommunicatorAPI is the standard API which can be used to
interact with communicator, showing dialogs etc.
CommunicatorPrivate provides much of the same functionality, but
without displaying any UI. Both libraries were installed to
c:\Program Files(x86)\Microsoft Office
Communicator\SDK
Working with the API - the CommunicatorService Class
For this little app, I'll need to subscribe to an event that
will tell me when my status changes. The event, in this case is
OnMyStatusChange. To keep the code somewhat cleaner, I decided to
package up all the Communicator interaction (of which there is
little in this app), including the wiring of OnMyStatusChange, into
a single CommunicatorService class.
public enum CommunicatorStatus
{
Available,
OnPhone
}
public class CommunicatorService : IDisposable
{
private Messenger _messenger;
public CommunicatorService()
{
_messenger = new Messenger();
_messenger.OnMyStatusChange += new DMessengerEvents_OnMyStatusChangeEventHandler(OnMyStatusChange);
//TODO: Map startup status
}
private void OnMyStatusChange(int hr, MISTATUS mMyStatus)
{
System.Diagnostics.Debug.WriteLine(mMyStatus.ToString());
switch (mMyStatus)
{
case MISTATUS.MISTATUS_IN_A_CONFERENCE:
case MISTATUS.MISTATUS_IN_A_MEETING:
case MISTATUS.MISTATUS_ON_THE_PHONE:
Status = CommunicatorStatus.OnPhone;
break;
case MISTATUS.MISTATUS_ONLINE:
case MISTATUS.MISTATUS_OFFLINE:
case MISTATUS.MISTATUS_AWAY:
Status = CommunicatorStatus.Available;
break;
}
}
public event EventHandler StatusChanged;
private CommunicatorStatus _status;
public CommunicatorStatus Status
{
get { return _status; }
set
{
if (_status != value)
{
_status = value;
if (StatusChanged != null)
StatusChanged(this, EventArgs.Empty);
}
}
}
public void Dispose()
{
if (_messenger != null)
{
_messenger.OnMyStatusChange -= OnMyStatusChange;
_messenger = null;
}
}
~CommunicatorService()
{
if (_messenger != null)
Dispose();
}
}
Note that I implement IDisposable here. I wanted to clean up the
Communicator hooks. It's very likely overkill, but if I end up
doing more with this class in the future, I'll be class it's in
place.
Once I had the service set up, I create a view model class to
act as the glue between the view and the service.
The ViewModel
This application has only a single viewmodel class. That's due
to having only a single view. This class is responsible for
creating the communicator service instance, and for handling the
StatusChanged event. It then takes the information from the service
and maps it to a set of bindable properties used by the UI.
public class MainViewModel : Observable
{
private CommunicatorService _communicatorService;
public MainViewModel()
{
_communicatorService = new CommunicatorService();
_communicatorService.StatusChanged += new EventHandler(OnCommunicatorStatusChanged);
}
void OnCommunicatorStatusChanged(object sender, EventArgs e)
{
switch (_communicatorService.Status)
{
case CommunicatorStatus.Available:
StatusText = "Available";
DoNotDisturb = false;
break;
case CommunicatorStatus.OnPhone:
StatusText = "On the Phone";
DoNotDisturb = true;
break;
}
}
private string _statusText = string.Empty;
public string StatusText
{
get { return _statusText; }
set
{
if (_statusText != value)
{
_statusText = value;
NotifyPropertyChanged("StatusText");
}
}
}
private bool _doNotDisturb = false;
public bool DoNotDisturb
{
get { return _doNotDisturb; }
set
{
if (_doNotDisturb != value)
{
_doNotDisturb = value;
NotifyPropertyChanged("DoNotDisturb");
}
}
}
}
As is my usual approach, I created a simple base class named
"Observable" to handle the INotifyPropertyChanged cruft.
public abstract class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The User Interface
The UI is pretty basic. If I'm not to be disturbed, the color is
Red. If I'm available, the color is green. In both cases, the
status text is displayed in white against this background
color.
<Window x:Class="PeteBrown.CommunicatorDisplay.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=PresentationFramework"
Title="MainWindow" Height="400" Width="800">
<Window.Resources>
<controls:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<Grid>
<Rectangle Fill="DarkGreen" />
<Rectangle Fill="DarkRed"
Visibility="{Binding DoNotDisturb, Converter={StaticResource BooleanToVisibilityConverter}}" />
<TextBlock Text="{Binding StatusText}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="120"
Foreground="White"
TextWrapping="Wrap" />
</Grid>
</Window>
I handled the display of the red background using the built-in
BooleanToVisibilityConverter in WPF in a binding statement against
the DoNotDisturb property of the viewmodel. The text displayed
comes from the StatusText property of the viewmodel. The green
background is always there, but it is revealed only when the red
background is collapsed.
The Window is sized fro the typical 800x400 resolution of some
of the mini USB displays. Some other displays have different
resolutions; adjust as necessary.
The code-behind for the window is pretty brief:
public partial class MainWindow : Window
{
MainViewModel _vm;
public MainWindow()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
_vm = new MainViewModel();
DataContext = _vm;
}
}
All I do there is instantiate the viewmodel and assign it as the
DataContext for the window.
Test Run
To test, I called my mobile phone from communicator. Within a
couple seconds, the WPF application picked up and displayed the
correct status:
Once I hung up, I got the expected "Available" status. Remember,
I don't care if the status is "Away" or anything like that; this is
just to tell people whether or not it's ok to wander into the
office and ask me a question
That's all there is to it from a proof-of-concept level of
application.
Wrap-Up
The Communicator API is simple to use from a WPF application,
even without using some of the wrapper WPF presence controls.
Of course, this is a proof-of-concept. To make this more robust,
I'll want to add in code to handle disconnection and shutdown, as
well as other events. I'll also need to handle other statuses to
ensure I have all the types of meetings covered. I'd want to check
the status at startup and make sure I have that handled as well.
Finally, this whole application could be refactored to use a custom
control with visual states for each of the communicator statuses
rather than the DoNotDisturb boolean value.
Oh, and plugging in other apps would be nice. For example, if
Camtasia is running, it would be nice to automatically set this to
stay "Recording" or something like that.