WP7 Silverlight/XNA split - silverlight

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

Related

GetWindowRect returning wrong coordinaties

i'm developing VSTO add-in for outlook which includes overlay on top of the window.
I'm building my UI using WPF.
Problem is that when i'm trying to attach WPF Window ( merge left/top/width/height ) to outlook window when STARTING at scale more than 100% GetWindowsRect Returns wrong rectangle.
BUT when i'm starting application at 100% scale then change windows scale at runtime to whatever value everything is good and DPI Aware. Both cases ( starting and runtime ) GetDpiForWindow returns correct values which is...strange. DPI Awareness is set using SetThreadDpiAwareness when forms are created.
Can't get my head what's wrong :<. Any advises appreciated.
Code for attaching:
public void AttachTo(IntPtr src, AttachFlagEnum flags)
{
var nativeRectangle = new WinAPI.RECT();
if (!WinAPI.GetWindowRect(src, ref nativeRectangle))
{
// throw new Win32Exception(Marshal.GetLastWin32Error());
return;
}
AttachToCoords(new Rectangle(nativeRectangle.Left, nativeRectangle.Top, nativeRectangle.Right - nativeRectangle.Left, nativeRectangle.Bottom - nativeRectangle.Top), flags);
}
Form create code:
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
StateManager.Init();
OutlookUtils.WaitOutlookLoading();
using (var ctx = new DPIContextBlock(WinAPI.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE))
{
new Forms.One().Show();
new Forms.Overlay().Show();
new Forms.Two();
}
}
Overlay attach code (executes by timer )
private void OverlayThink(object ob)
{
if (Managers.StateManager.OutlookState == OutlookStateEnum.MINIMIZED || Managers.StateManager.UiState == UIStateEnum.DESCWND)
{
if (this.IsVisible)
{
this.Dispatcher.Invoke(() => this.Hide());
}
return;
}
this.Dispatcher.Invoke(() => this.AttachTo(Utils.OutlookUtils.GetWordWindow(), AttachFlagEnum.OVERLAY));
this.Dispatcher.Invoke(() => this.Show());
}
My solution to the problem was that AttachToCoords method sets coords from GetWindowRect directly to Window.Left | Right | Top | Bottom. That's wrong because internally WPF positions it's elements in 96 DPI coordinate system. So i was need to convert it before assigning.
Solution:
private Rectangle TransformCoords(Rectangle coords)
{
var source = PresentationSource.FromVisual(this);
coords.X = (int)(coords.X / source.CompositionTarget.TransformToDevice.M11);
coords.Y = (int)(coords.Y / source.CompositionTarget.TransformToDevice.M22);
coords.Width = (int)(coords.Width / source.CompositionTarget.TransformToDevice.M11);
coords.Height = (int)(coords.Height / source.CompositionTarget.TransformToDevice.M22);
return coords;
}
WPF (as well as Windows forms) should be scaled automatically depending on the DPI value set on the system. There is no need to calculate the size and positions of the dialog window in Outlook add-ins.
Instead, you need to set up the form correctly to follow the DPI settings and set the window parent, so it will be displayed on top of the Outlook window.

Load PNG images to array and use as textures

I've been attempting to use PNG images as textures in Unity, when I use only one or two its easy to drag and drop them in the inspector. In my current project I have over 300 images which I am trying to load into an array, I then want to change the texture each time round the update so it appears like a video.
Here is what I have so far:
using UnityEngine;
using System.Collections;
public class ChangeImage : MonoBehaviour {
public Texture[] frames;
public int CurrentFrame;
public object[] images;
void OnMouseDown() {
if (GlobalVar.PlayClip == false){
GlobalVar.PlayClip = true;
} else {
GlobalVar.PlayClip = false;
}
}
public void Start() {
images = Resources.LoadAll("Frames");
for (int i = 0; i < images.Length; i++){
Texture2D texImage = (Texture2D) images[i];
frames[i] = texImage;
}
}
// Update is called once per frame
void Update () {
if(GlobalVar.PlayClip == true){
CurrentFrame++;
CurrentFrame %= frames.Length;
GetComponent<Renderer>().material.mainTexture = frames[CurrentFrame];
}
}
}
I have been attempting to load the images into an object array convert them to textures then output to a texture array. Does anyone know where I am going wrong with this it does not seem to give any errors but the texture is not changing?
Any advice is much appreciated
Thanks
What you are doing is kinda slow and inappropriate.
What I would recommend is to use the Animator and an animation. Have all your textures into a atlas texture, this way you will limit the draw call amount. Make that texture a sprite and use the sprite editor to cut in sub sprite.
Add an animator and create an animation. Select the whole sub sprites and drag them into the animation. Done.
Now you can easily control the speed and the playing via the animator component.
I managed to fix the problem eventually, the problem seemed to be the loading of assets was not working correctly and this was why the frame was not changing. I also changed the name of the folder containing the images from "Frames" to "Resources". Here is the completed code for anyone else who needs it:
using UnityEngine;
using System.Collections;
public class ChangeImage : MonoBehaviour {
public Texture[] frames;
public int CurrentFrame;
void OnMouseDown() {
if (GlobalVar.PlayClip == false){
GlobalVar.PlayClip = true;
} else {
GlobalVar.PlayClip = false;
}
}
public void Start() {
frames = Resources.LoadAll<Texture>("");
}
// Update is called once per frame
void Update () {
if(GlobalVar.PlayClip == true){
CurrentFrame %= frames.Length;
CurrentFrame++;
Debug.Log ("Current Frame is " + CurrentFrame);
GetComponent<Renderer>().material.mainTexture = frames[CurrentFrame];
}
}
}
Thanks for the advice on animations I will still look into it as the performance of the images is not great.

