Controlling DocumentViewer methods and properties using MVVM

In this post I demonstrate how the methods of a DocumentViewer class may be invoked via MVVM / WPF in order to modify the way an embedded PowerPoint presentation is displayed.

Step 1: Create a new WPF application

documentviewer1

Step 2: Create the Main Window view

Just a simple view to house the DocumentViewer control and a button with which to tell it to launch the PowerPoint presentation.

We will fill in the various command binding a bit later.

<Window x:Class="DocumentView.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DocumentView"
        mc:Ignorable="d"
        WindowState="{Binding WindowState, Mode=TwoWay}"
        Title="MainWindow" Height="350" Width="525">

    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="50" />
        </Grid.RowDefinitions>

        <DocumentViewer  
            Grid.Row="0"
            Document="{Binding FixedFixedDocumentSequence}"
            Name="DocumentViewPowerPoint"
            VerticalAlignment="Top"
            HorizontalAlignment="Left" />

        <Button 
            Grid.Row="1" 
            Command="{Binding Command}" 
            Width="70" Height="30" Content="Press" />
    </Grid>    
</Window>

Step 3: Add the event handling classes

EventArgs.cs

using System;

namespace DocumentView
{
   public class EventArgs<T> : EventArgs
   {
      public EventArgs(T value)
      {
         Value = value;
      }

      public T Value { get; private set; }
   }
}

EventRaiser.cs

using System;

namespace DocumentView
{
   public static class EventRaiser
   {
      public static void Raise(this EventHandler handler, object sender)
      {
         if (handler != null)
         {
            handler(sender, EventArgs.Empty);
         }
      }

      public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, T value)
      {
         if (handler != null)
         {
            handler(sender, new EventArgs<T>(value));
         }
      }

      public static void Raise<T>(this EventHandler<T> handler, object sender, T value) where T : EventArgs
      {
         if (handler != null)
         {
            handler(sender, value);
         }
      }

      public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, EventArgs<T> value)
      {
         if (handler != null)
         {
            handler(sender, value);
         }
      }
   }
}

RelayCommand.cs

using System;
using System.Windows.Input;

namespace DocumentView
{
   public class RelayCommand<T> : ICommand
   {
      private readonly Predicate<T> _canExecute;
      private readonly Action<T> _execute;

      public RelayCommand(Action<T> execute)
         : this(execute, null)
      {
         _execute = execute;
      }

      public RelayCommand(Action<T> execute, Predicate<T> canExecute)
      {
         if (execute == null)
         {
            throw new ArgumentNullException("execute");
         }
         _execute = execute;
         _canExecute = canExecute;
      }

      public bool CanExecute(object parameter)
      {
         return _canExecute == null || _canExecute((T)parameter);
      }

      public void Execute(object parameter)
      {
         _execute((T)parameter);
      }

      public event EventHandler CanExecuteChanged
      {
         add { CommandManager.RequerySuggested += value; }
         remove { CommandManager.RequerySuggested -= value; }
      }
   }

   public class RelayCommand : ICommand
   {
      private readonly Predicate<object> _canExecute;
      private readonly Action<object> _execute;

      public RelayCommand(Action<object> execute)
         : this(execute, null)
      {
         _execute = execute;
      }

      public RelayCommand(Action<object> execute, Predicate<object> canExecute)
      {
         if (execute == null)
         {
            throw new ArgumentNullException("execute");
         }
         _execute = execute;
         _canExecute = canExecute;
      }

      public bool CanExecute(object parameter)
      {
         return _canExecute == null || _canExecute(parameter);
      }

      public void Execute(object parameter)
      {
         _execute(parameter);
      }

      // Ensures WPF commanding infrastructure asks all RelayCommand objects whether their
      // associated views should be enabled whenever a command is invoked 
      public event EventHandler CanExecuteChanged
      {
         add
         {
            CommandManager.RequerySuggested += value;
            CanExecuteChangedInternal += value;
         }
         remove
         {
            CommandManager.RequerySuggested -= value;
            CanExecuteChangedInternal -= value;
         }
      }

      private event EventHandler CanExecuteChangedInternal;

      public void RaiseCanExecuteChanged()
      {
         CanExecuteChangedInternal.Raise(this);
      }
   }
}

