XTab's Blog

Ged Mead's Blog at vbCity

vbCity Blogs moved to:
http://cs.vbcity.com/blogs
  Home :: Syndication  :: Login

JulAugust 2007Sep
SMTWTFS
2930311234
567891011
12131415161718
19202122232425
2627282930311
2345678

Archives

Topics

Ramblings

VB.NET

Monday, August 06, 2007 #

     WPF Layout - The DockPanel

Introduction

 The DockPanel probably offers you a better option than the UniformGrid in many cases. However, this layout panel too is not without its eccentricities and you can spend a lot of time tweaking and fiddling with settings in order to get the exact display you want.

 However, if your needs are simple then the DockPanel works well.   You do have to be aware of the effect of its "Pecking Order" approach to layout and distribution of space.  What this means is that, if the user resizes the window to height/width ratios that result in the elements that are higher in the pecking order grabbing all the available real estate then some lower status elements may become unacceptably distorted or even disappear completely.

 You can partly control this by use of MaxWidth, MinWidth, MinHeight and MaxHeight settings, but this isn't guaranteed to do the trick. It appears that the demands of elements higher up the pecking order override the requests of the lower, as you will see when we work through the samples that follow.

How NOT To Do It.

 I wanted to create a simple DockPanel layout that had just three elements:

  1. An Image
  2. A Button to Move To Another Image
  3. A Text Description of the Image

 That doesn't sound too difficult, does it? Of course I wanted the three elements to be displayed without any truncation of text, cropping of image or - worst of all - loss of sight of any of the elements no matter how the user resized the Window. It seemed to me that WPF should be able to achieve this easily - after all, presentation is what it is all about.

 Here was my first effort:

Code Copy
<Window x:Class="DockPanelDemo1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="DockPanelDemo1" Height="460" Width="500">
    <DockPanel  >

      
       <Image Source="Images/Pictures 106.jpg" DockPanel.Dock="Top" />

       
       <Button Margin="4" Padding="4" Background="Green" FontSize="13" DockPanel.Dock="Right"
Foreground="White" MaxHeight="60" MaxWidth="199" HorizontalAlignment="Right">
  Next Image >
   Button>

   
     <TextBlock Margin="7" FontFamily="Verdana" FontSize="15" TextWrapping="Wrap"
        DockPanel.Dock ="Left" >
        PortPatrick, Dumfries and Galloway, South West Scotland May 2006
     TextBlock>

   DockPanel>
Window>

 I think the commenting makes the steps pretty clear. Some points to note:

 The DockPanel has an Attached Property named "Dock". If you haven't come across Attached Properties, they are used in WPF in a way that allows children to hook into properties of their parent. So, in this example, each of three child elements uses the DockPanel's Dock property to request the docking position they want.

  You will see that the Image has a Dock value of Top, and that the Button and TextBlock have Right and Left respectively. As mentioned earlier, the docking is handled on a first-come, first-served basis, so the Image - first in the XAML code - gets all the available top of the Panel.

 The second item, the button, is allocated sufficient of the remaining available "right hand space" for it to completely fit. (A button automatically sizes so that it is large enough to take its content - in this case, simple text).

 The third in line TextBlock is allocated the remaining space after the other two have taken their shares in turn. (As it happens it isn't really necessary to set the TextBlock's DockPanel.Dock property to Left as I have done in the code. If you were to delete that property setting, by default it would be given all remaining space)

 Initially, the display is excellent:

  But if you resize the window so that its height is greatly reduced, the image continues to display as you would hope, but the button and TextBlock are relegated to the wastelands.

DockPanel Squashed

  Disconcertingly, if you maximize the window, this too will cause an unwanted display: The Image stretches to take 90% of the screen and the button and TextBlock are scrunched up at the bottom. Although still usable, the button's content becomes unreadable.

 By the way, if you've had some experience with WPF layouts and are sat there thinking that setting the Window's MinWidth and MinHeight to, say, 500 and 460 will fix the problem (as I did) you too will find that it doesn't. Yes, it constrains the total window size, but that ol' Alpha Male Image just muscles in and grabs more than its fair share of the space and the button and TextBlock are still left sitting on the sidelines!

A Way That Does Work.

 Instead of spending a huge amount of time setting Min and Max Widths and Heights on each of the child elements, which only brought limited success anyway, I found that if I changed the pecking order and made the image wait its turn then I got the result we wanted. I understand that the key to this is that the button and the TextBlock have their sizes controlled by the text they contain and therefore - as their needs are less - they still leave enough real estate for the image to be fully displayed too.

 Have a look at this XAML code snippet. It's not a million miles away from the original, but the order in which the three elements are created and placed has changed - and that's the key to its success.

