Issues with XPSDocumentWriter and PrintDialog.PrintDocument - wpf

Our company is developing an application (WPF, targeted to .NET 3.5) with the WPF Diagramming Components from MindFusion.Apparently, printing and saving XPS Documents results in various errors on different systems.
I reduced the problem to a single sample XPS Document created from our application.I'll first give an overview of the concerned systems and break down the issues when respectively saving an xps document and printing the diagram visual using the new WPF Printing Path in the following list:
Note: All three systems have Windows XP SP3 with .NET 3.5 Framework SP1 installed.
Using the XpsDocumentWriter to write a XPS document with the Paginator:
PC 1 - The XPS Viewer (working with IE 7.0) doesn’t work (even after reinstall of .Net 3.5). XPS Viewer from the Essential Pack opens the document, but the view is completely blurred. But as you can see, our application on the right side of the screenshot uses a DocumentViewer to test this issue, which works correctly. Printing from the corrupted XPS Viewer results in the same output as on screen, while printing from the integrated print function in the DocumentViewer ( without intervention from our application) gives a blurry output which is a little more readable, but still not acceptable.
PC 2 - The IE XPS Viewer works correctly. The print ouput is inconsistent. Sometimes, the graphics (Shapes) are not complete, or the printing device notifies about lack of memory (with the same document).
PC 3 – The IE XPS Viewer works correctly, but initiating a print job always leads to this exception within IE itself.
Note: All heretofore mentioned issues have been tested with the XPS Document (already mentioned above) created by our application.
Creating a print job with PrintDialog.PrintDocument and the Paginator:
Printing from our application gives a consistent output with all system: the bigger the document (speaking in term of the page media size), the more blurry it gets. Unfortunately, a lot of potential causes have been already omitted.
The code for printing the document is fairly simple.
• Instead of using our own Paginator, I replaced the latter with another Paginator part of the MindFusion WPF Diagraming Components we use. I achieved the same result. (This statement is also true for XPSDocuments saved as file).
• I used the latest print driver available
• Changes to PrintTicket Resolution doesn’t seem to affect the ouput in any way
• Using another Visual instead of a diagram (like the Window of our Application itself) doesn’t affect the output
Due to these various issues, it seems that multiple causes are also probable. The previous exclusions lead me to assume that some crucial settings are missing in the PrintTicket, or something terribly wrong happens with the XPS to GDI Conversion behnd scenes. Apart from these assumptions, I'm running out of ideas.
Note: All print devices have non-XPS Drivers. HP Designjet 500, HP 2100
Last but not least, I serialised the same PrintTicket used for XPS Document file and the print job.
I would be thankful if anybody has experienced similar issues. Any suggestion is welcome.

