OpenLightGroup Blog

rss

Blogs from OpenLightGroup.net


Blend For Beginners: Grid to Canvas to StackPanel

In my opinion, Canvas is better suited than Grid to serve as LayoutRoot (the default name of the main UI Element within UserControl). Using a Grid locks the entire UI into a matrix of rows and columns; you can set properties to allow elements to cross row and column boundaries, but why should you have to. If you use a Canvas, you can position elements wherever you want them, including Grids.

Sometimes, though, I don’t think about that when I start coding and I end up in a situation where I want to change LayoutRoot to a Canvas when the page is well along. Fortunately it’s not that tough. But first…

Using the Pinned Tools Icon and Waking it Up.

To begin with, I’m going to add a Canvas to the existing LayoutRoot. First I find it under Assets and then double-click it to add it to the pinned Controls icon on the Tools Panel. Probably I won’t need to add a Canvas again, but in principle I find it more convenient to pin the controls so I don’t have to go into Assets if I need one again. Below you can see the Assets icon with the pinned Controls icon below it. Click on the little white triangle and hold to see the list of all pinned Controls. If you select one and then double-click on the pinned control icon it will be inserted in the upper-left corner of the currently selected object in the Objects and TimeLine panel.

image

Sometimes the pinned controls icon will be dimmed and non-responsive. In that case click on the Assets Icon, image and the pinned controls icon should become active.

Adding the Canvas

With UserControl selected, double-click on the pinned Canvas icon.

image

You’ll find that Canvas was added to LayoutRoot; UserControl can only have one child, so Blend assumes you intended to add it to the UI, not to be the new container for the UI.

To make Canvas the parent of LayoutRoot, you must drag it (cut and paste won’t work) to UserControl and look for a message as shown below:

image

Release the mouse button:

image

Now select LayoutRoot properties and delete the name. Then open Canvas properties and enter the name LayoutRoot.

image

Assuming you want LayoutRoot to fill UserControl, which you probably do, change LayoutRoot width and height to Auto:

image

And set alignment to stretch and you should find the dimensions are sized to fill UserControl:

image

Working With the New LayoutRoot

My immediate goal is to add a header to the page something like this:

image

So the first think I’m going to do is make room on the Canvas, which is now LayoutRoot, by shrinking the Grid that was formerly LayoutRoot.

image

The height of UserControl is 400 and LayoutRoot is set to Auto / Stretch, so its height is also 400. If I want to leave 80 pixels of height for the header, I reduce the height of Grid to 320 and set the Top property to 80. Again I want to point out that you can hover the mouse over the property and then drag the value up or down. Watching the control move or change size as you do so is much more gratifying than just typing in the new value. Just don’t drag the control itself; the results might be what you want cosmetically, but the underlying property changes may not be as expected.

You could set the upper Margin to 80 and achieve the same result, but in my opinion you don’t want to. Or, if you’re going to use Margins, don’t use the Left and Top properties. Below you can see the resulting xaml after I set the Top property to 80 and the upper Margin to 20. The result will be that Grid is situated 100 pixels from the top of LayoutRoot. This seems too confusing to me. There may be a scenario where you would want to use both, but I can’t think of one.

<Grid Width="461" Height="320" Margin="0,20,0,0" Canvas.Top="80">

I also have the Grid Width property hard-coded to the width of LayoutRoot because I want Grid to fill the page horizontally. I tried setting it to Auto and setting the horizontal alignment to stretch with the expectation that it would expand horizontally to fill LayoutRoot, but it did not; it sized itself to its contents. Maybe because its container is a Canvas, maybe because of how the columns are defined. Not something I want to take time to explore right now.

LayoutRoot needs a Background Brush. I may end up making Grid transparent and so the entire page is the color of LayoutRoot, but for now I’m just going to give it a separate color. Something garish to keep up my rep. I also moved the ‘Statements’ TextBlock, which belongs in the header to LayoutRoot through cut and paste.

