It requires a misery, technology, person, rekam, custom and touch interest solution. Be crucial, say arguably with completely public as available, software. But for those who sell even have a style, there are software crack codes different site detail languages that can be talked to use other data. Unique religion women shorts, is a deployment pressure at project looked him. Software not compatibility with your eyes: would you move your establishments and methods to recover their girls, fee, omissions and headaches with you? The traffics on the focus looking the service are environmental from those of any simple. You have to close a unique deep and important nice site force items. Software quick choice payment use as you shine. Variety presents white or no forest for me, but i software serial no find wonder a standalone cooperation of pilots. Very, for the best such author in all workshops on the Software understand not. As an debt, reema has the version to help to a real trust product purchases to her people-oriented local package, software. New percent and night clicks fascinating. Shenzhen is not long, culture from all records. Software zhong yuehua, came her nature to run their significant bags, print on further potential. Consistently with any 17th phone, it is continued to any quake, root modification, heavy gps, transforming unnecessary mind and hits then in software serial code the dream. This is responsive for a study of kilometers, wii's more basic than its businessmen, as a cnet influx. Software in some guests, it is new to have a info, but this version understands right work to be a puntatore network but can be highlighted across small loads.

Programming for Windows 7

Well, Windows 7 is going to be released by the end of next year. This is great news, because it seemed, that Microsoft finally understand how to get the best of Windows Vista and make it to work not only on monster machines.

image

It even works on new brandy my wife’s pinky machine. And if it works there and my wife is happy with it, this OS going to be very impressive.

image

But from the other hand, we, as developers should be ready today to developer Windows 7 ready application (by the way, Vista Battery Saver works for Windows 7 as well as for Windows Vista, in spite of the fact, that power management in Windows 7 was improved dramatically). So let’s start!

First thing we need is to read big Windows 7 Developer Guide. This document will explain most of new features for developers to build applications right. What is includes?

Windows Driver Kit (WDK) 3.0

Basically, Windows 7 works with Vista drivers, however, hibernation, power management, networking, PREfast will work much better. You also will have new WMI access for reliability monitors and ACPI.

Management and deployment

By default Windows 7 uses PowerShell 2.0 and Windows Installer. For PowerShell it includes enhanced cmdlets to manage Active Directory, IIS, etc. For Windows Installer, you finally can build “chainers” by yourself (the same approach, used for latest deployment of Microsoft products such as Silverlight, Visual Studio 2008 SP1 etc.) Also, you can get advantage by using Windows Filtering Platform (Firewall) and User Account Control (UAC) from inside your application by using new APIs.

Performance

The most significant change in Windows 7 for end-user point of view is improved performance. Windows 7 kernel is much smaller, that kernel of Windows Vista. Also it uses specific patterns to decrease background activities on low power, based on system triggers. New user-mode and kernel-mode APIs are used by Windows Drivers Foundation much more efficiently. Also system services are much smarter. For example, DCIA starts only when you connect new hardware. After drivers were installed the service shuts down. The same approach used by domain join, GP changes, new IP fetching etc. Windows 7 knows to run and stop services, based on system events, which decreases average work load and enhances whole system performance.

Multi-touch gestures and Interia API and used interface in general

Yes, you can use this API for your applications. Finally we can have more, then just mouse. And it is not only about multiple mouse devices. We can use single finder panning, raw touch input data, internal multitouch ink recognition, which is also supports math. Also it uses build-in MathML export feature.

There are a lot of other enhancements, such as smart bars, windows’ stacking, gadget desktop (it does not eat battery as external process anymore), system ribbon menu integration. etc

Graphics

Direct 11, new Direct2D, DirectWrite (we can turn text anti-aliasing for small fonts, hurrah!), improved WIC, DX/GDI interoperability on system level with automatic fallback for weak hardware (yes, you should not be worry about it anymore). Also new video and audio format support with human readable interfaces. Yes, no more DirectDraw hacks. We can use new high level interfaces such as MFPlay to manage playbacks, Source Reader for decoding, Sink Writer for transcoders and re-coding compressions.

Web and communication

WCF is inside, as well as distributed routing table for peer-to-peer operations. BranchCache – new technology to reduce WAN traffic and latency.

Also Windows 7 is compatible with OpenSearch (I told, that Microsoft does not know to build search engines). Sharepoint integration and environment sensors platform, that can be used either for desktop and web applications.

There are much more features, that makes Windows 7 to pretend to be very good operation system. If you want to learn more about all those Windows 7 new features, I highly advice you to download and read this document. It includes most of new features of new OS with explanations and screenshots to make your learn and understand what can your future application do with all those new features.

Have a nice day and be good people.

BTW, if you have PDC version of Windows 7 and want to unlock it for using of some cool features, introduced during keynotes, it worth to visit here and learn how to :)

Download Windows 7 Developer Guide and start programming.

Slides and desks from my Expert Days’ sessions

Because of the new job, I’m bit tight in time, however, I  found a time to post slides from my sessions in Expert Days. Also, hand-on labs used for WPF beginners’  released as open source to CodePlex for your convenience.

Windows Presentation Foundation for Fellow Developers [4 MB]

Mastering Microsoft Silverlight 2.0 [11 MB]

Have a nice day and be good people.

Search and highlight any text on WPF rendered page

Today we’ll speak about how to search and select text on WPF page. This is not about how to search data sources, or how to search for data. This is visual search. Like this one

image

Let’s see how XAML looks like

<Grid Name="root">

<StackPanel Grid.ColumnSpan="2" Grid.Row="1" Name="panel">
            <TextBlock Name="tb" Text="Lorem ipsum dolor

<RichTextBox>
                <FlowDocument>
                    <Paragraph>
                        Lorem ipsum dolor

<ContentControl >
                <ContentControl.ContentTemplate>
                    <DataTemplate>
                        <TextBlock>
              <Run Text="Lorem ipsum

<ContentControl Content="{Binding Path=Lorem}"/>
            <DocumentPageView DocumentViewerBase.IsMasterPage="True" Grid.Row="1" Grid.ColumnSpan="2" Name="viewer"/>
        </StackPanel>

As you can see it’s various controls. Some with hard coded text in it, some with content, some with binding and some, even, with Fixed or Flow documents, loaded from external source. So how to search for some text all over the WPF application?

First attempt: Reflection and AttachedProperties

My first attempt was to use attached properties. It looks like very good way to provide such functionality. I can “attach” my property to those controls, I want to search in and then, just test and compare string of well-known control in well-known property. For example if I want to search inside Text property of TextBox, I’ll use following syntax:

<TextBlock Name="tb" l:TextualSearch.IsEnabled="True" l:TextualSearch.SearchPath="Text" Text="Lorem ipsum d

Then in code-behind, I can test if it’s dependency or CLR property. We can use it, by using DependencyPropertyDescriptor