I have currently working code but I am still having alignment issues. But print is not blurred I am giving you my code in hope that it may help you and we both could find a solution.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Documents;
using System.Windows;
using System.Windows.Media;
using System.Diagnostics;
using System.Globalization;
using System.Windows.Controls;
using System.Data;
namespace VD
{
public class CustomPaginator : DocumentPaginator
{
private int _RowsPerPage;
private Size _PageSize;
private int _Rows;
public static DataTable dtToPrint;
public CustomPaginator()
{
}
public CustomPaginator(int rows, Size pageSize, DataTable dt)
{
_Rows = rows;
PageSize = pageSize;
dtToPrint = dt;
}
public override DocumentPage GetPage(int pageNumber)
{
int currentRow = _RowsPerPage * pageNumber;
var page = new PageElement(currentRow, Math.Min(_RowsPerPage, _Rows - currentRow))
{
Width = PageSize.Width,
Height = PageSize.Height,
};
page.Measure(PageSize);
page.Arrange(new Rect(new Point(0,0), PageSize));
return new DocumentPage(page);
}
public override bool IsPageCountValid
{ get { return true; } }
public override int PageCount
{ get { return (int)Math.Ceiling(_Rows / (double)_RowsPerPage); } }
public DataTable getDtProtols
{
get
{
return dtToPrint;
}
}
public override Size PageSize
{
get { return _PageSize; }
set
{
_PageSize = value;
_RowsPerPage = PageElement.RowsPerPage(PageSize.Height);
//Can't print anything if you can't fit a row on a page
Debug.Assert(_RowsPerPage > 0);
}
}
public override IDocumentPaginatorSource Source
{ get { return null; } }
}
public class PageElement : UserControl
{
private const int PageMargin = 75;
private const int HeaderHeight = 25;
private const int LineHeight = 20;
private const int ColumnWidth = 140;
private CustomPaginator objParent = new CustomPaginator();
private DataTable ProtocolsDT = null;
private int _CurrentRow;
private int _Rows;
public PageElement(int currentRow, int rows)
{
Margin = new Thickness(PageMargin);
_CurrentRow = currentRow;
_Rows = rows;
}
private static FormattedText MakeText(string text)
{
return new FormattedText(text, CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Tahoma"), 14, Brushes.Black);
}
public static int RowsPerPage(double height)
{
return (int)Math.Floor((height - (2 * PageMargin)
- HeaderHeight) / LineHeight);
}
protected override void OnRender(DrawingContext dc)
{
ProtocolsDT = objParent.getDtProtols;
Point curPoint = new Point(0, 0);
dc.DrawText(MakeText("Host Names"),curPoint );
curPoint.X += ColumnWidth;
for (int i = 1; i < ProtocolsDT.Columns.Count; i++)
{
dc.DrawText(MakeText(ProtocolsDT.Columns[i].ToString()), curPoint);
curPoint.X += ColumnWidth;
}
curPoint.X = 0;
curPoint.Y += LineHeight;
dc.DrawRectangle(Brushes.Black, null, new Rect(curPoint, new Size(Width, 2)));
curPoint.Y += HeaderHeight - LineHeight;
Random numberGen = new Random();
//for (int i = _CurrentRow; i < _CurrentRow + _Rows; i++)
//{
// dc.DrawText(MakeText(ProtocolsDT.Rows[i][0].ToString()), curPoint);
//curPoint.X += ColumnWidth;
for (int j = 0; j < ProtocolsDT.Rows.Count; j++)
{
for (int z = 0; z < ProtocolsDT.Rows[j].ItemArray.Length; z++)
{
dc.DrawText(MakeText(ProtocolsDT.Rows[j].ItemArray[z].ToString()), curPoint);
curPoint.X += ColumnWidth;
}
curPoint.Y += LineHeight;
curPoint.X = 0;
//dc.DrawText(MakeText(ProtocolsDT.Rows[j].ItemArray[1].ToString()), curPoint);
//curPoint.X += ColumnWidth;
//dc.DrawText(MakeText(ProtocolsDT.Rows[j].ItemArray[2].ToString()), curPoint);
//curPoint.X += ColumnWidth;
//}
//curPoint.Y += LineHeight;
//curPoint.X = 0;
}
}
}
}
Another Class
private void PrintDataTable(DataTable dt)
{
var printDialog = new PrintDialog();
if (printDialog.ShowDialog() == true)
{
var paginator = new CustomPaginator(dt.Rows.Count,
new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight),dt);
try
{
printDialog.PrintDocument(paginator, "Running Protocols Report");
}
catch (Exception ex)
{
MessageBox.Show(this, "Unable to print protocol information. Please check your printer settings.", "VD", MessageBoxButton.OK, MessageBoxImage.Warning, MessageBoxResult.OK);
}
}
}

Related

How to debug layout performance problems in WPF?

