Thursday, May 17, 2012    
Blog  

OpenLight Blog

Simple Layout Techniques In Blend

Oct 3

Written by:
10/3/2010 1:36 PM  RssIcon

Just to set the record straight, I’m usually not all that enthusiastic about visual programming tools. I rarely use the UI in Visual Studio to add controls, Silverlight or ASP.NET – in fact about the only time I do use it is to set properties. But Blend is different. Just having a view-centric tool that you can use to reach out to the ViewModel without ever touching a line of code helps in visualizing the relationships and in appreciating just what binding is about.

But, much as I love Blend, sometimes it confuses me. To this day the dimensions assumed by some auto-sized controls bewilders me. So when I say simple layout techniques, I mean simple. It’s sort of a path hacked out by an earlier explorer that may not be the best, but avoids falling into the swamp.

So to start with I’ll show you the feature-complete but aesthetically challenged first pass at the UI for one of the pages of a project I just started. The Application is called Test Me, and the first iteration will show two TextBlocks, one for the question and one for the answer. Essentially this version is the equivalent of flash cards. You read the question in the question box and when you’re ready click on the ‘Show’ button to see the answer. The ‘Next’ button, as you’d expect, causes the next question to be displayed.

 image

Preliminaries: Auto-Size and Stretch Alignment

Before starting on the actual project, I’d like to explore the consequences of auto-sizing and alignment. Generally speaking, an auto-sized element sizes itself according to its contents, unless its alignment is set to ‘Stretch’, in which case it will size itself to fill its container. I say generally speaking, not only because there are other properties, such as margins, to consider, but also because sometimes the results are not what you would expect.

So let’s establish the size of the entire page by setting the UserControl dimensions to 640x480 (not shown). Let’s look at what affect this has on our main UIElement, LayoutRoot. In the screenshot below, if we (1) Select LayoutRoot (2) Set LayoutRoot alignment to stretch and (3) Set Width and Height to Auto, LayoutRoot makes itself the same size as the encompassing UserControl as we’d expect:

image

What happens if we set the alignment to anything other than ‘Stretch’? As you can see below, with (1) LayoutRoot selected, I (2) Set horizontal alignment to ‘Center’ (3) Layout root sizes itself to zero width and (4) Appears as a vertical line with a dot in the center in the design area.

image

We should be able to give LayoutRoot some width by adding an element. Now select LayoutRoot in Objects and Timeline and double-click the button icon to add one.

image

Well, not what I would have expected, which is a Button sized to its content of ‘Button’. If you can’t tell, the Button is at (1). It's positioned in the upper left corner (2), and the dimensions are set to a Width of 6 … and there’s our problem. Our button is only 6 pixels wide because the width is hard-coded. And see the little white dot to the right? That indicates the Width value has been changed from the default, which is Auto. Why? I don’t know. The point is that the safest way out of this kind of situation is not to start grabbing things with the mouse cursor, but instead to check the properties panel to see what the problem might be.

image

If you click on the white dot and select reset from the resulting dialog, the Width will be set to Auto, or, you can just double-click on the Auto-Size icon:

image

And we see the button width set to Auto and therefore sized according to its content.

image

Ok, one more point from this rather contrived example and we’ll move on to the project.

We can see that LayoutRoot sizes its width according to the width of the button. Let’s see what happens when we give Button a left margin.

image

As you can see, LayoutRoot has resized itself to accommodate Button’s left margin. A cool way to do this is to hover your mouse over the Margin setting and when it changes, hold the left button down and you can increment the value by dragging up or right and decrement by dragging down or left; LayoutRoot on the design surface widens or narrows as you drag.

Now let’s say you had gone at this by trying to “manually’ widen LayoutRoot by grabbing a drag-box (1) and dragging to the right. Well, because you resized LayoutRoot to a specific size, the Width is no longer set to Auto (2).

image

Would you notice, or would you just keep dragging to increase the width, and then try to drag the button to the right? You might end up with the correct appearance, but as you add more elements it becomes more and more difficult because things keep re-positioning themselves in unexpected ways. You can end up with a StackPanel, for instance, positioned entirely on the wrong part of the page. But the buttons ‘within’ are positioned correctly because their margins are set so they appear entirely outside the stack panel. They will continue to be stacked, so if the StackPanel is transparent, you might not notice that the panel is in the wrong place – until you need to make some changes in a hurry and every change you make affects something else, and so on, and so on….

In the example below you can see that the Buttons (1) are all contained in StackPanel, which I have colored Green so you can see it. By setting the left margin of all three buttons to a negative number (2), I can position the buttons entirely outside the StackPanel.

image

Now if you’ll notice at (3), the button’s vertical alignment is set to ‘Stretch’. Why doesn’t the button stretch vertically to the height of the stack panel? I’m sure there’s a good reason, such as the fact that it’s contained in a stack panel whose orientation is vertical. To test that theory out, I removed the offsets so the buttons appeared in the stack panel where you’d expect, then changed the stack panel orientation to horizontal.

image

As you can see, the buttons have now all stretched to fill the stack panel vertically. The offsets at the top are from the vertical margins set on the buttons that spaced them vertically when the orientation was vertical.

When you think about it this makes sense. You’d expect a vertically oriented stack panel to be tall and narrow. It’s reasonable that you’d want buttons to fill the space horizontally but constrain their height, and vice-versa. We get this behavior in spite of the fact that the default Horizontal and Vertical alignments for Button are Stretch, so as you can see, just because an element has its alignment set to ‘Stretch’ doesn’t necessarily mean the setting will be honored.

Ok, enough about the dangers of blundering around off the path. It’s ok to explore, but you have to keep your head about you so you can get back if you need instead of falling in the swamp where even Ctrl-Z won’t help you. But I guess I should come up with a rule to justify what I’ve said so far. I guess it would be:

Richard’s Rules Of Blend #1 – Avoid sizing and positioning elements in the design area using the mouse. It deprives you of situational awareness.

The most insidious effect is that what’s onscreen may look correct, but because you just grabbed and dragged, you may end up with an intricately balanced tangle of margin settings that will fundamentally bite you two weeks later when you need to add new elements. Ctrl-Z your fundament out of that.

You can still resize elements “dynamically”, by making them grow or shrink. As I mentioned above in the example where I incremented the button’s left margin – hover the mouse over the Margin setting you want to change, when the cursor changes, hold the left button down and drag the mouse to increment or decrement the value. This works for most numeric fields, such as width or height. It’s super useful, which is ironic, because when I first saw it I thought it was gimmicky. Don’t bother to tell me if I’m misusing the word “irony”. Ironically, I’m ok with it.

Down To Business

If you’ve been following along, get rid of everything except LayoutRoot, set its dimensions to Auto and alignment to ‘Stretch’. The UserControl is a page and we always want the UI to fill the entire page.

We know we want two columns and that each column should have a heading, a text area, and a button arranged vertically. We could take all this into account before we start, but I want to show you how you can layer these things on as you go along. So let’s just start with the heading.

Pinning Controls to the Tools Panel

First, go to (1) Assets, (2) Controls and double click on  (3) Label.

image

This will result in the label icon (4) showing up under the Assets icon (1). The label icon is actually a Pinned Controls icon which may represent several pinned controls. The label appears because it is the currently selected pinned Control.

Now double-click on the label / pinned controls icon and end up with a (1) Label, sized to its content (2), positioned at the upper-left of its container (3).

image

With a few simple property changes we can get the results we want with no fuss:

image

Plus scroll down below Common Properties to set font size:

image

This brings us to:

Richard’s Rules Of Blend #2 – Place, then Position and Size.

Get in the habit of placing controls by selecting the target in Objects and Timeline, then inserting the control by double-clicking if possible, or by dragging from Assets. The real point is to avoid the habit of grabbing controls and drawing them on the design area. Unless you know what you’re doing you may end up with unwanted margin settings or flat out errors in placing the control in the wrong container – and then there’s inadvertently changing the size of existing elements. Once you have the control in the right container just adjust the properties as required.

Ok, this is getting good to me – here’s:

Richard’s Rules Of Blend #3 – Use Margins Consistently

This will come pretty naturally if you avoid using the mouse for positioning. Notice above I positioned the Label vertically by setting the upper margin. The controls are stacked vertically, so it makes sense to use the margin above each one to control the space  between them, in a StackPanel at least (in a grid it would be the space from the top of the row). It also makes sense to avoid setting the bottom margin (with one exception), but that’s what happens if you drag elements around to position them. For a horizontal stack panel you’d want to set the left margin and leave the right margin alone. The same principle would apply in grid rows and columns except the margins are from the row or column border, not the adjacent control.

As for the color, unless I already know the color I want, I just pick some arbitrary color so I can easily spot the dimensions of the element in the design area, even if it may end up being transparent in the long run.

In the screen below I’ve added a background color to LayoutRoot, but the point in this screen is to make sure you have LayoutRoot selected and double-click on the TextBlock icon:

image

You may have to scroll the design area to the upper left to see it:

image

I want to set the background color of the TextBlock, which has no Background brush. We can give it a background color by wrapping it within a border. I like to add assets to the tool bar as I use them, so after adding the border by finding it in Assets and double-clicking I get something like this:

image

Make sure LayoutRoot is selected then double-click on the border icon to add a Border Element to LayoutRoot. Then, in the Objects and Timeline Panel, cut and paste TextBlock into the Border, then set Border’s background brush:

image

Now it’s easy to tell where TextBlock is. But you know, things are getting too spread out. I know I want a column of centered elements, but as it is it’s not convenient to see the center and the left edge at the same time.  Now you may have noticed that every new element is added to the upper left corner of layout root by virtue of their alignment settings. And you may have further noticed that LayoutRoot is a grid and every element is added with a Column property of zero.

So, assuming we want LayoutRoot to remain a grid, which we do, the most useful approach would be to add a column to LayoutRoot. It’s quite easy to do this visually. Simply hover over the point shown and click to position a column at that point.

image

However, let me show you an alternative which is useful when you need to set the widths with precision. Select LayoutRoot and look under the Properties tab to  (1)Layout . Click on the icon at (2) and select the button next to ColumnDefinitions at (3):

image

Use the button at (1) to add two column definitions (2), with identical Layout Width of 0.5 (star):

image

And back on the design screen:

image

That’s more like it. Now we can get on with positioning TextBlock, or I should say, Border (1). We position Border and thereby TextBlock by setting HorizonalAlignment to ‘Center’ and the upper margin to 80. This positions TextBlock 80 pixels beneath… what?

image

Well, Label’s upper margin is 35 and it’s height is 28, so it’s pretty clear the margin is the vertical offset from the top of LayoutRoot – where do you think you are, some busybody StackPanel?

So what happens if we decide we do want these controls in a StackPanel? It’s easy enough to find out. Just make sure both Label and Border are selected in Objects and Timeline, right-click and select Group Into StackPanel.

image

The result is not too bad. As shown below, the resulting StackPanel has an upper margin set to Label’s original upper margin of 35 and Label's upper margin set to zero so it’s top is the top border of StackPanel. TextBlock also gets a margin of zero and appears just below Label because if follows Label in a StackPanel with the Orientation property set to Vertical:

image

We adjust TextBlock’s vertical position by setting the upper margin as usual. But now the margin applies to the distance between adjacent elements in a vertically-oriented stack panel (relative) rather than from the top of a grid column (absolute).

In the shot below I’ve given the StackPanel a background color and added vertical spacing to the two controls by adjusting their upper margins.

image

You can see that the stack panel has sized itself to its content both vertically and horizontally. Let’s see what happens when we add a button by double-clicking the button icon on the toolbar. Make sure that StackPanel is selected first:

image

Cool, right where we want it. Simple to position it by increasing the upper margin, un-stretching the width, and so forth. The point is we got it where we want it clean and easy.

image