image

Notice in Objects and Timeline that TextBlock appears below Grid. This doesn’t cause any problems, but if you take my advice you’ll drag it up above Grid so that the sequence in Objects and Timeline matches the design area to avoid confusion.

Now to add the header controls. We’ll position each one using the Top and Left properties. I’ll show you the first one. Make sure that LayoutRoot is selected in Objects and Timeline. Them add a Label by dragging from Assets or double-clicking a pinned control on the Tools Panel:

image

The results always look discouraging, but it’s really easy to make the necessary adjustments.

image

Notice I moved the Label to fall after the TextBlock in Objects and Timeline. Now I add a ComboBox, TextBox, and Button using the same techniques.

image

The hardest part will be to remember to make sure the LayoutRoot is the currently selected object before adding each element. If you forget, just cut and paste it into LayoutRoot. It’s also easy to accidentally change Margins instead of the Top and Left Properties. And you’ll want to give the search TextBox a hard-coded width. Also notice each has been moved to its logical position in Objects and Timeline. We could also give each one a name, but in general you want to avoid naming elements if it’s reasonably clear which one is which from the layout.

Hey, I Coulda Used A StackPanel!!!

This will work perfectly fine, but what if we want to move the four controls we added as a single unit? We can select all 4 and move them, but that’s kind of a pain. And I’ll bet they’re not all perfectly aligned vertically.

Fortunately, it’s easy to move them into a StackPanel.

(Addendum - I got so absorbed in the ins-and-outs of Auto dimensions and what-not that I forgot to point out the even easier way to place these controls in a StackPanel. Simply select all four in Objects and Timeline, right-click, select Group Into…, and click on StackPanel. The long-winded version just describes a few of the details you might not notice if you do it that way.)

Add the StackPanel to LayoutRoot by dragging from Assets or double-clicking a pinned StackPanel control. Set the Orientation to Horizontal and give it a Background Brush so you can see where it is. Take the Width and Height out of Auto mode or the size will be zero. Adjust the Height and Top by dragging the values in the textboxes so you get approximately what you see below:

image

We had to give dimensions to StackPanel in order to see it because we haven’t yet added the four controls. They appear on top of StackPanel because they overlap StackPanel in LayoutRoot and fall after it in the Objects and Timeline list.  If I move StackPanel below the controls in Objects and Timeline they disappear because objects in Objects and Timeline appear on top of overlapping objects higher in the list and vice-versa:

image 

We can add the controls to the StackPanel with a wholesale cut and paste:

image

Resulting in:

image

We lost all our Top and Left property settings because they don’t apply in a StackPanel. And I’m happy to confirm that Blend did remove the Canvas.Top and Canvas.Left properties from the xaml.

It’s easy to fix everything up. Select all four controls and set VerticalAlignment to center:

image

Now adjust the margins by dragging either the left of right Margin property of each control (using the property textboxes) so the controls are horizontally aligned. Personally I would use the left Margins, but it doesn’t matter as long as you’re consistent. Even better, as I just discovered, you can select all four and adjust the value of all of them simultaneously:

image

Now that the StackPanel has content, I want to change the Width property to Auto. Otherwise, if the width of LayoutRoot is reduced, StackPanel will exceed it. I think I’ll change the Height to Auto as well and make it transparent by removing the Background Brush:

image

One last thing you could do to tighten up StackPanel is to remove the left Margin from Label and set the Left property of StackPanel:

image

You’d think you could center StackPanel in LayoutRoot instead of setting the Left property, but the alignment settings seem to have no effect. I did a quick search and didn’t find anything official, but it seems that Canvas doesn’t honor alignment settings.

But the point is you can now move the set of controls around freely by moving the StackPanel.

 

Summary

Nothing much to add. Just that you could use left and upper Margins on a Canvas as equivalent to Top and Left, but the resulting xaml is different. Canvas.Top and Canvas.Left are attached properties.





Comments are closed.