I'm writing a custom control for WPF (a drawn one) and I'm having massive performance issues. The fact is that I'm drawing a lots of text and this might be a part of the problem. I timed the OnRender method however, and I'm faced with very weird results - the whole method (especially after moving to GlyphRun implemenation) takes around 2-3ms to complete. Everything looks like following (take a look at the Output window for debug timing results) (requires Flash to play):
https://www.screencast.com/t/5p6mC6rxFv0
The OnRender method doesn't have anything special in particular, it just renders some rectangles and text:
protected override void OnRender(DrawingContext drawingContext)
{
var stopwatch = Stopwatch.StartNew();
ValidateMetrics();
base.OnRender(drawingContext);
var pixelsPerDip = VisualTreeHelper.GetDpi(this).PixelsPerDip;
// Draw header
DrawHeader(drawingContext, pixelsPerDip);
// Draw margin
DrawMargin(drawingContext, pixelsPerDip);
// Draw data
DrawData(drawingContext, pixelsPerDip);
// Draw footer
DrawFooter(drawingContext);
stopwatch.Stop();
System.Diagnostics.Debug.WriteLine($"Drawing took {stopwatch.ElapsedMilliseconds}ms");
}
I ran Visual Studio's performance analysis and got the following results:
Clearly "Layout" of the editor control takes a lot of time, but still rendering is very quick.
How to debug this performance issue further? Why performance is so low despite OnRender taking milliseconds to run?
Edit - as response to comments
I didn't find a good way to time drawing, though I found, what was the cause of the problem and it turned out to be text drawing. I ended up with using very low-level text drawing mechanism, using GlyphRuns and it turned out to be fast enough to display full HD worth of text at least 30 frames per second, what was enough for me.
I'll post some relevant pieces of code below. Please note though: this is not ready-to-use solution, but should point anyone interested in the right direction.
private class GlyphRunInfo
{
public GlyphRunInfo()
{
CurrentPosition = 0;
}
public void FillMissingAdvanceWidths()
{
while (AdvanceWidths.Count < GlyphIndexes.Count)
AdvanceWidths.Add(0);
}
public List<ushort> GlyphIndexes { get; } = new List<ushort>();
public List<double> AdvanceWidths { get; } = new List<double>();
public double CurrentPosition { get; set; }
public double? StartPosition { get; set; }
}
// (...)
private void BuildTypeface()
{
typeface = new Typeface(FontFamily);
if (!typeface.TryGetGlyphTypeface(out glyphTypeface))
{
typeface = null;
glyphTypeface = null;
}
}
private void AddGlyph(char c, double position, GlyphRunInfo info)
{
if (glyphTypeface.CharacterToGlyphMap.TryGetValue(c, out ushort glyphIndex))
{
info.GlyphIndexes.Add(glyphIndex);
if (info.GlyphIndexes.Count > 1)
info.AdvanceWidths.Add(position - info.CurrentPosition);
info.CurrentPosition = position;
if (info.StartPosition == null)
info.StartPosition = info.CurrentPosition;
}
}
private void DrawGlyphRun(DrawingContext drawingContext, GlyphRunInfo regularRun, Brush brush, double y, double pixelsPerDip)
{
if (regularRun.StartPosition != null)
{
var glyphRun = new GlyphRun(glyphTypeface,
bidiLevel: 0,
isSideways: false,
renderingEmSize: FontSize,
pixelsPerDip: (float)pixelsPerDip,
glyphIndices: regularRun.GlyphIndexes,
baselineOrigin: new Point(Math.Round(regularRun.StartPosition.Value),
Math.Round(glyphTypeface.Baseline * FontSize + y)),
advanceWidths: regularRun.AdvanceWidths,
glyphOffsets: null,
characters: null,
deviceFontName: null,
clusterMap: null,
caretStops: null,
language: null);
drawingContext.DrawGlyphRun(brush, glyphRun);
}
}
And then some random code, which used the prior methods:
var regularRun = new GlyphRunInfo();
var selectionRun = new GlyphRunInfo();
// (...)
for (int ch = 0, index = line * Document.BytesPerRow; ch < charPositions.Count && index < availableBytes; ch++, index++)
{
byte drawnByte = dataBuffer[index];
char drawnChar = (drawnByte < 32 || drawnByte > 126) ? '.' : (char)drawnByte;
if (enteringMode == EnteringMode.Overwrite && (selection?.IsCharSelected(index + offset) ?? false))
AddGlyph(drawnChar, charPositions[ch].Position.TextCharX, selectionRun);
else
AddGlyph(drawnChar, charPositions[ch].Position.TextCharX, regularRun);
}
regularRun.FillMissingAdvanceWidths();
selectionRun.FillMissingAdvanceWidths();
DrawGlyphRun(drawingContext, regularRun, SystemColors.WindowTextBrush, linePositions[line].TextStartY, pixelsPerDip);
DrawGlyphRun(drawingContext, selectionRun, SystemColors.HighlightTextBrush, linePositions[line].TextStartY, pixelsPerDip);

WP7 Silverlight/XNA split

