Search code examples
c#wpfxamldocumentviewer

Notice page change in WPF Document Viewer


I have a C# Wpf project in which I have successfully loaded an Xps. file into a Document Viewer. I want to be able to have a variable in my C# code that notices a page change when you scroll the document. So far I have figured out, that there is a function for the xaml code, that automatically changes the page number if you scroll to the next page:

    <DocumentViewer x:Name="viewDocument" HorizontalAlignment="Left" VerticalAlignment="Top" Height="Auto" Grid.Row="0" Grid.Column="0" >
            <FixedDocument></FixedDocument>
        </DocumentViewer>
    <TextBlock Text="{Binding ElementName=viewDocument,Path=MasterPageNumber}" Grid.Row="1"/>

My final goal is to sto pthe time the user spends on each page which is why I need to be able to connect the current page number with a variable in my code which I cannot do with the above example. I have tried to implement an INotifyPropertyChanged, but I am fairly new to C# and I cannot find the error. it sets the variable to the first page, but after that it doesn't update.

This is my View Model:

using System; using System.ComponentModel; 
namespace Tfidf_PdfOnly {
public class MainViewModel : INotifyPropertyChanged
{

    private int _myLabel;

    public int MyLabel
    {
        get
        {
            return this._myLabel;
        }
        set
        {
            this._myLabel = value;
            NotifyPropertyChanged("MyLabel");
        }
    }


    public MainViewModel()
    {
        _myLabel = 55;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}
}

and this is in my Document_Viewer.xaml.cs file

 XpsDocument document1 = new XpsDocument(path, System.IO.FileAccess.Read);
        //load the file into the viewer
        viewDocument.Document = document1.GetFixedDocumentSequence();

        MainViewModel vm = new MainViewModel();
        this.DataContext = vm;
        vm.MyLabel = viewDocument.MasterPageNumber; 

To see if it works I bound it to a label on the UI:

<DocumentViewer x:Name="viewDocument" HorizontalAlignment="Left" VerticalAlignment="Top" Height="Auto" Grid.Row="0" Grid.Column="0" >
            <FixedDocument></FixedDocument>
        </DocumentViewer>
    <TextBlock Text="{Binding MyLabel, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" Grid.Row="1" HorizontalAlignment="Right"/>

I hope my question is clear, and any help is appreceated!


Solution

  • The DocumentViewer has a property named MasterPageNumber (which is supposed to be the page index of the document). The following sample uses Prism and the Blend SDK (behaviors). The converter is quick-and-dirty. For the timing, you could use StopWatch instances to track how long in between page changes.

    MVVM Approach

    ViewModel

    public class ShellViewModel : BindableBase
    {
        private int _currentPage;
    
        public string Title => "Sample";
    
        public string DocumentPath => @"c:\temp\temp.xps";
    
        public int CurrentPage
        {
            get => _currentPage;
            set => SetProperty(ref _currentPage, value);
        }
    
        public ICommand PageChangedCommand => new DelegateCommand<int?>(i => CurrentPage = i.GetValueOrDefault());
    }
    

    View

    <Window x:Class="Poc.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:viewModels="clr-namespace:Poc.ViewModels"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:behaviors="clr-namespace:Poc.Views.Interactivity.Behaviors"
        xmlns:converters="clr-namespace:Poc.Views.Converters"
        xmlns:controls1="clr-namespace:Poc.Views.Controls"
        mc:Ignorable="d"
        Title="{Binding Title}" Height="350" Width="525">
    <Window.Resources>
        <converters:PathToDocumentConverter x:Key="PathToDocumentConverter"></converters:PathToDocumentConverter>
    </Window.Resources>
    <Window.DataContext>
        <viewModels:ShellViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>
        <DocumentViewer Document="{Binding DocumentPath,Converter={StaticResource PathToDocumentConverter}}">
            <i:Interaction.Behaviors>
                <behaviors:DocumentViewerBehavior PageViewChangedCommand="{Binding PageChangedCommand}"></behaviors:DocumentViewerBehavior>
            </i:Interaction.Behaviors>
        </DocumentViewer>
        <TextBlock Grid.Row="1" Text="{Binding CurrentPage}"></TextBlock>
    </Grid>
    

    Behavior

    public class DocumentViewerBehavior : Behavior<DocumentViewer>
    {
        public static readonly DependencyProperty PageViewChangedCommandProperty = DependencyProperty.Register(nameof(PageViewChangedCommand), typeof(ICommand), typeof(DocumentViewerBehavior));
    
        public ICommand PageViewChangedCommand
        {
            get => (ICommand)GetValue(PageViewChangedCommandProperty);
            set => SetValue(PageViewChangedCommandProperty, value);
        }
    
        protected override void OnAttached()
        {
            base.OnAttached();
    
            AssociatedObject.PageViewsChanged += OnPageViewsChanged;
        }
    
        private void OnPageViewsChanged(object sender, EventArgs e) => PageViewChangedCommand?.Execute(AssociatedObject.MasterPageNumber);
    
        protected override void OnDetaching()
        {
            base.OnDetaching();
    
            AssociatedObject.PageViewsChanged -= OnPageViewsChanged;
        }
    }
    

    Converter

    public class PathToDocumentConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var fileInfo = new FileInfo((string)value);
    
            if (fileInfo.Exists)
            {
                if (String.Compare(fileInfo.Extension, ".XPS", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    return new XpsDocument(fileInfo.FullName, FileAccess.Read).GetFixedDocumentSequence();
                }
            }
    
            return value;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }