PETZOLD BOOK BLOG

Charles Petzold on writing books, reading books, and exercising the internal UTM


Recent Entries
< PreviousBrowse the ArchivesNext >
Subscribe to the RSS Feed

Silverlight Apps that Resize Themselves

December 17, 2009
New York, N.Y.

Yesterday I was working on a Silverlight application that adjusted its size within the browser page when I began encountering erratic behavior. Turns out I hadn't taken account of the zooming feature implemented in recent versions of Internet Explorer (and other browsers), and now I'm not sure I should need to.

In IE, you can zoom a whole page using the Ctrl key in combination with + and –, or you can twiddle the mouse wheel while holding down the Ctrl key. This zooming is independent of the Text Size feature that's been in IE for many years. The zoom percentage can also be set (or reset) in the Zoom menu item in the View menu, or by a little flip-up down in the lower right corner of the browser. The zoom affects pretty much everything — including images, video, ads, and Silverlight applications.

This blog entry is certainly not an exhaustive discussion of zooming in relation to Silverlight applications. I won't be discussing how you can turn off zooming in your Silverlight app with the enableautozoom parameter of the plug-in object in the HTML or ASPX file, or in code through the Settings object of the SilverlightHost object. Nor will I be discussing how to handle zooming behavior in your Silverlight app yourself by installing a handler for the Zoomed event of the Content object of SilverlightHost (which then disables automatic zooming), but I'll be touching briefly on getting access to the multiplicative zoom factor through that same Content object.

When you create a new Silverlight project in Visual Studio, HTML and ASPX files are created that contain an <object> tag for the Silverlight plug-in, which is the host for your application. The <object> start tag looks like this (though not formatted quite so neatly):

The width and height attributes of 100% mean that your application will be given an area equal to the current page size within the browser. Very often, this is exactly what you want when you create an HTML page that contains nothing more than a single Silverlight app. Here's an example:

ShowSizeApp.html

This particular application simply shows its size, which is the same size as the browser page. As you resize the window, the application displays an updated size.

You can also zoom that window using one of the methods I described above. (I commonly experience a redraw problem when zooming in Internet Explorer 8, but simply change the window size slightly and everything gets updated properly.) You'll see that zooming indeed affects the Silverlight application: When the zoom factor goes above 100%, the application believes that it's getting smaller because it still needs to fit within the window. Zoom out, and the application thinks that it's getting larger. But this is exactly the behavior you want for a "demo" program that adapts itself to the size of its container.

Let me show you the source code for that program. I wanted to do everything in XAML so I defined all those lines as Rectangle elements that could be stretched and aligned and rotated to look like arrows:

<UserControl x:Class="ShowSizeApp.MainPage"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid x:Name="LayoutRoot">
        <Rectangle Width="2" Fill="Blue" HorizontalAlignment="Center" />

        <Rectangle Width="2" Height="24" Fill="Blue" 
                   HorizontalAlignment="Center" VerticalAlignment="Top" 
                   RenderTransform="0.7 0.7 -0.7 0.7 0 0" />

        <Rectangle Width="2" Height="24" Fill="Blue" 
                   HorizontalAlignment="Center" VerticalAlignment="Top" 
                   RenderTransform="0.7 -0.7 0.7 0.7 0 0" />

        <Rectangle Width="2" Height="24" Fill="Blue" 
                   HorizontalAlignment="Center" VerticalAlignment="Bottom" 
                   RenderTransform="-0.7 0.7 -0.7 -0.7 3 24" />

        <Rectangle Width="2" Height="24" Fill="Blue" 
                   HorizontalAlignment="Center" VerticalAlignment="Bottom" 
                   RenderTransform="-0.7 -0.7 0.7 -0.7 3 24" />

        <Rectangle Height="2" Fill="Blue" VerticalAlignment="Center" />

        <Rectangle Height="2" Width="24" Fill="Blue" 
                   HorizontalAlignment="Left" VerticalAlignment="Center" 
                   RenderTransform="0.7 0.7 -0.7 0.7 0 0" />

        <Rectangle Height="2" Width="24" Fill="Blue" 
                   HorizontalAlignment="Left" VerticalAlignment="Center" 
                   RenderTransform="0.7 -0.7 0.7 0.7 0 0" />

        <Rectangle Height="2" Width="24" Fill="Blue" 
                   HorizontalAlignment="Right" VerticalAlignment="Center" 
                   RenderTransform="-0.7 0.7 -0.7 -0.7 24 3" />

        <Rectangle Height="2" Width="24" Fill="Blue" 
                   HorizontalAlignment="Right" VerticalAlignment="Center" 
                   RenderTransform="-0.7 -0.7 0.7 -0.7 24 3" />

        <Border BorderBrush="Blue" BorderThickness="2" Background="White"
                HorizontalAlignment="Center" VerticalAlignment="Center"
                Padding="12" CornerRadius="12">
            <TextBlock Name="txtblk" Foreground="Blue" />
        </Border>
    </Grid>
</UserControl>

For the text in the center, I wanted to set up ElementName bindings to the ActualWidth and ActualHeight properties of the UserControl. The bindings seemed to hooked up properly but they always showed values of 0. (At this point, I'm not sure I've ever persuaded an ElementName binding to work in Silverlight!) For that reason I had to set the text from code:

using System;
using System.Windows;
using System.Windows.Controls;

namespace ShowSizeApp
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            SizeChanged += OnPageSizeChanged;
        }

        void OnPageSizeChanged(object sender, SizeChangedEventArgs args)
        {
            txtblk.Text = String.Format("{0:F1} × {1:F1}", args.NewSize.Width,
                                                           args.NewSize.Height);
        }
    }
}

Using width and height attributes of 100% is ideal when you want a Silverlight application to live on a page by itself and assume the entire dimensions of the browser page. It does not make sense when the application is surrounded by HTML, for example, embedded in a blog entry. In those cases, you want to give the <object> tag a specific pixel width and height. The <object> might look like this, for example:

Those numbers aren't as strange as they seem! If you assume 96 pixels to the inch, the Silverlight application is actually being sized to a dimension of 4 inches by 3 inches. You can also put these explicit dimensions on the <div> tag that usually encloses the <object> tag, or the <form> tag that encloses the <div> tag but you'll need to make them styles, like this:

If the Silverlight application does not set its own size (such as with Width and Height properties set on the UserControl derivative generally called MainPage) the application will get the size indicated in the <object> tag. Here's the same application as the one referenced above but with a specific size defined right in the HTML for this blog entry:

Get Microsoft Silverlight

I've also put a

attribute on the <div> element to center it. You could also align it at the right or replace that attribute with a:

or:

so that text flows around the application.

If you now zoom the browser window, you'll see the Silverlight application getting larger and smaller, but the application continues to perceive a size of 384 by 288. The zooming is for the benefit of the user and has no effect on the application.

Sometimes Silverlight applications need to adjust their size within the browser. The application can do this by reaching into the <object> tag and adjusting the width and height attributes. The application gets access to this element with the static HtmlPage.Plugin property, which returns an object of type HtmlElement that refers to the <object> tag. You then use the methods GetAttribute and SetAttribute to get and set the width and height attributes. (Alternatively, you can use HtmlPage.Plugin.Parent to get access to the <div> element that usually encloses the <object> element and then use GetStyleAttribute and SetStyleAttribute to adjust styles.)

Get Microsoft Silverlight

To the left is a Silverlight program that does just that. The style attribute on the <div> element sets float: left and margin: 12 so it won't be too crowded by this paragraph. The two gray bars are basically sizing borders that you can manipulate with the mouse. Move the bottom one up and down, and the right one left and right to effectively resize the Silverlight application. As you do this, the browser reflows the HTML around the application:

Play around with this long enough, and you're sure to find some flaws. For example, collapse the application into its narrowist width, which is set in code at 72 pixels, but keep moving the mouse. Now with the mouse button still pressed, come back and make the application wider. You'll see that the sizing border and mouse are now out of sync.

Things get really nuts when you scroll the page all the way to the bottom, and then try to make the Silverlight application shorter. (You won't be able to do that here unless you have a very tall monitor or zoom out a lot.) Because the page itself is decreasing in size as the Silverlight object is getting shorter, it amplifies the mouse movements.

Both of these problems are related to the simple technique I used. Those gray bars are just templated Thumb controls, and they're generating DragDelta events based on mouse movement without reference to any underlying control. I'll be working on something better soon.

Here's the XAML:

<UserControl x:Class="SizableApp.MainPage"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:src="clr-namespace:SizableApp">
    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        
        <Thumb Grid.Row="1"
               Grid.Column="0"
               Cursor="SizeNS"
               DragDelta="OnBottomThumbDragDelta">
            <Thumb.Template>
                <ControlTemplate>
                    <Rectangle Height="6" Fill="Gray" />
                </ControlTemplate>
            </Thumb.Template>
        </Thumb>

        <Thumb Grid.Row="0"
               Grid.Column="1"
               Cursor="SizeWE"
               DragDelta="OnRightThumbDragDelta">
            <Thumb.Template>
                <ControlTemplate>
                    <Rectangle Width="6" Fill="Gray" />
                </ControlTemplate>
            </Thumb.Template>
        </Thumb>

        <src:ShowSizeControl Grid.Row="0"
                             Grid.Column="0" />
    </Grid>
</UserControl>

It's just a 4 by 4 Grid with the two Thumb controls and a UserControl derivative named ShowSizeControl that is basically the same as the earlier program. (I normally would put another Thumb in the lower-right corner, but there's no cursor in Silverlight for diagonal sizing!)

The code basically handles the two DragDelta events:

using System;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Interop;

namespace SizableApp
{
    public partial class MainPage : UserControl
    {
        SilverlightHost silverlightHost = Application.Current.Host;
        HtmlElement silverlightPlugin = HtmlPage.Plugin;

        public MainPage()
        {
            InitializeComponent();
        }

        void OnBottomThumbDragDelta(object sender, DragDeltaEventArgs args)
        {
            double height = GetDimensionPixelValue("height");
            height += silverlightHost.Content.ZoomFactor * args.VerticalChange;
            height = Math.Max(height, 72);
            SetDimensionPixelValue("height", height);
        }

        void OnRightThumbDragDelta(object sender, DragDeltaEventArgs args)
        {
            double width = GetDimensionPixelValue("width");
            width += silverlightHost.Content.ZoomFactor * args.HorizontalChange;
            width = Math.Max(width, 72);
            SetDimensionPixelValue("width", width);
        }

        double GetDimensionPixelValue(string style)
        {
            string dimension = silverlightPlugin.GetAttribute(style);

            if (String.IsNullOrEmpty(dimension))
                return 0;

            if (dimension.EndsWith("px"))
                dimension = dimension.Substring(0, dimension.Length - 2);

            return Double.Parse(dimension);
        }

        void SetDimensionPixelValue(string style, double value)
        {
            silverlightPlugin.SetAttribute(style, ((int)Math.Round(value)).ToString() + "px");
        }
    }
}

Notice the two fields at the top: The SilverlightHost object is used for its Content property, which has the current ZoomFactor. This is necessary for calculations. The HtmlElement called silverlightPlugin is the <object> tag with the height and width attributes.

From the two DragDelta events, the GetDimensionPixelValue obtains the current height or width value from the <object> element, strips off the "px" suffix, and converts it to a double. One would think that it would only be necessary to add to that value the HorizontalChange or VerticalChange of the event. The SetDimensionPixelValue then appends the "px" back on the end and set the attribute. This approach worked fine when the browser window wasn't zoomed, but behaved poorly otherwise. The borders lagged behind the mouse.

I discovered that it's necessary to multiply the HorizontalChange and VerticalChange values by the ZoomFactor. I'm convinced this is a bug in Thumb, or some kludge to make the Thumb work with scrollbars and sliders. (Try running my Thumb-based ClickAndDeformText app at 200% and then TouchAndDeformText, which uses my replacement for the Thumb.) When the browser is zoomed 200%, moving the mouse by a single pixel causes a MouseMove event to indicate a change of half a pixel, which is correct, but the Thumb indicates a delta of a quarter pixel.


Comments:

Sorry but I don't understand the point. To be blunt I have had tried number of your books and frankly neither of them can be labeled as great. As a vivid book reader I gave your earlier books great leeway (e.g programming windows) as there was not enough material back then. But after .NET I was totally disappointed on any work you had done. Then I tried to read you book on Turing, again what book lacked objectivity and originality. I hope that you take the criticism positively and do listen to people who read your book!

I hope this comment will be published , and not land in recycle bin.

Regards

— Tu hai Gandu, Thu, 7 Jan 2010 02:47:57 -0500

Wanted to post this elsewhere, where you stated you were moving on to other subject matter, but the post failed there.

Behind me sits a substantial investment in bookware that I seldom re-visit. Why? Because books have become rather primitive in light of the options now available.

Electronic editions are an improvement, but even this small step forward is remarkably backward compared to where we should be.

An electronic book should incorporate audio, video, simulators, and a fluid navigation system that goes well beyond keyword searches, but can synthesize new content from its own material.