I also added some vertical offset under the ‘Next’ button. I did this by adding a lower margin to the button, which is the exception I was talking about when I said you should avoid setting the bottom margin on controls in a vertical column. Because StackPanel sizes vertically according to its content, this causes the bottom of StackPanel to be pushed down. You’ll find if you use this approach that you can adjust the upper margin (do it by dragging the margin value in the properties panel – it looks cool) on any element in the stack and the others will be positioned as you’d expect and the bottom of StackPanel will be pushed down consistently. The drawback is if you add another element below the ‘Next’ button you need to reset the ‘Next’ button lower margin to zero. Too bad StackPanel doesn’t have Padding.

We still have a little clean up to do. We want Border to size itself to accommodate TextBlock, but for whatever reason its height is set to 100. If we change it to Auto we get this.

image

So the caption and the ‘Next’ button are fine, more or less. One change we need for sure is size TextBlock independently of its content. Now to my way of thinking, I should be able to leave TextBlock centered and add left and right margins. Then StackPanel should adjust itself when I change the size of TextBlock to whatever I decide is right. But it turns out that StackPanel is constrained by its own margins, and when I make TextBlock 300 pixels wide and centered I see this:

image

StackPanel’s width is Auto, but it has left and right margins of 110 in a column of width 320, which only leaves 100 pixels of width for StackPanel. So in this case Auto and stretch together mean I’ll stretch as far as I can but there’s no guarantee that will be far enough. By taking the StackPanel width out of stretch mode by setting the HorizonalAlignment to left and reducing the left margin so the StackPanel has room to expand we start to get control of the situation.

image

Richard’s Rules Of Blend #4 – If controls get clipped unexpectedly, look for AutoSize being constrained by margins.

Now it’s tempting to just wing it, but less time-consuming in the long run to figure out just how wide you want TextBlock to be. We know the total column width is 320, so let’s go with 300 pixels with left and right-margins of 10.

image

That centers the StackPanel within the column. Now at this point you can add a left margin so there’s some padding between the left edge of the StackPanel and Border, but you probably don’t want to. If we AutoSize Border so it’s always the same size as TextBlock, then TextBlock will never be clipped except when it is - if Border is constrained so it can’t stretch enough to accommodate TextBlock then TextBlock will be clipped in spite of Border being set to AutoSize. So here we have TextBox already consuming the entire width, so if we add any margin at all then TextBlock gets clipped by that amount. It seems so obvious when I write it down, but so easy to fall prey to:

So to gain a margin to the left of Border/TextBlock, TextBlock must reduce its width. Let’s go with 280 and allow ten pixels on each side:

image

As it turns out, if TextBlock is 280 pixels wide, then Border ends up at 282, leaving 9 pixels on each side. The point is, if you need margins around Border, you have to keep in mind they need to allow Border enough width to accommodate whatever Border contains.

Ok, so now we can resize TextBlock vertically just by dragging the lower border downwards. Let me clue you into a pitfall I just fell into however. I decided that the entire StackPanel needed to be higher on the page so I just dragged the upper border upwards. The problem is, that took the StackPanel out of AutoSize vertically, so when I drag the bottom of TextBlock downwards it pushes the ‘Next’ button into the bottom of the StackPanel instead of pushing the bottom of the Panel downwards.The only way I know to avoid this kind of thing is to use the property panel in preference to dragging and resizing with the cursor.

So whether by dragging the border down or setting the height in the properties panel we set TextBlock to its final height. Notice we set the height of TextBlock, not Border. We use Border to set position and TextBlock to set dimensions.

image

Ok, we’re almost done. Now we’re going to select StackPanel in Objects and Timeline and create another copy which we drop onto LayoutRoot, so we end up with two identical StackPanels in column 0 of LayoutRoot:

image

We move the second StackPanel to Column 1 and make a few cosmetic changes:

image

Summary

While programming, I prefer to use Blend whenever I can and I prefer to avoid editing xaml directly because I find it provides a better experience in visualizing what’s going on with binding and a better sense of separation between View and ViewModel. But it can be challenging to size and position elements, particularly so that elements can be re-positioned or added in the future easily. I hope I’ve made it a little easier in your own explorations of the Blend design experience.

Tags:
Categories:

Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
CAPTCHA image
Enter the code shown above in the box below
Add Comment   Cancel 
  
Copyright 2009 by OpenLightGroup.net   |  Privacy Statement  |  Terms Of Use