How to achieve smooth UI updates every 16 ms?

I am trying to create sort of a radar. Radar is VisualCollection that consists of 360 DrawingVisual's (which represent radar beams). Radar is placed on Viewbox.
class Radar : FrameworkElement
{
private VisualCollection visuals;
private Beam[] beams = new Beam[BEAM_POSITIONS_AMOUNT]; // all geometry calculation goes here
public Radar()
{
visuals = new VisualCollection(this);
for (int beamIndex = 0; beamIndex < BEAM_POSITIONS_AMOUNT; beamIndex++)
{
DrawingVisual dv = new DrawingVisual();
visuals.Add(dv);
using (DrawingContext dc = dv.RenderOpen())
{
dc.DrawGeometry(Brushes.Black, null, beams[beamIndex].Geometry);
}
}
DrawingVisual line = new DrawingVisual();
visuals.Add(line);
// DISCRETES_AMOUNT is about 500
this.Width = DISCRETES_AMOUNT * 2;
this.Height = DISCRETES_AMOUNT * 2;
}
public void Draw(int beamIndex, Brush brush)
{
using (DrawingContext dc = ((DrawingVisual)visuals[beamIndex]).RenderOpen())
{
dc.DrawGeometry(brush, null, beams[beamIndex].Geometry);
}
}
protected override Visual GetVisualChild(int index)
{
return visuals[index];
}
protected override int VisualChildrenCount
{
get { return visuals.Count; }
}
}
Each DrawingVisual has precalculated geometry for DrawingContext.DrawGeometry(brush, pen, geometry). Pen is null and brush is a LinearGradientBrush with about 500 GradientStops. The brush gets updated every few milliseconds, lets say 16 ms for this example. And that is what gets laggy. Here goes the overall logic.
In MainWindow() constructor I create the radar and start a background thread:
private Radar radar;
public MainWindow()
{
InitializeComponent();
radar = new Radar();
viewbox.Child = radar;
Thread t = new Thread(new ThreadStart(Run));
t.Start();
}
In Run() method there is an infinite loop, where random brush is generated, Dispatcher.Invoke() is called and a delay for 16 ms is set:
private int beamIndex = 0;
private Random r = new Random();
private const int turnsPerMinute = 20;
private static long delay = 60 / turnsPerMinute * 1000 / (360 / 2);
private long deltaDelay = delay;
public void Run()
{
int beginTime = Environment.TickCount;
while (true)
{
GradientStopCollection gsc = new GradientStopCollection(DISCRETES_AMOUNT);
for (int i = 1; i < Settings.DISCRETES_AMOUNT + 1; i++)
{
byte color = (byte)r.Next(255);
gsc.Add(new GradientStop(Color.FromArgb(255, 0, color, 0), (double)i / (double)DISCRETES_AMOUNT));
}
LinearGradientBrush lgb = new LinearGradientBrush(gsc);
lgb.StartPoint = Beam.GradientStarts[beamIndex];
lgb.EndPoint = Beam.GradientStops[beamIndex];
lgb.Freeze();
viewbox.Dispatcher.Invoke(new Action( () =>
{
radar.Draw(beamIndex, lgb);
}));
beamIndex++;
if (beamIndex >= BEAM_POSITIONS_AMOUNT)
{
beamIndex = 0;
}
while (Environment.TickCount - beginTime < delay) { }
delay += deltaDelay;
}
}
Every Invoke() call it performs one simple thing: dc.DrawGeometry(), which redraws the beam under current beamIndex. However, sometimes it seems, like before UI updates, radar.Draw() is called few times and instead of drawing 1 beam per 16 ms, it draws 2-4 beams per 32-64 ms. And it is disturbing. I really want to achieve smooth movement. I need one beam to get drawn per exact period of time. Not this random stuff. This is the list of what I have tried so far (nothing helped):
placing radar in Canvas;
using Task, BackgroundWorker, Timer, custom Microtimer.dll and setting different Thread Priorities;
using different ways of implementing delay: Environment.TickCount, DateTime.Now.Ticks, Stopwatch.ElapsedMilliseconds;
changing LinearGradientBrush to predefined SolidColorBrush;
using BeginInvoke() instead of Invoke() and changing Dispatcher Priorities;
using InvalidateVisuals() and ugly DoEvents();
using BitmapCache, WriteableBitmap and RenderTargetBitmap (using DrawingContext.DrawImage(bitmap);
working with 360 Polygon objects instead of 360 DrawingVisuals. This way I could avoid using Invoke() method. Polygon.FillProperty of each polygon was bound to ObservableCollection, and INotifyPropertyChanged was implemented. So simple line of code {brushCollection[beamIndex] = (new created and frozen brush)} led to polygon FillProperty update and UI was getting redrawn. But still no smooth movement;
probably there were few more little workarounds I could forget about.
What I did not try:
use tools to draw 3D (Viewport) to draw 2D radar;
...
So, this is it. I am begging for help.
EDIT: These lags are not about PC resources - without delay radar can do about 5 full circles per second (moving pretty fast). Most likely it is something about multithread/UI/Dispatcher or something else that I am yet to understand.
EDIT2: Attaching an .exe file so you could see what is actually going on: https://dl.dropboxusercontent.com/u/8761356/Radar.exe
EDIT3: DispatcherTimer(DispatcherPriority.Render) did not help aswell.
For smooth WPF animations you should make use of the
CompositionTarget.Rendering event.
No need for a thread or messing with the dispatcher. The event will automatically be fired before each new frame, similar to HTML's requestAnimationFrame().
In the event update your WPF scene and you're done!
There is a complete example available on MSDN.
You can check some graphics bottleneck using the WPF Performance Suite:
http://msdn.microsoft.com/es-es/library/aa969767(v=vs.110).aspx
Perforator is the tool that will show you performance issues. Maybe you are using a low performance VGA card?
while (Environment.TickCount - beginTime < delay) { }
delay += deltaDelay;
The sequence above blocks the thread. Use instead "await Task.Delay(...)" which doesn't block the thread like its counterpart Thread.Sleep(...).

WPF, app looses ability to create or manipulate GUI objects

i need some help with WPF
I'm getting started with WPF and wanted to make a simple snake game with the following classes:
GameBoard : Window
{
private Game game;
public GameBoard(){ this.game = new Game(this); }
public Rectangle rectangleFactory(int x, int y)
{
Rectangle rect = new Rectangle();
rect.Width = 10;
rect.Height = 10;
GameArea.Children.Add(rect); /// My Canvas is called GameArea
Canvas.SetLeft(rect, 10 * x);
Canvas.SetTop(rect, 10 * y);
return rect;
}
public void rectangleBin(Rectangle)
{
GameArea.Children.Remove(rect);
}
}
Thing{
private GameBoard Board;
private System.Windows.Shapes.Rectangle rect;
private int[] coordinaters;
public Thing(GameBoard board, int[] coordinates)
{
this.Board = board;
Draw();
}
public Draw(){ rect = Board.rectangleFactory(coordinates);}
public Dispose(){ Board.rectangleBin(rect)}
}
SnakeSegment : Thing
{
SnakeSegment next;
public SnakeSegment(GameBoard board, int[] coordinates) : base(board, coordinates) { }
}
Apple : Thing()
{
public Apple(GameBoard board, int[] coordinates) : base(board, coordinates) { }
public void newApple()
{
Dispose();
getNewCoordinate();
Draw();
}
}
Game
{
private GameBoard Board;
private Apple apple;
Private Snake snake;
private static System.timers.timer timer
public Game(GameBoard board)
{
this.Board = board;
this.apple = new Apple(board, int start_coordinates);
this.snake = new snake(this);
this.timer = new Timer(100);
timer.Elapsed += new ElapsedEventHandler(update_game);
}
public void update_game(object o, ElapsedEventArgs s){ snake.move_forward(); }
}
Snake
{
private SnakeSegmet Head;
Private SnakeSegment Tail
private GameBoard Board;
public Snake(Game game)
{
this.Board = game.Board;
this.Head = new SnakeSegment(Board, start_cooridantse)
this.Tail = Head;
}
private void AddSegment()
{
Head.Next = new SnakeSegment(Board, newCoordinates);
Head = Head.Next;
}
}
There is of course more but i left it out in the interest of readability.
When i start the game it draws the game area and adds an apple and an initial rectangle for the snake, so far so good
But then i update the game and things go south
my next call to SnakeSegment constructor in Head.Next = new SnakeSegment()
ends in rectangleFactory when calling new Rectangle()
the program doesn't crash, but it topples the current stack and nothing more is executed untinl the timer raises an other event
ive tried to troubleshoot and reroute the flow a bit and if i instead call Apple::newApple() the same thing happens when i get to GameArea.Children.Remove(rect);
Im completely new to WPF and this really gives me a headache, if anybody could explain why it happens id be very grateful.
Regards, Tobias
If you want to write your program like that, use WinForms. If you want to use WPF, you'll need to pretty much start again from scratch. WPF is a data-centric language... we manipulate data objects, not UI objects.
Using WPF, you'd need to think about your game objects as a collection of data objects. The board would then be the UI container control that you data bind your collection to. You would then define what each object should look like in a DataTemplate in the XAML. After binding the location properties of the data objects to the location properties of the UI objects you could then move the objects in the UI by changing the location properties of the data objects from code.
Having said all that, I think that you'll find that WPF is not a good Framework for making games. Its main strengths are its data binding capabilities and rich UI possibilities, but it's not very efficient. You'll need a good graphics card to get the most out of it.

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.

Resources