Sadly, Book publishers have opted to stay in the present day equivalent of the stone ages. Creating a modern book should be akin to the process used for making a 3D animated feature. That said, with the technology at hand today, the costs needn't be exorbitant given the tools available for such tasks.

I find myself pasting together my own books these days, by combining online videos, podcasts, voiced over screencams, blogs, searches and annotations. It's painful, yet still surpasses what I can get from a typical book.

At the minimum, books should be riddled with rich media content. I want to hear the authors/actors voice stepping me through a concept using some form of visual metaphor. I want to watch Mr. Petzold stitch together a Visual Studio solution as he talks me through the process and provides pertinent anecdotal details.

Turning a page is just so 19th century.

Keep writing Mr. Petzold (we need you to), but add in the rich media goodness that we now expect. Even your other topical choices will soon require such treatment to be marketable to the younglings that will soon consider 3D movies the norm.

— Mario, Wed, 27 Jan 2010 14:41:36 -0500

> I find myself pasting together my own books these days, by combining online videos, podcasts, voiced over screencams, blogs, searches and annotations. It's painful, yet still surpasses what I can get from a typical book.

Even with the "painful" amount of time and effort you spend on these projects, obviously they're still not ready for public consumption; otherwise you would have posted a URL so we could see your work.

Now imagine this: Instead of doing a mash-up, imagine you're actually creating all the content as well. Imagine how much more work would be involved in making the types of books you envision. It takes me a year of full-time work to write a totally conventional 1000-page book. I personally can't imagine how much longer it would take to integrate a bunch of other media into this narrative in a seamless harmonious manner.

> Creating a modern book should be akin to the process used for making a 3D animated feature.

Creating a 3D animated feature involves the work of hundreds of people. Have you ever sat through the credits at the end of one of these movies? The sheer labor involved only makes sense when a movie has a paid audience of millions of people. An audience for a typical programming book is fewer than 10,000 readers!

Moreover, programmers no longer want to pay for anything. We now exist in a culture where everything composed of bits is fair game for unlimited distribution, a culture where the founding principle is "information wants to be free," and a culture where any suggestion that creators should be paid for their work brings down the wrath of anonymous trolls.

Trying to make money writing programming books these days is akin to laboring in a hostile work environment. People are stealing our work and then complaining that it's not good enough. — Charles

Charles, ignore these guys, I've been reading your books (and buying them) since college and learned more from them than any others.

This post helped me with a strip charting silverlight app Ive been building (actually, converting from WPF to silverlight) that I'm having trouble making resize correctly.

Paintball Barrel, Mon, 1 Feb 2010 16:13:33 -0500

I like your article on "Silverlight Apps that Resize themselves", and the research followed. I think you should continue to write books and the blogs, and they help people like me tremendously. Please continue your work.

One thing I noticed, is that when you first run ShowSizeApp.html, it displays the original browzer size, but when you just click on the page, vertical and horizontal scroller bars appear on the browzer, and the sizes displayed change. May be you have already covered why the scroller bars appear on the browzer,when the page gets clicked, but I could not find. I am having same problem in my applications, and do not know why those scroller bars appear. Thank you.

Kumar Reddi, Sat, 13 Feb 2010 12:21:32 -0500

"Silverlight Apps that Resize themselves" gave me just what I was looking for when I needed to solve that problem in my code. I have written an article on code project 'Flexibox �?" A Silverlight alternative to Lightbox' that makes use of your technique. I added a link to your blog in the article to give you credit.

I would also like to say a big thank you for teaching me to program Windows back in 1992. I still have your Programming Windows in my loft, a fantastic book. It's great to know you are still going strong.

Tom Wright, Wed, 10 Mar 2010 13:13:20 -0500

I for one found this article to be EXTREMELY useful. THANK YOU CHARLES! In fact, I have been scouring the web for days trying to learn how to use Silverlight and react with the asp/html "wrapper" in a dynamic way, and this example is general enough, with explicit code, that it has opened up a whole new world to me.

I understand you have written books? I want to find them and use them. If you feel so generous as to send me a link or two that would be great. Otherwise, I hope that I will find them, especially if you have written anything about Silverlight or WPF. Thanks again!

Vaughn Bigham, Wed, 31 Mar 2010 21:28:22 -0400


Recent Entries
< PreviousBrowse the ArchivesNext >
Subscribe to the RSS Feed

(c) Copyright Charles Petzold
www.charlespetzold.com