I'm trying to bind a DocumentViewer to a document via the ViewModel and am not succeeding at all.
Here is my view model code...
private DocumentViewer documentViewer1 = new DocumentViewer();
public DocumentViewerVM()
{
string fileName = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "Here an xps document.xps");
XpsDocument document = new XpsDocument(fileName, FileAccess.Read);
documentViewer1.Document = document.GetFixedDocumentSequence();
document.Close();
}
public DocumentViewer DocumentViewer1
{
get
{ return documentViewer1; }
set
{
documentViewer1 = value;
OnPropertyChanged("DocumentViewer1");
}
}
here is the xaml in the view...
<UserControl x:Class="DemoApp.View.DocumentViewerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid>
<DocumentViewer Name="DocumentViewer1" Document="{Binding Path=DocumentViewer1, UpdateSourceTrigger=PropertyChanged}" ></DocumentViewer>
开发者_如何转开发 </Grid>
</UserControl>
the code behind for the view contains no code other than 'InitializeComponent()'
What I do find strange is that if I place the document generation code from the view model constructor into the view constructor the document is displayed correctly, this leads me to think it is a binding issue, but where or how I know not.
You are binding the Document
property of the DocumentViewer
to a property called DocumentViewer1
which is itself a DocumentViewer
. The Document
property expects an instance of a type that implements IDocumentPaginatorSource
, such as a FixedDocument.
If you want to keep your view models pristine and avoid including PresentationCore.dll in your view model library, then use a WPF IValueConverter such as the following.
namespace Oceanside.Desktop.Wpf.Dialogs.Converters
{
using System;
using System.Globalization;
using System.IO;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Xps.Packaging;
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <inheritdoc />
/// <summary>
/// Our view models contain string paths to all XPS documents that we want to show. However,
/// the DocumentViewer.Document property must be of type IDocumentPaginatorSource which we do
/// not want to include in our view model because it will tie our view models to the
/// PresentationCore.dll. To assure all view logic and libraries are kept separate from our
/// view model, this converter to take a string path and convert it to a
/// FixedDocumentSequence which implements the IDocumentPaginatorSource interface.
/// </summary>
////////////////////////////////////////////////////////////////////////////////////////////////////
[ValueConversion(typeof(string), typeof(IDocumentPaginatorSource))]
public sealed class DocumentPaginatorSourceConverter : IValueConverter
{
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <inheritdoc />
////////////////////////////////////////////////////////////////////////////////////////////////////
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (!(value is string xpsFilePath)) return null;
var document = new XpsDocument(xpsFilePath, FileAccess.Read);
var source = document.GetFixedDocumentSequence();
document.Close();
return source;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <inheritdoc />
/// <summary>This function is not supported and will throw an exception if used.</summary>
////////////////////////////////////////////////////////////////////////////////////////////////////
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
//We have no use to convert from IDocumentPaginatorSource to a string path.
throw new NotSupportedException("Unable to convert an IDocumentPaginatorSource to a string path.");
}
}
}
The XAML below shows how to use the above converter. This example is a data template that has a view model of Type MessageBoxShowXpsDoc which has a simple string property called DocumentPath. This is passed to the converter to obtain the IDocumentPaginatorSource.
<!-- For showing xps/flow docs such as customer receipts -->
<DataTemplate
DataType="{x:Type dialogVms:MessageBoxShowXpsDoc}"
xmlns:converters="clr-namespace:Oceanside.Desktop.Wpf.Dialogs.Converters">
<DataTemplate.Resources>
<converters:DocumentPaginatorSourceConverter x:Key="DocPagConverter" />
</DataTemplate.Resources>
<DocumentViewer Document="{Binding DocumentPath,
Converter={StaticResource DocPagConverter}}" />
</DataTemplate>
Although including the full view model is outside the scope of the OP, this is an example of how I set that string path which is passed from the view model to the converter.
var viewModel = MessageBoxShowXpsDoc {DocumentPath = @"TestData\sample.xps"};
As explained already by devdigital (above), a public property of type
IDocumentPaginatorSource
is needed.
Something like this perhaps:
private IDocumentPaginatorSource _fixedDocumentSequence;
public IDocumentPaginatorSource FixedDocumentSequence
{
get { return _fixedDocumentSequence; }
set
{
if (_fixedDocumentSequence == value) return;
_fixedDocumentSequence = value;
OnPropertyChanged("FixedDocumentSequence");
}
}
And in your xaml just bind this to the DocumentViewer
Document
property:
<Grid>
<DocumentViewer
Name="DocViewer"
Document="{Binding FixedDocumentSequence}"/>
</Grid>
For people who might still have no clue how to get it done. Here is an example:
The View:
<Grid>
<DocumentViewer HorizontalAlignment="Center"
Margin="0,20,0,0"
Document="{Binding Manual}"
VerticalAlignment="Top"
Height="508" Width="766" />
</Grid>
The ViewModel:
public OperationManualViewModel()
{
var _xpsPackage = new XpsDocument(@"C:\Users\me\Desktop\EngManual.xps", FileAccess.Read);
_manual = _xpsPackage.GetFixedDocumentSequence();
}
private IDocumentPaginatorSource _manual;
public IDocumentPaginatorSource Manual
{
get { return _manual; }
set { _manual = value; NotifyOfPropertyChange(() => Manual); }
}
精彩评论