I want to make an app that has Silverlight menus but the game part of the app is XNA. I am trying to get the Silverlight/XNA split working using the code from this example game and the method of doing XNA rendering in Silverlight here. Combining these 2 tutorials I have source code that looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
using Microsoft.Phone.Controls;
using System.Windows.Navigation;
namespace FYP
{
public partial class GamePage : PhoneApplicationPage
{
GameTimer timer;
SpriteBatch spriteBatch;
Texture2D ballTexture;
IList<Ball> balls = new List<Ball>();
bool touching = false;
public GamePage(ContentManager contentManager)
{
InitializeComponent();
//base.Initialize();
// Create a timer for this page
timer = new GameTimer();
timer.UpdateInterval = TimeSpan.FromTicks(333333);
//timer.Update += OnUpdate;
//timer.Draw += OnDraw;
// TODO: use this.Content to load your game content here
ballTexture = contentManager.Load<Texture2D>("Ball");
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Set the sharing mode of the graphics device to turn on XNA rendering
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(true);
timer.Start();
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(SharedGraphicsDeviceManager.Current.GraphicsDevice);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Set the sharing mode of the graphics device to turn off XNA rendering
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(false);
// Stop the timer
timer.Stop();
}
private void OnUpdate(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
//this.Exit();
// TODO: Add your update logic here
//base.Update(gameTime);
HandleTouches();
UpdateBalls();
}
private void OnDraw(GameTime gameTime)
{
SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.White);
// TODO: Add your drawing code here
foreach (Ball ball in balls)
{
ball.Draw(spriteBatch);
}
//base.Draw(gameTime);
}
private void HandleTouches()
{
TouchCollection touches = TouchPanel.GetState();
if (!touching && touches.Count > 0)
{
touching = true;
Random random = new Random(DateTime.Now.Millisecond);
Color ballColor = new Color(random.Next(255), random.Next(255), random.Next(255));
Vector2 velocity = new Vector2((random.NextDouble() > .5 ? -1 : 1) * random.Next(9), (random.NextDouble() > .5 ? -1 : 1) * random.Next(9)) + Vector2.UnitX + Vector2.UnitY;
//Vector2 center = new Vector2((float)SharedGraphicsDeviceManager.Current.GraphicsDevice.Viewport.Width / 2, (float)SharedGraphicsDeviceManager.Current.GraphicsDevice.Height / 2);
Vector2 center = new Vector2((float)SharedGraphicsDeviceManager.Current.GraphicsDevice.Viewport.Width / 2, 200);
float radius = 25f * (float)random.NextDouble() + 5f;
balls.Add(new Ball(this, ballColor, ballTexture, center, velocity, radius));
}
else if (touches.Count == 0)
{
touching = false;
}
}
private void UpdateBalls()
{
foreach (Ball ball in balls)
{
ball.Update();
}
}
}
}
I do not understand how base works, I've had to comment out base.initialize, update and draw however base.OnNavigatedFrom works.
Also, should I be able to get my code to work in theory? I find it very complicated and although reading about XNA/Silverlight to be possible I can't find any source code where people have successfully combined XNA and Silverlight into the same app.
these videos will help you out to understand XNA and SilverLight/XNA combined platform
http://channel9.msdn.com/Series/Mango-Jump-Start/Mango-Jump-Start-11a-XNA-for-Windows-Phone--Part-1
http://channel9.msdn.com/Series/Mango-Jump-Start/Mango-Jump-Start-11b-XNA-for-Windows-Phone--Part-2
Theoretically XNA and SilverLight XNA Combined platform are pretty much the same, just a difference of bits and pieces, You can even ask XNA to render some SilverLight Control, which will make it easier to handle some button event in your game.
Hope this helps

Determine if an open WPF window is visible on any monitor