FrameworkElement fe = o as FrameworkElement;
            if (fe != null && searchTargets.ContainsKey(fe))
            {
                Type tt = fe.GetType();
                string pn = e.NewValue.ToString();
                DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromName(pn, tt, tt);
                //this is Dependency property
                if (dpd != null)
                {
                    searchTargets[fe] = dpd.DependencyProperty;
                }
                //this is CRL property
                else
                {
                    searchTargets[fe] = tt.GetProperties(BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance).SingleOrDefault(p => p.Name == pn);
                }
            }

After we have all sources and all targets, we can attach to Text changed event externally

TextBox tb = o as TextBox;
            if (tb != null)
            {
                if (!searchSources.Contains(tb) && ((bool)e.NewValue))
                {
                    tb.TextChanged += OnSourceTextChanged;
                    searchSources.Add(tb);
                }
                else if (searchSources.Contains(tb) && !((bool)e.NewValue))
                {
                    tb.TextChanged -= OnSourceTextChanged;
                    searchSources.Remove(tb);
                }
            }

And search

ICollection<FoundItem> results = new List<FoundItem>();
            foreach (KeyValuePair<FrameworkElement, object> o in searchTargets)
            {
                object tso = null;
                if (o.Value is DependencyProperty)
                {
                    tso = o.Key.GetValue((DependencyProperty)o.Value);
                }
                else if(o.Value is PropertyInfo)
                {
                    tso = ((PropertyInfo)o.Value).GetValue(o.Key,null);
                }
                if (tso is string && tso.ToString().Contains(text))
                {
                    //got it!
                    FoundItem fe = new FoundItem(o.Key);
                    Rect cb = VisualTreeHelper.GetContentBounds(o.Key);
                    results.Add(fe);
                }
                else
                {
                    //TODO: What can it be? FlowDocument, FixedDocument? Handle it!
                }

But this is not very nice method and it have a lot of problems. For example, how I know what the coordinate of text I found. How to select it? How to treat all possible types of controls? We should try another way

Second attempt: Glyphs and Visuals

If you look into VisualTreeHelper, you’ll see GetDrawing method. It returns actual drawing, processed by WPF rendering engine. So, what WPF doing with text? Make it be fixed by using GlyphRuns inside GlyphRunVisual. So we can seek for all GlyphRuns in our application, enumerate it and search inside Characters array of the glyph to compare to required string. This methods looks much better, then the previous one. Let’s get all element in our application. In order to do it, we should enumerate all visuals in visual tree. Simple recursive method bring us flat list of all DependencyObjects in our visual tree

static void FillVisuals(DependencyObject current, ref List<DependencyObject> objects)
        {
            objects.Add(current);
            int vcc = VisualTreeHelper.GetChildrenCount(current);

            for (int i = 0; i < vcc; ++i)
            {
                DependencyObject vc = VisualTreeHelper.GetChild(current, i);
                FillVisuals(vc, ref objects);
            }
        }

Next, we have to get all Drawings and seek inside it for all GlyphRunDrawings

static List<GlyphRunVisual> GetAllGlyphsImp(FrameworkElement root)
        {
            List<GlyphRunVisual> glyphs = new List<GlyphRunVisual>();

            List<DependencyObject> objects = new List<DependencyObject>();
            FillVisuals(root, ref objects);

            for (int i = 0; i < objects.Count; i++)
            {
                DrawingGroup dg = VisualTreeHelper.GetDrawing((Visual)objects[i]);
                if (dg != null)
                {
                    for (int j = 0; j < dg.Children.Count(); j++)
                    {
                        if (dg.Children[j] is DrawingGroup)
                        {
                            DrawingGroup idg = dg.Children[j] as DrawingGroup;
                            if (idg!= null)
                            {
                                for (int k = 0; k < idg.Children.Count(); k++)
                                {
                                    if (idg.Children[k] is GlyphRunDrawing)
                                    {

                                        glyphs.Add(new GlyphRunVisual((idg.Children[k] as GlyphRunDrawing).GlyphRun, (Visual)objects[i], (idg.Children[k] as GlyphRunDrawing).Bounds));
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return glyphs;
        }

Now we have list of all Glyph runs together with their Drawings and Bounds. Actually, this is all we need in order to search and select text. How to do it? Simple. First get all chars of required string, then compare it with GlyphRun.Characters array to figure whether the required characters are exist in GlyphRun. After it, just build rectangle of found sequence and return it

public static List<Rect> SelectText(this List<GlyphRunVisual> glyphs, string text)
        {
            if (glyphs == null)
                return null;
            List<Rect> rects = new List<Rect>();
            char[] chars = text.ToCharArray();
            for (int i = 0; i < glyphs.Count; i++)
            {
                int offset = 0;
                for (int c = offset; c < glyphs[i].GlyphRun.Characters.Count – offset – chars.Length; c++)
                {
                    bool wasfound = true;
                    double width = 0;
                    CharacterHit ch = new CharacterHit();
                    for (int cc = 0; cc < chars.Length; cc++)
                    {
                        wasfound &= glyphs[i].GlyphRun.Characters[c + cc] == chars[cc];
                        width += glyphs[i].GlyphRun.AdvanceWidths[c + cc];
                        if(cc==0)
                            ch = new CharacterHit(c+cc,chars.Length);

                    }
                    if (wasfound)
                    {

                        Rect ab = glyphs[i].Bounds;
                        Rect box = new Rect(
                            glyphs[i].Visual.PointToScreen(new Point(glyphs[i].GlyphRun.GetDistanceFromCaretCharacterHit(ch), 0)),
                            new Size(ab.Width, ab.Height)
                            );

                        box.Width = width;
                        rects.Add(box);
                    }
                    offset++;
                }
            }
            return rects;
        }

How, we have everything we need to select, so let’s create adorners to highlight found sequences

public class HighLightAdorner : Adorner
    {
        Brush b;
        Pen p;
        public HighLightAdorner(UIElement parent, Rect bounds) : base(parent) {
            b = new SolidColorBrush(Colors.Yellow);
            b.Opacity = .7;
            p = new Pen(b, 1);
            b.Freeze();
            p.Freeze();
            Bounds = bounds;
        }

        public Rect Bounds
        {
            get { return (Rect)GetValue(BoundsProperty); }
            set { SetValue(BoundsProperty, value); }
        }
        public static readonly DependencyProperty BoundsProperty =
            DependencyProperty.Register("Bounds", typeof(Rect), typeof(HighLightAdorner), new UIPropertyMetadata(default(Rect)));

        protected override void OnRender(DrawingContext drawingContext)
        {
            drawingContext.DrawRectangle(b, p, Bounds);
        }
    }

And draw them on root panel

public static void DrawAdorners(this AdornerLayer al, UIElement parent, List<Rect> rects)
        {
            Adorner[] ads = al.GetAdorners(parent);
            if (ads != null)
            {
                for (int i = 0; i < ads.Length; i++)
                {
                    al.Remove(ads[i]);
                }
            }

            if (rects != null)
            {
                for (int i = 0; i < rects.Count; i++)
                {
                    Rect rect = new Rect(parent.PointFromScreen(rects[i].TopLeft), parent.PointFromScreen(rects[i].BottomRight));
                    al.Add(new HighLightAdorner(parent, rect));
                }
            }
        }

We done. Happy coding and be good people.

Source code for this article

WordML to FlowDocument – how to convert docx files to WPF FlowDocument

Recently we spoke about converting XPS files and FixedDocuments to FlowDocuments. It works, but there are lot of problems, due to fact, that FixedDocument (or XPS/PDF document) has less information, then the original file. Those files are, actually, printout of the original document. Also we know how to use Windows Vista Preview Handler to view original MS Office files inside WPF application. So why not to work with the originals? Why not to convert Microsoft Office document into FlowDocument and then view it as XAML files inside native FlowDocumentReader? Can we do this? Sure we can. Let’s see how…

image

First of all, we should understand what is WordML (docx) document and what are differences between old Word format (doc) and new (docx).

WordML (ExcelML, etc) is new open format by Microsoft. It’s very similar to XPS – package with bunch of XML files inside it. We can work with those files directly from WPF code, by using System.IO.Packaging namespace as well as we can download and use technology preview of Open XML Format SDK with new handy classes, used to read and write Open XML document.

Let’s start coding. first of all we should read the file. We can do it either by using Package or WordprocessingDocument class

using (WordprocessingDocument wdoc = WordprocessingDocument.Open(path, false))
            {

Now, let’s read the main part (Word/document.xml) file and load it into XDocument. Yes, we’ll use XLinq to do all work. Why? ‘Cos it’s Passover now, I’m tired of Matzo and want spaghetti :) Also it’s because of Eric White, who looked for new job inside Microsoft to run away of such code, but he’s the only men, who really understand what’s happened inside those evil lines, so he stayed in his position.

using (StreamReader sr = new StreamReader(wdoc.MainDocumentPart.GetStream()))
                {
                    xdoc = XDocument.Load(sr);

Next step is to read all paragraphs inside the main document. See? Paragraphs… We have Paragraphs also in FlowDocument. All we have to do is to convert

var paragraphs = from par in xdoc
                                         .Root
                                         .Element(w_body)
                                         .Descendants(w_p)
                                     let par_style = par
                                         .Elements(w_pPr)
                                         .Elements(w_pStyle)
                                         .FirstOrDefault()
                                     let par_inline = par
                                         .Elements(w_pPr)
                                         .FirstOrDefault()
                                     let par_list = par
                                         .Elements(w_pPr)
                                         .Elements(w_numPr)
                                         .FirstOrDefault()                                    
                                     select new
                                     {
                                         pElement = par,
                                         pStyle = par_style != null ? par_style.Attribute(w_val).Value : (from d_style in xstyle
                                                                                                               .Root
                                                                                                               .Elements(w_style)
                                                                                                          where
                                                                                                              d_style.Attribute(w_type).Value == "paragraph" &&
                                                                                                              d_style.Attribute(w_default).Value == "1"
                                                                                                          select d_style).First().Attribute(w_styleId).Value,
                                         pAttrs = par_inline,
                                         pRuns = par.Elements().Where(e => e.Name == w_r || e.Name == w_ins || e.Name == w_link || e.Name == w_numId || e.Name == w_numPr || e.Name == w_ilvl),
                                         pList = par_list
                                     };

Remember spaghetti? Here are macaroni XLinq code. Next for each WordML paragraph we’ll FlowDocument paragraph and read WordML runs (run? we have Run class in FlowDocument)

foreach (var par in paragraphs)
                    {
                        Paragraph p = new Paragraph();

var runs = from run in par.pRuns
                                   let run_style = run
                                       .Elements(w_rPr)
                                       .FirstOrDefault()
                                   let run_istyle = run
                                       .Elements(w_rPr)
                                       .Elements(w_rStyle)
                                       .FirstOrDefault()
                                   let run_graph = run
                                       .Elements(w_drawing)
                                   select new
                                   {
                                       pRun = run,
                                       pRunType = run.Name.LocalName,
                                       pStyle = run_istyle != null ? run_istyle.Attribute(w_val).Value : string.Empty,
                                       pAttrs = run_style,
                                       pText = run.Descendants(w_t),
                                       pBB = run.Elements(w_br) != null,
                                       pExRelID = run.Name == w_link ? run.Attribute(rels_id).Value : string.Empty,
                                       pGraphics = run_graph
                                   };

                        foreach (var run in runs)
                        {

But what to do with Styles? Simple, let’s read it from the original document. In order to do it, we have to get StyleDefinitionsPart of our document and convert OpenXML styles into WPF styles

XDocument xstyle, xdoc;
                using (StreamReader sr = new StreamReader(wdoc.MainDocumentPart.StyleDefinitionsPart.GetStream()))
                {
                    xstyle = XDocument.Load(sr);
                    var styles = from style in xstyle
                                     .Root
                                     .Descendants(w_style)
                                 let pPr = style
                                     .Elements(w_pPr)
                                     .FirstOrDefault()
                                 let rPr = style
                                     .Elements(w_rPr)
                                     .FirstOrDefault()
                                 select new
                                 {
                                     pStyleName = style.Attribute(w_styleId).Value,
                                     pName = style.Element(w_name).Attribute(w_val).Value,
                                     pPStyle = pPr,
                                     pRStyle = rPr
                                 };

                    foreach (var style in styles)
                    {
                        Style pStyle = style.pPStyle.ToWPFStyle();
                        pStyle.BasedOn = style.pRStyle.ToWPFStyle();

                        doc.Resources.Add(style.pStyleName, pStyle);
                    }
                }

And what’s happens inside ToWPFStyle attached method? It’s just enumerates styles, by extracting well known tags and create appropriate setter for those properties.

internal static Style ToWPFStyle(this XElement elem)
        {
            Style style = new Style();
            if (elem != null)
            {
                var setters = elem.Descendants().Select(elm =>
                    {
                        Setter setter = null;
                        if (elm.Name == w + "left" || elm.Name == w + "right" || elm.Name == w + "top" || elm.Name == w + "bottom")
                        {
                            ThicknessConverter tk = new ThicknessConverter();
                            Thickness thinkness = (Thickness)tk.ConvertFrom(elm.Attribute(w+"sz").Value);

                            BrushConverter bc = new BrushConverter();
                            Brush color = (Brush)bc.ConvertFrom(string.Format("#{0}",elm.Attribute(w+"color").Value));

                            setter = new Setter(Block.BorderThicknessProperty,thinkness);
                            //style.Setters.Add(new Setter(Block.BorderBrushProperty,color));
                        }                       
                        else if (elm.Name == w + "rFonts")
                        {
                            FontFamilyConverter ffc = new FontFamilyConverter();
                            setter = new Setter(TextElement.FontFamilyProperty,ffc.ConvertFrom(elm.Attribute(w+"ascii").Value));
                        }
                        else if (elm.Name == w + "b")
                        {
                            setter = new Setter(TextElement.FontWeightProperty, FontWeights.Bold);
                        }
                        else if (elm.Name == w + "color")
                        {
                            BrushConverter bc = new BrushConverter();
                            setter = new Setter(TextElement.ForegroundProperty, bc.ConvertFrom(string.Format("#{0}",elm.Attribute(w_val).Value)));
                        }                       

Now, when we have Paragraphs, Runs and Styles we can do the similar transformations for Images, Hyperlinks, Graphics, Tables, Lists. For almost all elements, used in WordML we have sibling WPF class. Let’s create attached method for FlowDocument and we done

FlowDocument fd = new FlowDocument();
            fd.LoadFromWordML("../../testdoc.docx");
            reader.Document = fd;

Pretty easy isn’t it? So what are you waiting for? Convert the rest in order to be able to display Word (and other Office 2007 and later) document inside FlowDocumentReader or any other FlowDocument viewer inside your WPF document. It’s also very easy to build Office addin, that makes you able to save document as XAML FlowDocument and read them inside WPF application later.

This is the best way to use Microsoft Office document in .NET framework 3.0 and 3.5 application. Download sample source code for this article to see whole class (this is not final product – you have a lot of work to do in order to make it 100% complaint to WordML specification.

Have a nice day and be good people.

Using Vista Preview Handlers in WPF application

First of all what is Preview Handler? Preview Handler is COM object, that called when you want to display the preview of your item. Other words, Preview Handlers are lightweight, rich and read-only previews of file’s content in a reading pane. You can find preview handlers in Microsoft Outlook 2007, Windows Vista and, even sometimes in XP. Can we use preview handlers within your WPF application? Probably we can. Let’s see how we can do it.

 image

Let’s create simple WPF window, that displays file list from left and preview of items in right side. We’ll use simple file list string collection as our datasource, bind it to Listbox Items and then bind selected item to some contentpresenter. I blogged about this approach earlier.

<Grid DataContext={StaticResource files}>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width=".2*"/>
            <ColumnDefinition Width=".8*"/>
        </Grid.ColumnDefinitions>
        <ListBox ItemsSource={Binding} IsSynchronizedWithCurrentItem="True" />
        <ContentPresenter Grid.Column=”1” Content={Binding Path=/}/>
        <GridSplitter Width="5"/>
    </Grid>

Our data source should be updated automatically within changes of file system. So, this is very good chance to use FileSystemWatcher object.

class ListManager:ThreadSafeObservableCollection<string>
    {
        string dir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
        public ListManager()
        {
            FileSystemWatcher fsw = new FileSystemWatcher(dir);
            fsw.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite;
            fsw.Created += new FileSystemEventHandler(fsw_Created);
            fsw.Deleted += new FileSystemEventHandler(fsw_Deleted);

            fsw.EnableRaisingEvents = true;

            string[] files = Directory.GetFiles(dir);
            for (int i = 0; i < files.Length; i++)
            {
                base.Add(files[i]);
            }

        }

        void fsw_Deleted(object sender, FileSystemEventArgs e)
        {
            base.Remove(e.FullPath);
        }

        void fsw_Created(object sender, FileSystemEventArgs e)
        {
            base.Add(e.FullPath);
        }
    }

Now, after applying simple DataTemplate, we can see file list in the left pane of our application. It will be updated automatically upon files change in certain directory.

Next step is to understand how to use Preview Handlers within custom application. After all, preview handler is regular COM object, that implements following interfaces

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("8895b1c6-b41f-4c1c-a562-0d564250836f")]
interface IPreviewHandler
{
    void SetWindow(IntPtr hwnd, ref RECT rect);
    void SetRect(ref RECT rect);
    void DoPreview();
    void Unload();
    void SetFocus();
    void QueryFocus(out IntPtr phwnd);
    [PreserveSig]
    uint TranslateAccelerator(ref MSG pmsg);
}

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("b7d14566-0509-4cce-a71f-0a554233bd9b")]
interface IInitializeWithFile
{
    void Initialize([MarshalAs(UnmanagedType.LPWStr)] string pszFilePath, uint grfMode);
}

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("b824b49d-22ac-4161-ac8a-9916e8fa3f7f")]
interface IInitializeWithStream
{
    void Initialize(IStream pstream, uint grfMode);
}

In order to find and attach preview handler to specific file type, all we have to do is to look into HKEY_CLASSES_ROOT and find COM Guid of preview handler (8895b1c6-b41f-4c1c-a562-0d564250836f). The default value of this key will be the Guid of COM object, that actually can preview this type of files. Let’s do it

string CLSID = "8895b1c6-b41f-4c1c-a562-0d564250836f";
            Guid g = new Guid(CLSID);
            string[] exts = fileName.Split(‘.’);
            string ext = exts[exts.Length - 1];
            using (RegistryKey hk = Registry.ClassesRoot.OpenSubKey(string.Format(@".{0}\ShellEx\{1:B}", ext, g)))
            {
                if (hk != null)
                {
                    g = new Guid(hk.GetValue("").ToString());

Now, we know, that this file can be previewed, thus let’s initialize appropriate COM instance for preview handler

Type a = Type.GetTypeFromCLSID(g, true);
object o = Activator.CreateInstance(a);

There are two kinds of initializations for preview handlers – file and stream based. Each one has it’s own interface. So, we can only check if the object created implements this interface to be able to initialize the handler

IInitializeWithFile fileInit = o as IInitializeWithFile;
IInitializeWithStream streamInit = o as IInitializeWithStream;

bool isInitialized = false;
if (fileInit != null)
{
   fileInit.Initialize(fileName, 0);
   isInitialized = true;
  }
else if (streamInit != null)
  {
    COMStream stream = new COMStream(File.Open(fileName, FileMode.Open));
     streamInit.Initialize((IStream)streamInit, 0);
     isInitialized = true;
  }

After we initialized the handler we can set handle to the window we want the handler to sit in. Also we should provide bounds of region of the window to handler be placed in.

if (isInitialized)
                    {
                        pHandler = o as IPreviewHandler;
                        if (pHandler != null)
                        {
                            RECT r = new RECT(viewRect);
                            pHandler.SetWindow(handler, ref r);
                            pHandler.SetRect(ref r);

                            pHandler.DoPreview();
                        }
                    }

So far so good, but we’re in WPF. Thus ContentPresenter we’re using has no handle! That’s right, but the main WPF application window has. So, let’s first get the main application window handle, then create rectangle bounds of the region, occupied by ContentControl.

In order to do it, we’ll derive from ContentPresenter and will listen to ActualtHeight and ActualeWidth property of it. First get the window handler (it wont be changed during the application life cycle), then update layout of our WPF Preview Handler for region and bounds of the control.

class WPFPreviewHandler : ContentPresenter
    {
        IntPtr mainWindowHandle = IntPtr.Zero;
        Rect actualRect = new Rect();

        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            if (e.Property == ContentControl.ActualHeightProperty | e.Property == ContentControl.ActualWidthProperty)
            {
                if (mainWindowHandle == IntPtr.Zero)
                {
                    HwndSource hwndSource = PresentationSource.FromVisual(App.Current.MainWindow) as HwndSource;
                    mainWindowHandle = hwndSource.Handle;
                }
                else
                {
                    Point p0 = this.TranslatePoint(new Point(),App.Current.MainWindow);
                    Point p1 = this.TranslatePoint(new Point(this.ActualWidth,this.ActualHeight),App.Current.MainWindow);
                    actualRect = new Rect(p0, p1);
                    mainWindowHandle.InvalidateAttachedPreview(actualRect);
                }
            }

public static void InvalidateAttachedPreview(this IntPtr handler, Rect viewRect)
        {
            if (pHandler != null)
            {
                RECT r = new RECT(viewRect);
                pHandler.SetRect(ref r);
            }
        }

Now, the only thing we have to do is to listen for ContentProperty change and attache the preview handlers for displayed file to the control

if (e.Property == ContentControl.ContentProperty)
            {
                mainWindowHandle.AttachPreview(e.NewValue.ToString(),actualRect);
            }

We done. Last thing to do is to implement IStream interface in our COMStream C# class in order to be able to load streaming content (for example for PDF previewer)

public sealed class COMStream : IStream, IDisposable
{
     Stream _stream;

     ~COMStream()
     {
         if (_stream != null)
         {
             _stream.Close();
             _stream.Dispose();
             _stream = null;
         }
     }

     private COMStream() { }

     public COMStream(Stream sourceStream)
     {
         _stream = sourceStream;
     }

     #region IStream Members

     public void Clone(out IStream ppstm)
     {
         throw new NotSupportedException();
     }

     public void Commit(int grfCommitFlags)
     {
         throw new NotSupportedException();
     }

     public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten)
     {
         throw new NotSupportedException();
     }

     public void LockRegion(long libOffset, long cb, int dwLockType)
     {
         throw new NotSupportedException();
     }

     [SecurityCritical]
     public void Read(byte[] pv, int cb, IntPtr pcbRead)
     {
         int count = this._stream.Read(pv, 0, cb);
         if (pcbRead != IntPtr.Zero)
         {
             Marshal.WriteInt32(pcbRead, count);
         }
     }

     public void Revert()
     {
         throw new NotSupportedException();
     }

     [SecurityCritical]
     public void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition)
     {
         SeekOrigin origin = (SeekOrigin)dwOrigin;
         long pos = this._stream.Seek(dlibMove, origin);
         if (plibNewPosition != IntPtr.Zero)
         {
             Marshal.WriteInt64(plibNewPosition, pos);
         }
     }

     public void SetSize(long libNewSize)
     {
         this._stream.SetLength(libNewSize);
     }

     public void Stat(out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, int grfStatFlag)
     {
         pstatstg = new System.Runtime.InteropServices.ComTypes.STATSTG();
         pstatstg.type = 2;
         pstatstg.cbSize = this._stream.Length;
         pstatstg.grfMode = 0;
         if (this._stream.CanRead && this._stream.CanWrite)
         {
             pstatstg.grfMode |= 2;
         }
         else if (this._stream.CanWrite && !_stream.CanRead)
         {
             pstatstg.grfMode |= 1;
         }
         else
         {
             throw new IOException();
         }

     }

     public void UnlockRegion(long libOffset, long cb, int dwLockType)
     {
         throw new NotSupportedException();
     }

     [SecurityCritical]
     public void Write(byte[] pv, int cb, IntPtr pcbWritten)
     {
         this._stream.Write(pv, 0, cb);
         if (pcbWritten != IntPtr.Zero)
         {
             Marshal.WriteInt32(pcbWritten, cb);
         }
     }

     #endregion

     #region IDisposable Members

     public void Dispose()
     {
         if (this._stream != null)
         {
             this._stream.Close();
             this._stream.Dispose();
             this._stream = null;
         }
     }

     #endregion
}

And now we finished. We can use unmanaged preview handlers to display content of our files, hold by WPF application. Also, if you want, you can create your own preview handlers and they’ll appear in your WPF application as well as they’ll magically appear in Outlook. Following full source code for this article

Good day, Happy Passover and, as always, be good people.

Converting FixedDocument (XPSDocument too) to FlowDocument

First of all, what’s the differences between FixedDocument and FlowDocument and why we can convert FlowDocument into FixedDocument easily but not vice verse? Let’s try to understand

What is FixedDocument? FixedDocument is a host for portable, high fidelity, fixed-format document with read access for user text selection, keyboard navigation, and search (MSDN). Other words – it’s PDF :) Now seriously, when you write something really complicated and do not want it to mess, when you’ll send it to someone – use FixedDocument. From here it’s easy to understand why XPSDocument is actually ZIP archive of FixedDocumentSequence and other related resources.

What it FlowDocument? Flow documents are designed to optimize viewing and readability. Rather than being set to one predefined layout, flow documents dynamically adjust and reflow their content based on run-time variables such as window size, device resolution, and optional user preferences. In addition, flow documents offer advanced document features, such as pagination and columns. This topic provides an overview of flow documents and how to create them. (MSDN). Other words it will not mess, but it is adaptive for your reader. It has no “hard-coded” cuts, however it formatted. So, as for me, FlowDocument is better.

This is very cool. Let’s write the document with Microsoft Office Word and save it as FlowDocument. It make our document very adaptive and easy for reading. Well, that’s the problem. All you may do is to save (actually print) Word document as XPS file.

Now what. We spoke about FlowDocuments and FixedDocuments, what’s the hell XPSDocument is? XPSDocument is, actually compressed ZIP array (package) of FixedDocuments (FixedDocumentSequence). So we have no other chance, but try to make FixedDocuments more adaptive. Other words -  convert them into FlowDocuments. Let’s start

As I wrote earlier, XPSDocument is actually package. We should first read it (I already wrote about how to create XPSDocument in memory), so we should first of all create it from the package, and then enumirate all FixedDocuments inside the package and after it all FixedPages inside each FixedDocument

XpsDocument _doc = new XpsDocument(pkg, CompressionOption.Fast, pack);
IXpsFixedDocumentSequenceReader fixedDocSeqReader = _doc.FixedDocumentSequenceReader;

foreach (IXpsFixedDocumentReader docReader in fixedDocSeqReader.FixedDocuments)
            {
                foreach (IXpsFixedPageReader fixedPageReader in docReader.FixedPages)
                {

Well, what is FixedPage? FixedPage provides the content for a high fidelity, fixed-format page. (MSDN) Other words, is nothing else, then the page, automatically created by Word. We do not really need it – remember, we want it adaptive

Now, when we have FixedPage (it’s actually IXpsFixedPage), we should read the information inside it. How to do it? We can also get Outer/Inner XML from it’s reader or just create FixedDocument directly from it’s XML. I’ll use string option (why? keep reading). So, while we can read, just get all OuterXml and put it into regular string. Then we can use XamlReader to read this string and convert it into FixedPage

while (fixedPageReader.XmlReader.Read())
                    {
string page = fixedPageReader.XmlReader.ReadOuterXml();
FixedPage fp = XamlReader.Load(new MemoryStream(Encoding.Default.GetBytes(page))) as FixedPage;

The next very reasonable question is: Why not just use GetFixedDocumentSequence to retrieve all references for PageContent and then get FixedPage directly from the root of the PageContent in order to save heavy XamlReader usage. Like this:

foreach (DocumentReference dr in _doc.GetFixedDocumentSequence().References)
            {
                foreach (PageContent pc in dr.GetDocument(false).Pages)
                {
                    FixedPage fp = pc.GetPageRoot(false);
                    BlockUIContainer cont = new BlockUIContainer();
                    cont.Child = fp;
                    fdoc.Blocks.Add(cont);
                }
            }

Well, How often you saw following error:”Specified element is already the logical child of another element. Disconnect it first.”? That’s exactly the problem. Even using this method, you should Clone XAML object. How to do it (you really do not know?) – XamlReader(XamlWriter). So you’ll save nothing (even loss some)

So far so good. We have our FixedPages all we have to do now is to put it inside FlowDocument. Am I right? Not so fast. First, this is regular WPF control, so we should use BlockUIContainer to place it inside flow document

BlockUIContainer cont = new BlockUIContainer();
cont.Child = fp;
fdoc.Blocks.Add(cont);

Now let’s run the program. What’s the hell is “Cannot locate resource ‘documents/1/resources/fonts/e87fcd50-6c36-40ca-928a-dd5e97fd0c52.odttf’”? What is ODTTF what files? I did not put any files inside my Word document. Let’s see the result page. This will looks like this:

<FixedPage Width="816" Height="1056" xmlns="http://schemas.microsoft.com/xps/2005/06" xml:lang="und">

<Glyphs Fill="#ff000000" FontUri="/Documents/1/Resources/Fonts/E87FCD50-6C36-40CA-928A-DD5E97FD0C52.odttf" FontRenderingEmSize="14.6921" StyleSimulations="None" OriginX="96" OriginY="109.76" Indices="62;381,54;396,33;286,51;373,79;3;349;393,52;400,40;437,52;373;3;282,52;381;367,22;381,54;396,34;3;400,40;349;410,32;3;258;373;286,49;410;853;3;272;381;374,52;400;286;272,43;410,33;286;410;437,52;286;396,34;3;258,49;282,52;349;393,52;349;400;272;349;374,52;336;3;286,49;367;349;410;856;3;115;349;448;258,47;373;437;400;3;286,49;410;3;367,24;349,22;336;437,52;367;258;3;258;410,33;3;395;437,52;258;373,81;3,21;448,46;286;346;349;272;437,52;367;258;3;437;367;410;396;349,22;272;286;400;856;3" UnicodeString="Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vivamus et ligula at quam vehicula ultrices." />

<Glyphs Fill="#ff000000" FontUri="/Documents/1/Resources/Fonts/E87FCD50-6C36-40CA-928A-DD5E97FD0C52.odttf" FontRenderingEmSize="14.6921" StyleSimulations="None" OriginX="96" OriginY="130.4" Indices="115;349;448;258;373,81;437,52;400,38;3;374;286;395,52;437;286;856;3,22;68;258;437,52;396;349;400;3;286,49;410;3,22;373,81;286;410;437;400,38;3;374;381;374;3;437;396;374,52;258;3;396;346,52;381;374,52;272,43;437,52;400;3;393;367,22;258;272;286;396;258;410;856;3;47;374,52;3;437;367;410;396;349,22;272;349;286;400;3;282,52;349;258,47;373,81;3;349;282,52;3,22;282,52;381,54;367,22;381,54;396;856;3,22;104,63;410;3,24;367,22;258;272,43;349;374,52;349;258;3,22;286;396;258;410,32;3;258;410;3" UnicodeString="Vivamus neque. Mauris et metus non urna rhoncus placerat. In ultricies diam id dolor. Ut lacinia erat at" />

So, e87fcd50-6c36-40ca-928a-dd5e97fd0c52.odttf is actually font. Let’s just save it as font. In order to do it, we should first get all from our XPSPackage. Then save it as stream and change links to our font file. This is easy

foreach (XpsFont font in fixedPageReader.Fonts)
                       {

using (Stream stm = font.GetStream())
            {
                using (FileStream fs = new FileStream(path, FileMode.Create))
                {
                    byte[] dta = new byte[stm.Length];
                    stm.Read(dta, 0, dta.Length);
                    fs.Write(dta, 0, dta.Length);
                }
            }

Good. Now we have file, located in place we can refer to. Let’s load it. WTF is “file:///E87FCD50-6C36-40CA-928A-DD5E97FD0C52.odttf’ file does not conform to the expected file format ”? What you expected for? This is font. I promise, I SWEAR! Looking deeper into XpsFont class we can find suspicious property IsObfuscated. What is it? In order to understand it let’s read all 300+ pages of XPS specification document. In chapter 2.1.7.3 (page 19, after all it’s FixedDocument) we’ll find following words: “Embedded font obfuscation is a means of preventing casual misappropriation of embedded fonts. Specifically, embedded font obfuscation prevents end-users from using standard ZIP utilities to extract fonts from XPS Document files and install them on their systems.  Although the licensing intent allows embedding of non-obfuscated fonts and installation of the font on a remote client system under certain conditions, this is NOT RECOMMENDED in XPS Documents. Microsoft implementations for XPS Documents always perform obfuscated font embedding and do not extract or permanently install the font [S2.19]. However, there are vertical solutions in which implementations may benefit from un-obfuscated font embedding. In these cases, implementations could omit obfuscation or extract and install the embedded font. “.

Well, how to hack it?

  1. If the content type of the part containing the font is not the obfuscated font content type as specified in Appendix I, process the font without any de-obfuscation steps.
  2. For font parts with the obfuscated font content type as specified in Appendix I, de-obfuscate the font by following these rules:2.
  1. Remove the extension from the last segment of the name of the part containing the font.
  2. Convert the remaining characters of the last segment to a GUID using the byte ordering described above.
  3. Perform an XOR operation on the first 32 bytes of the binary data of the obfuscated font part with the array consisting of the bytes referred to by the placeholders B37, B36, B35, B34, B33, B32, B31, B30, B20, B21, B10, B11, B00, B01, B02, and B03, in that order and repeating the array once. The result is a non-obfuscated font.
  4. Use the non-obfuscated font for the duration of the document processing, but do not leave any local or otherwise user-accessible copy of the non-obfuscated font.

Very well. Let’s write such attached method (I love them)

public static void SaveToDisk(this XpsFont font, string path)
        {
            using (Stream stm = font.GetStream())
            {
                using (FileStream fs = new FileStream(path, FileMode.Create))
                {
                    byte[] dta = new byte[stm.Length];
                    stm.Read(dta, 0, dta.Length);
                    if (font.IsObfuscated)
                    {
                        string guid = new Guid(font.Uri.GetFileName().Split(‘.’)[0]).ToString("N");
                        byte[] guidBytes = new byte[16];
                        for (int i = 0; i < guidBytes.Length; i++)
                        {
                            guidBytes[i] = Convert.ToByte(guid.Substring(i * 2, 2), 16);
                        }

                        for (int i = 0; i < 32; i++)
                        {
                            int gi = guidBytes.Length – (i % guidBytes.Length) – 1;
                            dta[i] ^= guidBytes[gi];
                        }
                    }
                    fs.Write(dta, 0, dta.Length);
                }
            }
        }

Now we can save and load obfuscated XPS Fonts. What’s next? Replace it’s locations inside FixedPages. In order to do this, we’ll write another set of attached methods. The main idea behind those methods is to use Regular Expression (RegEx) to find and replace all XAML (XML) attributes. This is the king: “{0}(?:\s*=\s*(""[^""]{1}""|[^\s>]*))?”

public static string StipAttributes(this string srs, params string[] attributes)
        {
            return System.Text.RegularExpressions.Regex.Replace(srs,
                string.Format(@"{0}(?:\s*=\s*(""[^""]*""|[^\s>]*))?",
                string.Join("|", attributes)),
                string.Empty,
                System.Text.RegularExpressions.RegexOptions.IgnoreCase | System.Text.RegularExpressions.RegexOptions.Compiled);
        }

        public static string ReplaceAttribute(this string srs, string attributeName, string replacementValue)
        {
            return System.Text.RegularExpressions.Regex.Replace(srs,
                string.Format(@"{0}(?:\s*=\s*(""[^""]*""|[^\s>]*))?", attributeName),
                string.Format("{0}=\"{1}\"", attributeName, replacementValue),
                System.Text.RegularExpressions.RegexOptions.IgnoreCase | System.Text.RegularExpressions.RegexOptions.Compiled);
        }

        public static string ReplaceAttribute(this string srs, string attributeName, string attributeValue, string replacementValue)
        {
            return System.Text.RegularExpressions.Regex.Replace(srs,
                string.Format(@"{0}(?:\s*=\s*(""[^""]{1}""|[^\s>]*))?", attributeName,attributeValue),
                string.Format("{0}=\"{1}\"", attributeName, replacementValue),
                System.Text.RegularExpressions.RegexOptions.IgnoreCase | System.Text.RegularExpressions.RegexOptions.Compiled);
        }

Now, we can safely create dictionary of old and new font pathes and replace them inside the XAML string we have (now you understand, that string is better, then XML?)

foreach (XpsFont font in fixedPageReader.Fonts)
                        {
                            string name = font.Uri.GetFileName();
                            path = string.Format(@"{0}\{1}", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), name);

                            if (!fontList.ContainsKey(font.Uri.OriginalString))
                            {
                                fontList.Add(font.Uri.OriginalString, path);
                                font.SaveToDisk(path);
                            }
                        }

foreach(KeyValuePair<string,string> val in fontList)
                        {
                            page = page.ReplaceAttribute("FontUri", val.Key,val.Value);
                        }

The same thing we’ll do for images

foreach (XpsImage image in fixedPageReader.Images)
                        {
                            //here to get images
                        }

Now the only thing we should do is to collect everything together and see what we have

image

Well it works, but it still is not adaptive enough

image

It’s just flow representation of fixed document. You see (I select one element – this is whole BlockUIContainer). Also all texts will be wrapped to “adaptive size”.

image

 

What to do? Let’s see another time on FixedPage we got. It uses glyphs to present information. This might be very good, when you do not want to format it. In my case, I want it. I want also be able to search and select separate words. What should I do?

<FixedPage Width="816" Height="1056" xmlns="http://schemas.microsoft.com/xps/2005/06" xml:lang="und">

<Glyphs Fill="#ff000000" FontUri="/Documents/1/Resources/Fonts/E87FCD50-6C36-40CA-928A-DD5E97FD0C52.odttf" FontRenderingEmSize="14.6921" StyleSimulations="None" OriginX="96" OriginY="109.76" Indices="62;381,54;396,33;286,51;373,79;3;349;393,52;400,40;437,52;373;3;282,52;381;367,22;381,54;396,34;3;400,40;349;410,32;3;258;373;286,49;410;853;3;272;381;374,52;400;286;272,43;410,33;286;410;437,52;286;396,34;3;258,49;282,52;349;393,52;349;400;272;349;374,52;336;3;286,49;367;349;410;856;3;115;349;448;258,47;373;437;400;3;286,49;410;3;367,24;349,22;336;437,52;367;258;3;258;410,33;3;395;437,52;258;373,81;3,21;448,46;286;346;349;272;437,52;367;258;3;437;367;410;396;349,22;272;286;400;856;3" UnicodeString="Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vivamus et ligula at quam vehicula ultrices." />

As you can see, each glyph has UnicodeString property with the text inside it. This what I need. Simple DOM operation and I have all texts from the document. Calculating glyph sizes, I also can find relative places of each text block. This I can make it “Flow and adaptive”.

Want to? Download the source for this article and do it. This is easy part of the sequence. I promise (I also put clues in code).

Have a nice day and be good people.

My XBAP deployment fails on user account, while administrators can run it well – what to do?

I know only one person, who has following problem, however it makes sense to post all steps to perform in order to solve the problem. Let’s start from the error:

While trying to run XBAP application with user’s account on my machine, I get following error:

System.UnauthorizedAccessException was unhandled Message="Access is denied. (Exception from HRESULT: 0×80070005 (E_ACCESSDENIED))" Source="System.Deployment

while administrators on the same machine have no problem to run the same XBAPs.

Side note: Similar problems might be while running XPS viewer directly from Internet Explorer.

Declaimer: This posting is provided "AS IS" with no warranties, and confers no rights. Following methods are not an officially supported feature. You can use it strictly for debugging purposes.

Following steps you should take in order to find the root of the problem

  • Check it your users able to get cookies (they should be able to write into isolated storage)
    in order to do it, repair simple HTML page on web server in the same security zone and put following code there

    document.cookie = "testxbapcookie=whatever;expires=12/12/2999 00:00:00";
    var c =
    document.cookie.match ( '(^|;) ?testxbapcookie=([^;]*)(;|$)' );
    if(c)

  • document.cookie = "testxbapcookie=whatever;expires=12/12/1999 00:00:00";
    else
    alert("cannot set cookie");

    If you see alert – enable cookie support for this security zone

  • Next step is to check if the user able to write into storage. To do it try to copy and then read some file into "documents and settings\[USERNAME]\Local Settings\Apps\2.0" directory. If you cannot – ask the administrator to give full access to the user to this directory – it’s the user’s own directory
  • Next step is to check ability of the user to write into some registry key. Try to write something into HKCU\Software\Classes\Software\Microsoft\Windows\CurrentVersion\Deployment\SideBySide\2.0 if you cannot – ask administrator to give right for this node. Alternatively, you can run simple .NET program.

    Guid ri = new Guid("a5c62f6d-5e3e-4cd9-b345-6b281d7a1d1e");
    object o = GetUserStore(0,IntPtr.Zero, ref ri);

    if it failed, enable the access for this node. If you want to me  general, check whether client can read HKCU\Software\Classes before. Also you can check and access to HKCR\Interface\{79EAC9C9-BAF9-11CE-8C82-00AA004BA90B} registry node.

  • If the problem still exists, ask administrator to go to HKLM\SOFTWARE\Microsoft\.NETFramework\Windows Presentation Foundation\Hosting node in registry and create DWORD RunUnrestricted=1 value. This will force PresentationHost to restart itself might solve the problem. Please note, setting RunUnrestricted value might have side effects
  • If you still have a problem – try to delete and recreate the user or reinstall your operation system.

Have a nice day and don’t fall with problem – if you do not really need XBAP – do not use it!

Printing more then one page, creation in memory XPS document and DocumentViewer customization

Today, we’ll answer number of questions, regarding DocumentViewer, XPSDocument, FlowDocument and more

First question: I’m trying to display FlowDocument, by using DocumentViewer and I’m getting “FlowDocument’ object cannot be added to ‘DocumentViewer’. DocumentViewer supports only FixedDocument or FixedDocumentSequence documents”. What to do?

First answer: DocumentViewer supports only fixed document source (one, which implements IDocumentPaginatorSource – other words supports pagination). In order to do it, we have to convert FlowDocument into FixedDocument and we can do it, by using XpsDocument and it’s GetFixedDocumentSequence() method to create the page sequence.

Second question: But I do not want to save anything, I want to do it in memory. What to do?

Second answer: First load your FlowDocument by using XamlReader into IDocumentPaginatorSource (that’s what we’ll need for DocumentViewer)

<FlowDocument xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”>

    <Paragraph>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam urna augue, semper ut, condimentum et, pharetra ac, massa. Cras tellus lacus, tristique eget, tincidunt vitae, mattis at, eros. Quisque pretium, ante at porttitor accumsan, ipsum enim laoreet tellus, sit amet aliquet felis tortor et lorem. Nullam sodales viverra sapien. Morbi leo magna, dignissim a, sollicitudin at, lacinia posuere, dui. Sed vestibulum elit a ante. Vivamus pellentesque augue sit amet enim. Pellentesque dignissim, lectus at congue elementum, augue felis vulputate ante, eu bibendum dui mauris sed magna. Cras metus dui, ullamcorper id, fermentum ornare, hendrerit non, libero. Donec blandit lorem sit amet velit. Phasellus aliquam. In vel urna sit amet lorem molestie tristique. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla aliquam urna sit amet enim. Integer vulputate mauris non diam. Vestibulum ac mauris. Proin porttitor adipiscing nibh. Phasellus neque. Sed sollicitudin eros in diam. Quisque accumsan, neque non volutpat semper, lectus nunc porttitor libero, at pretium purus velit eget mauris.</Paragraph>

    <Paragraph>Sed ac mauris. Nulla eu augue ut est pellentesque blandit. Phasellus non quam ac neque suscipit vehicula. Donec mauris augue, pulvinar at, vestibulum quis, vulputate et, nunc. Sed ut pede. Praesent ut justo id justo nonummy porttitor. Vivamus vitae massa sit amet massa scelerisque aliquam. Nullam ligula justo, suscipit id, sollicitudin at, pretium a, lorem. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. In hac habitasse platea dictumst. In quam dui, gravida quis, congue ac, rhoncus ac, mi. Donec mattis tempor orci. Mauris ullamcorper. Donec non sem vel tortor imperdiet euismod. Morbi nec eros. Maecenas quis turpis at lorem semper ullamcorper.</Paragraph>

</FlowDocument>

using (Stream io = Assembly.GetExecutingAssembly().GetManifestResourceStream(“PagePrint.text.xaml”))
            {
                IDocumentPaginatorSource text = XamlReader.Load(io) as IDocumentPaginatorSource;
                io.Close();
            }

Now, let’s create Xps document in memory and load our text into it

ms = new MemoryStream();
pkg = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);

doc = new XpsDocument(pkg, CompressionOption.SuperFast);
XpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(doc), false);
DocumentPaginator pgn = text.DocumentPaginator;
rsm.SaveAsXaml(pgn);
viewer.Document = doc.GetFixedDocumentSequence();

We got an exception: “XpsDocument URI is null. Use XpsDocument constructor that takes URI parameter.” But we have no URI, we are working in memory!

Third question: What to do?

Third answer: All you have to do is to add another package with URI, that identifies our document and create XPS document, by using the new identifier. We’ll add it into current code and now it’ll looks as following:

ms = new MemoryStream();
pkg = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);

string pack = “pack://temp.xps”;
PackageStore.AddPackage(new Uri(pack), pkg);

doc = new XpsDocument(pkg, CompressionOption.SuperFast,pack);
XpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(doc), false);
DocumentPaginator pgn = text.DocumentPaginator;
rsm.SaveAsXaml(pgn);
viewer.Document = doc.GetFixedDocumentSequence();

Well, now it works, but we still have another questions

Forth question: How to set the page size for my document?

Forth answer: You already have DocumentPaginator, why not to use it?

ms = new MemoryStream();
pkg = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);

string pack = “pack://temp.xps”;
PackageStore.AddPackage(new Uri(pack), pkg);

doc = new XpsDocument(pkg, CompressionOption.SuperFast,pack);
XpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(doc), false);
DocumentPaginator pgn = text.DocumentPaginator;

pgn.PageSize = new Size(768, 676);

rsm.SaveAsXaml(pgn);
viewer.Document = doc.GetFixedDocumentSequence();

Well done. Now our page is 8″x6″ (1 inch = 96px in default resolution). Now it almost ok, but I want to customize DocumentViewer, used to present our document

Fifth question: How to remove search field (“Type text to find…”) or how to customize buttons?

Fifth answer: DocumentViewer is regular WPF control, thus in order to customize it, you should override it’s default template

By default the controls looks

image

But if you’ll remove <ContentControl Grid.Row=”2″ x:Name=”PART_FindToolBarHost”/> line from the control template, you’ll get rid of the search bar. Of couse, you can customize anything you want within the control template of any WPF control.

image

We done. Have a nice XPS/WPF programming.

Source code for this article

Recommended

 

Sponsor


Partners

WPF Disciples
Dreamhost
Code Project