Code Copy
<Window x:Class="DockPanelDemo"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="DockPanelDemo" Height="440" Width="480" MinHeight="300" MinWidth="350">
   <DockPanel  >

     <Button Margin="4" Padding="4" Background="Green" FontSize="13"
      DockPanel
.Dock="Bottom" Foreground="White"
      HorizontalAlignment
="Right">
      Next Image >
     Button>

     <TextBlock Margin="7" FontFamily="Verdana" FontSize="15" TextWrapping="Wrap"
        HorizontalAlignment="Center"  DockPanel.Dock ="Top" >
        PortPatrick, Dumfries and Galloway, South West Scotland May 2006
     TextBlock>

     <Image Source="Images/Pictures 106.jpg" />
   DockPanel>

Window>

 Now, no matter what I do on the Resizing front, all three elements are visible and scaled sensibly.

 DockPanel - Elements correctly displayed.

 The eagle-eyed among you might have noticed that I slipped in a MinHeight and MinWidth value for the containing Window. I wouldn't want you to think that was some sleight of hand trick that changes anything I've said about the pecking order. With those minimum values removed, the scaling still works perfectly - except of course you eventually reach a point where the image is as tiny as my bank balance.

 Image too smll to be of use.

 It's actually quite an interesting experiment to continue to make the window smaller and smaller, because it very clearly demonstrates how each element drops off the edge in reverse pecking order as the amount of screen space available continues to reduce.

 If you continue to reduce from the size shown above, the image disappears altogether:

 Image disappears

 And as you continue to squeeze it, the next in line - the TextBlock - also gets pushed out of the nest:

 
Button only

 There are several other aspects of the DockPanel that I want to cover, but I will leave those for a second instalment. I hope this brief initial look has shown that with minimal effort it is quite easy to create docked layouts that scale perfectly as the window is resized.

posted @ 5:02 PM | Feedback (0)

 As I had some feedback on how the images are accessed in my blog item on UniformGrids, I thought I would just very quickly explain what I did.

 There are a whole host of approaches you can use to create, store and access resources in WPF. Many of them are very similar to what you would do with Windows Forms. Some are quite new. I will be writing in more detail about the new ways later, but for now I only want to cover the very basics; just enough to get you started.

 The XAML code I use to display the images is:



     Image Margin="2" Source="Images/Loch View.jpg" x:Name="Image1"



 As you can see, the source is set as "Images/Loch View.jpg", this being the name of the folder in which that file is stored, followed by the filename. You'll probably have noticed that a forward slash is used instead of the usual backslash. This is because under the covers the path is changed to a URI. You can in fact use either a forward or backslash when using this particular addressing method, but to save confusion later (if and when you start to use more complex path configurations) it is recommended that you use the forward slash as default.

 You can point to your image resources in various ways. The most basic is to add images individually to the Solution Explorer in the usual way. That is:

  • Right click on the project name
  • Select Add..
  • Select Existing Item
  • Navigate to the image file on your hard drive
  • Select the one you want .

 It will then be added to the list of files in Solution Explorer as you would expect. In the example below I have added a new jpg file in this way - Blue Hills.jpg.

 If you check out the properties of this file in the Visual Studio you should see that its Build property is set to "Resource".

 Build Action set to Resource

 This resource is now available for your project. You would point to it by using:



    Source="Blue Hills.jpg"



  Resources which are assigned this Build action will automatically be deployed with the compiled application when you come to roll it out as a completed application. So on the face of it, it's the easiest approach. However your Solution Explorer will soon get crowded if you have many images, which is why I prefer to place groups of related images in folders within the Explorer.

 You can simply click on the project name in Solution Explorer, select Add .. New Folder and create a folder to hold images. In the UniformGrid demo I created a folder named "Images". As with adding files to the main project, you simply right click on the name of this new folder and select files to be stored within it. You can then point to those files by using the folder name and file name combination as shown at the start of this item.

  You can add multiple files at a time and - as with the first method - the default setting of the file's Build property will be "Resource" so it is available to the project immediately.

 Using an Images Folder

 If you are in the habit of using the "Resources" Tab in VS 2005 and storing your images in there, you can still do this. The only thing to watch is that you will need to manually change the Build Setting from "None" (the default when you use this approach) to "Resource". Otherwise you will get a "Cannot find resource" error if you try and use it.

 Using the traditional Resources Tab Page

 If you use this approach the Resources folder will be created for you in Solution Explorer, as well as an actual folder named Resources in the Project's sub folders on the hard drive.

 The following screenshot shows that separate physical folders are created for the Images and Resources folder items that I added to the project.

 Folders are created for you.

As I mentioned at the start, there are several more sophisticated (and complicated) ways of adding and manipulating resources in WPF, but these basic approaches will get you started.

posted @ 10:29 AM | Feedback (2)