Is there a way to determine if an open WPF window is currently visible in any of the desktop's connected monitors? By visible I mean that the window's bounds rectangle intersects with the desktop rectangle of any of the monitors.
I need this functionality to determine if a window needs to be repositioned because the monitor configuration (working areas bounds, monitor count) changed between restarts of my application (which saves window positions).
I have come up with the code below and it seems to work, but it has several problems:
I need to reference windows forms.
I need the desktop's DPI settings and transform the windows forms actual pixels to WPF virtual pixels.
I need an acutal Visual instance that already has been rendered to perform the transformation.
Do you know of a solution that gets rid of some or all of the 3 issues above?
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Media;
internal static class Desktop
{
private static Size dpiFactor = new Size(1.0, 1.0);
private static bool isInitialized;
public static IEnumerable<Rect> WorkingAreas
{
get
{
return
Screen.AllScreens.Select(
screen =>
new Rect(
screen.WorkingArea.Left * dpiFactor.Width,
screen.WorkingArea.Top * dpiFactor.Height,
screen.WorkingArea.Width * dpiFactor.Width,
screen.WorkingArea.Height * dpiFactor.Height));
}
}
public static void TryInitialize(Visual visual)
{
if (isInitialized)
{
return;
}
var ps = PresentationSource.FromVisual(visual);
if (ps == null)
{
return;
}
var ct = ps.CompositionTarget;
if (ct == null)
{
return;
}
var m = ct.TransformToDevice;
dpiFactor = new Size(m.M11, m.M22);
isInitialized = true;
}
}
Usage of the (initialized) Desktop class:
private bool IsLocationValid(Rect windowRectangle)
{
foreach (var workingArea in Desktop.WorkingAreas)
{
var intersection = Rect.Intersect(windowRectangle, workingArea);
var minVisible = new Size(10.0, 10.0);
if (intersection.Width >= minVisible.Width &&
intersection.Height >= minVisible.Height)
{
return true;
}
}
return false;
}
Update
Using the virtual screen (SystemParameters.VirtualScreen*) does not work because when using multiple monitors the "desktop" is not a simple rectangle. It might be a polygon. There will be blind spots in the virtual screen because
the connected screens can have different resolutions
you can configure the position of each screen.
The code we use to do something similar uses information from SystemParameters, in particular SystemParameter.VirtualScreenLeft, Top, Width and Height.
If we have a saved Location and Size then we determine if the window is out of bounds like this:
bool outOfBounds =
(location.X <= SystemParameters.VirtualScreenLeft - size.Width) ||
(location.Y <= SystemParameters.VirtualScreenTop - size.Height) ||
(SystemParameters.VirtualScreenLeft +
SystemParameters.VirtualScreenWidth <= location.X) ||
(SystemParameters.VirtualScreenTop +
SystemParameters.VirtualScreenHeight <= location.Y);
I use this code snipped in my WPF project on startup; it's written in vb.net:
If My.Settings.RememberWindowPositionAndSize Then
If My.Settings.winMainWinState > 2 Then My.Settings.winMainWinState = WindowState.Normal
If My.Settings.winMainWinState = WindowState.Minimized Then My.Settings.winMainWinState = WindowState.Normal
Me.WindowState = My.Settings.winMainWinState
If My.Settings.winMainWinState = WindowState.Normal Then
Dim winBounds As New System.Drawing.Rectangle(CInt(My.Settings.winMainPosX), CInt(My.Settings.winMainPosY),
CInt(My.Settings.winMainSizeB), CInt(My.Settings.winMainSizeH))
For Each scr As System.Windows.Forms.Screen In System.Windows.Forms.Screen.AllScreens
If winBounds.IntersectsWith(scr.Bounds) Then
Me.Width = My.Settings.winMainSizeB
Me.Height = My.Settings.winMainSizeH
Me.Top = My.Settings.winMainPosY
Me.Left = My.Settings.winMainPosX
Exit For
End If
Next
End If
End If
and here is same (converted) code in C#
if (My.Settings.RememberWindowPositionAndSize) {
if (My.Settings.winMainWinState > 2)
My.Settings.winMainWinState = WindowState.Normal;
if (My.Settings.winMainWinState == WindowState.Minimized)
My.Settings.winMainWinState = WindowState.Normal;
this.WindowState = My.Settings.winMainWinState;
if (My.Settings.winMainWinState == WindowState.Normal) {
System.Drawing.Rectangle winBounds = new System.Drawing.Rectangle(Convert.ToInt32(My.Settings.winMainPosX), Convert.ToInt32(My.Settings.winMainPosY), Convert.ToInt32(My.Settings.winMainSizeB), Convert.ToInt32(My.Settings.winMainSizeH));
foreach (System.Windows.Forms.Screen scr in System.Windows.Forms.Screen.AllScreens) {
if (winBounds.IntersectsWith(scr.Bounds)) {
this.Width = My.Settings.winMainSizeB;
this.Height = My.Settings.winMainSizeH;
this.Top = My.Settings.winMainPosY;
this.Left = My.Settings.winMainPosX;
break;
}
}
}
}
This code checks if a window top left corner is inside the Virtual Screen box (the rectangle that includes all the available screens). It also takes care of multimonitor set ups where coordinates can be negative, for example primary monitor on the right or on the bottom.
bool valid_position =
SystemParameters.VirtualScreenLeft <= saved_location.X &&
(SystemParameters.VirtualScreenLeft + SystemParameters.VirtualScreenWidth) >= saved_location.X &&
SystemParameters.VirtualScreenTop <= saved_location.Y &&
(SystemParameters.VirtualScreenTop + SystemParameters.VirtualScreenHeight) >= saved_location.Y;