Step 4: Create the ViewModel classes

MainWindowViewModel.cs

using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Xps.Packaging;
using Microsoft.Office.Core;
using Microsoft.Office.Interop.PowerPoint;
using Application = Microsoft.Office.Interop.PowerPoint.Application;
using System.Windows.Documents;
using System.Windows.Input;

namespace DocumentView
{
   public class MainWindowViewModel : ViewModelBase
   {
      private ICommand _command;
      private DocumentViewer _documentViewer;

      public DocumentViewer DocumentViewer
      {
         get
         {
            return _documentViewer;
         }
         set
         {
            _documentViewer = value;
            OnPropertyChanged("DocumentViewer");
         }
      }

      private IDocumentPaginatorSource _fixedDocumentSequence;

      public IDocumentPaginatorSource FixedFixedDocumentSequence
      {
         get
         {
            return _fixedDocumentSequence;
         }
         set
         {
            _fixedDocumentSequence = value;
            OnPropertyChanged("DocViewer");
         }
      }

      public ICommand Command
      {
         get
         {
            return _command ?? (_command = new RelayCommand(
                x =>
                {
                   DocumentViewer = MainWindow.GetInstance();

                   const string powerPointFile = @"c:\temp\ppt.pptx";
                   var xpsFile = Path.GetTempPath() + Guid.NewGuid() + ".xps";
                   var xpsDocument = ConvertPowerPointToXps(powerPointFile, xpsFile);

                   FixedFixedDocumentSequence = xpsDocument.GetFixedDocumentSequence();
                   DocumentViewer.Document = FixedFixedDocumentSequence;

                   DocumentViewer.GoToPage(1);
                   DocumentViewer.FitToMaxPagesAcross(1);
                   WindowState = WindowState.Maximized;
                   DocumentViewer.FitToMaxPagesAcross(1);
                }));
         }
      }

      private WindowState _windowState;

      public WindowState WindowState
      {
         get
         {
            return _windowState;
         }
         set
         {
            _windowState = value;
            base.OnPropertyChanged("WindowState");
         }
      }

      public MainWindowViewModel()
      {
         FixedFixedDocumentSequence = null;
         WindowState = WindowState.Maximized;
      }

      private static XpsDocument ConvertPowerPointToXps(string pptFilename, string xpsFilename)
      {
         var pptApp = new Application();

         var presentation = pptApp.Presentations.Open(pptFilename, MsoTriState.msoTrue, MsoTriState.msoFalse,
             MsoTriState.msoFalse);

         try
         {
            presentation.ExportAsFixedFormat(xpsFilename, PpFixedFormatType.ppFixedFormatTypeXPS);
         }
         catch (Exception ex)
         {
            MessageBox.Show("Failed to export to XPS format: " + ex);
         }
         finally
         {
            presentation.Close();
            pptApp.Quit();
         }

         return new XpsDocument(xpsFilename, FileAccess.Read);
      }
   }
}

ViewModelBase.cs

using System.ComponentModel;

namespace DocumentView
{
   public class ViewModelBase : INotifyPropertyChanged
   {
      public event PropertyChangedEventHandler PropertyChanged;
      protected void OnPropertyChanged(string propertyName)
      {

         var handler = PropertyChanged;
         if (handler != null)
         {
            handler(this, new PropertyChangedEventArgs(propertyName));
         }
      }
   }
}

Step 5: Add the references

Office

documentviewer2

Microsoft.Office.Interop.PowerPoint

documentviewer3

Reach Framework

documentviewer4

Step 6: Update MainWindow.xaml.cs

using System.Windows.Controls;

namespace DocumentView
{
   public partial class MainWindow
   {
      private static DocumentViewer _docViewer;

      public MainWindow()
      {
         InitializeComponent();
         _docViewer = DocumentViewPowerPoint;
      }

      public static DocumentViewer GetInstance()
      {
         return _docViewer;
      }
   }
}

Step 7: Run the code

On building and running the app we see the empty DocumentViewer control as shown:

documentviewer5

Pressing the button converts the PowerPoint into xps file format and the view property is applied so that the whole slide id displayed completely:

documentviewer6

Download sample Visual Studio 2015 project from here:

www.technical-recipes.com/Downloads/PowerPoint2.zip