WPF WebBrowser control zoom in/out support?

For a WPF WebBrowser control, is there a way to duplicate Internet Explorer's zoom functionality?
In other words, Internet Explorer has the menu View > Zoom > 75%, which renders the web page at 75% scale. Is there a way to make a web browser control, which is embedded in a WPF app, do the same thing?
I've seen this post:
WPF WebBrowser - How to Zoom Content?
But it only seems to scale the page and not the page content.
public partial class TestWindow: UserControl
{
public TestWindow()
{
InitializeComponent();
browser.LoadCompleted += new LoadCompletedEventHandler(browser_LoadCompleted);
}
private void browser_LoadCompleted(object sender, NavigationEventArgs e)
{
try
{
FieldInfo webBrowserInfo = browser.GetType().GetField("_axIWebBrowser2", BindingFlags.Instance | BindingFlags.NonPublic);
object comWebBrowser = null;
object zoomPercent = 120;
if (webBrowserInfo != null)
comWebBrowser = webBrowserInfo.GetValue(browser);
if (comWebBrowser != null)
{
InternetExplorer ie = (InternetExplorer)comWebBrowser;
ie.ExecWB(SHDocVw.OLECMDID.OLECMDID_OPTICAL_ZOOM, SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER, ref zoomPercent, IntPtr.Zero);
}
}
catch (Exception ex)
{
}
}
public void SetBrowser(string url)
{
browser.Navigate(url,null,null,null);
}
internal void Destroy()
{
try
{
if (browser.Parent != null)
{
((Grid)browser.Parent).Children.Remove(browser);
browser.Navigate("about:blank");
browser.Dispose();
browser = null;
}
}
catch { }
}
}
Here's how I did it:
// Needed to expose the WebBrowser's underlying ActiveX control for zoom functionality
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
internal interface IServiceProvider
{
[return: MarshalAs(UnmanagedType.IUnknown)]
object QueryService(ref Guid guidService, ref Guid riid);
}
static readonly Guid SID_SWebBrowserApp = new Guid("0002DF05-0000-0000-C000-000000000046");
private void ZoomListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
object zoomPercent; // A VT_I4 percentage ranging from 10% to 1000%
switch(ZoomListBox.SelectedItem.ToString())
{
case "System.Windows.Controls.ListBoxItem: 200%":
zoomPercent = 200;
break;
case "System.Windows.Controls.ListBoxItem: 100%":
zoomPercent = 100;
break;
case "System.Windows.Controls.ListBoxItem: 50%":
zoomPercent = 50;
break;
default:
zoomPercent = 100;
break;
}
// grab a handle to the underlying ActiveX object
IServiceProvider serviceProvider = null;
if (m_webView.Document != null)
{
serviceProvider = (IServiceProvider)m_webView.Document;
}
Guid serviceGuid = SID_SWebBrowserApp;
Guid iid = typeof(SHDocVw.IWebBrowser2).GUID;
SHDocVw.IWebBrowser2 browserInst = (SHDocVw.IWebBrowser2)serviceProvider.QueryService(ref serviceGuid, ref iid);
// send the zoom command to the ActiveX object
browserInst.ExecWB(SHDocVw.OLECMDID.OLECMDID_OPTICAL_ZOOM, SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER, ref zoomPercent, IntPtr.Zero);
}
All the service provider stuff exposes the ActiveX since the WPF WebBrowser control doesn't expose it directly. Aside from that, it's pretty much the same as alexei's solution.
This is not an exact answer since it is for the WinForms control, but perhaps will be useful in case you decide to use it in a WindowsFormsHost instead of the WPF control, which exposes way too little to be useful.
You could use an OLE commands through ExecWB on the ActiveX instance: OLECMDID_ZOOM for text size and OLECMDID_OPTICAL_ZOOM for optical zoom. For example,
object pvaIn = 200; // A VT_I4 percentage ranging from 10% to 1000%
var browserInst = ((SHDocVw.IWebBrowser2)(browserContol.ActiveXInstance));
browserInst.ExecWB(SHDocVw.OLECMDID.OLECMDID_OPTICAL_ZOOM,
SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER,
ref pvaIn, IntPtr.Zero);
Some notes:
a reference to Interop.SHDocVw assembly is needed
the command succeeds only after a document has loaded
the range of pvaIn could be retrieved via OLECMDID_GETZOOMRANGE
for reference list of commands is on MSDN
I experienced this strange behavior that seemed to happen only on non-96 dpi. Upon startup, the rendered text size did not correspond to that stored in OLECMDID_ZOOM state. Setting the value (to any value) did not fix the discrepancy: the rendered size is still what looked like [stored size + 2]. When optical zoom was set to 100%, the discrepancy in text-size went away (text size visibly shrank after zooming to 100%). This did't happen in IE, and perhaps that was just a weird artifact in my environment -- but just fyi.
When using the other solutions, I always get errors of kind
HRESULT: 0x80040100
DRAGDROP_E_NOTREGISTERED
I found a solution on this page that worked for me:
var wb = webBrowser.ActiveXInstance.GetType();
object o = zoomPercentage; // Between 10 and 1000.
wb.InvokeMember(
#"ExecWB",
BindingFlags.InvokeMethod,
null,
webBrowser.ActiveXInstance,
new[]
{
OLECMDID.OLECMDID_OPTICAL_ZOOM,
OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER,
o,
o
});
With OLECMDID_OPTICAL_ZOOM being 63 and OLECMDEXECOPT_DONTPROMPTUSER being 2.

Drift when restoring window location and size in WPF

I am using the code below to save and restore the window position and size upon restart.
I am observing an upward drift of 28 pixels everytime I execute this code!
Am I reading the wrong values, or am I restoring them incorrectly? Where is the number 28 (size of the chrome?) coming from (and how would I account for it programmatically, rather than a fixed number in code)?
Here is my code:
public partial class MainStudioWindowControl : RibbonWindow
{
public MainStudioWindowControl()
{
App.MainWindowOwner = this;
this.Loaded += new System.Windows.RoutedEventHandler(MainStudioWindowControl_Loaded);
}
void MainStudioWindowControl_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
System.Windows.Window mainWindow = System.Windows.Application.Current.MainWindow;
mainWindow.WindowStartupLocation = System.Windows.WindowStartupLocation.Manual;
if (Studio.Properties.Settings.Default.Width > 0)
{
mainWindow.Left = Studio.Properties.Settings.Default.Left;
mainWindow.Top = Studio.Properties.Settings.Default.Top;
mainWindow.Width = Studio.Properties.Settings.Default.Width;
mainWindow.Height = Studio.Properties.Settings.Default.Height;
}
Debug.WriteLine(string.Format("Loading: Top = {0}", this.Top));
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base.OnClosing(e);
System.Windows.Window mainWindow = System.Windows.Application.Current.MainWindow;
Studio.Properties.Settings.Default.Left = mainWindow.Left;
Studio.Properties.Settings.Default.Top = mainWindow.Top;
Studio.Properties.Settings.Default.Width = mainWindow.Width;
Studio.Properties.Settings.Default.Height = mainWindow.Height;
Studio.Properties.Settings.Default.Save();
Debug.WriteLine(string.Format("Saving: Settings.Top = {0}", Studio.Properties.Settings.Default.Top));
}
}
Try this:
1) Derive your class from the normal Window, not the RibbonWindow - if that fixes it, it's a RibbonWindow issue.
2) Use hard-coded values to set the measurments in the Loaded handler - if that fixes it, the problem's got something to do with the settings.
With these two changes, it worked fine for me. The window appeared where it should every time.

Resources