Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Picture Frame Control in Expression Blend & Silverlight

4.94/5 (37 votes)
9 Jun 2010CPOL23 min read 1   1.3K  
How to build your first re-useable Control from scratch in Expression Blend

Click here to Skip to main content

Introduction

Welcome to my third beginners' tutorial for Expression Blend and Silverlight.

(Updated with new additional Style Brushes thanks to Andrew Rissing - See here!)

Apologies to some of you, for the deviation from buttons, but there is so much more to Blend!

PictureFrameControl/img22.jpg

But it is definitely NOT Picture Frames!

Although a Picture Frame will be an excellent opportunity to build our first Control.

A Control is basically a reusable Asset, with pre-defined Properties and a Style.

PictureFrameControl/ExtraBrushesFrame.jpg

Overview

A developer friend came to me in a bit of a panic, saying he needed a picture frame with "mitred" corners (45 degrees) for a demo he was presenting the next day. He'd had a quick go at creating it himself, but was having problems doing the mitred corners of the frame.

Expect he probably also thought: Why have a dog and bark yourself? (English Expression)

So I threw something together for him, but as a Designer was concerned about the context in which the picture frame was going to be used, i.e., what colours would be used, how thick would the frame be and what sort of profile would look best. As such, I wanted to ensure these were all easily editable for him and the frame I created (and put my name too), would look as good as it could. (We designers are very sensitive about our work being presented the best it can be - Sorry!)

So whilst making the Picture Frame, I released it would make an excellent beginners' tutorial, on making your first Control.

Before commencing this tutorial, I recommend that you read my previous CodeProject tutorials:

I have divided this tutorial into sections for easier reference:

  1. Setting up the basic Frame
  2. Profiling the Frame
  3. Converting into a Control
  4. Cosmetic Surgery and Bindings
  5. Paths and Vector Graphic data
  6. Final Touches
  7. Additional Functionality and Thoughts
  8. Deployment and Re-use
  9. Source
  10. History

Section 1 - Setting Up the Basic Frame

Create a new Silverlight project in Expression Blend and call it something like "PictureFrame".

Using the Selection Tool, roughly set up some Grid dividers as shown below:

Image 4

(Don't worry about the proportions, we will sort these out later.)

Now select the Rectangle Tool and drag out a Rectangle to fill the top left square.

Ensure the Margins are all set to 0 and remove the Stroke.

Set the Fill to a colour that stands out on the background (I've chosen a bright green).

Right click on the Rectangle and choose Path > Convert to Path.

Image 5

(I have also changed the colour of the LayoutRoot Grid, to an off white for the clarity of this tutorial.)

Select the Pen Tool from the left hand tool bar and click on the bottom left point of the Rectangle (Square) to delete it.

(This could also have been achieved by using the Direct Selection tool, selecting the node/point and deleting it.)

Select the newly created Triangle and duplicate it using Copy and Paste.

Now move the duplicated Triangle over to the top right square of the Grid and ensure the Margins are set to 0.

Next in the Transform section of the Properties tab, Flip the triangle along the X axis.

Image 6

Select the Rectangle Tool again and drag out a Rectangle to fill the middle section of the Grid between the 2 Triangles.

Ensure the Margins of the Rectangle are set to 0 and the Stroke and Fill match the Triangles.

Image 7

In the Objects and Timeline, rename the three new elements as shown in the image below:

Image 8

(TopLeftMask, TopRightMask and TopMiddleMask)

Select these three elements and duplicate them by using Copy and Paste.

Move the duplicated elements to the bottom of the Grid and Flip them along the Y axis to invert them.

Image 9

Ensure the Margins are set to 0 and rename these elements to BottomLeftMask, BottomRightMask and BottomMiddleMask.

Duplicate more of these elements and fill in the side pieces as shown below.

Flip the Triangle elements to fit and resize the Rectangles - Do not Rotate any of the elements!

(I have changed the Fill colour of the side elements to blue for clarity.)

Image 10

Ensure all the Margins are set to 0 and rename the elements to suit their locations in the Grid.

Image 11

Section 2 - Profiling the Frame

Now we have the basic components of the Frame, let us make it look a bit more appealing.

Select the TopMiddleMask element and set the Fill to a Linear Gradient as shown below:

Image 12

(With black Gradients Stops at 0 and 100 on the Ribbon and a white Gradient Stop at 40 on the Ribbon.)

Now in the Advanced Properties for the Fill, select Convert to New Resource.

Image 13

In the popup window, call this new resource "VerticalGradient".

Image 14

Now select the rest of the Top and Bottom elements and apply this newly created resource, to the Fill of each.

Image 15

Next repeat the process to setup a HorizontalGradient resource for all the side elements.

Depending on how you moved and Flipped your elements, you will probably end up with something looking like this:

Image 16

Notice that the Gradients on some of the side elements, do not match.

(I purposely offset the white centre of the Gradient, to show and ensure we get all the elements aligned properly.)

If your project looks like the above, the easiest way to re-align all the elements is:

To Flip the LeftMiddleMask and RightMiddleMask elements and reverse the direction of the Gradient Stops of the HorizontalGradient resource. This will unfortunately mean, that the white Gradient Stop is now set at 60 on the Ribbon, and does not match the VerticalGradient resource.

So instead:

I will still Flip the LeftMiddleMask and RightMiddleMask elements and use the Gradient Tool to make the Arrow point in the opposite direction.

This can also be done:

By opening the Advanced properties of the HorizontalGradient resource, and changing the StartPoint to 1 and the EndPoint to 0.

Image 17

Now you should have a Frame, with a matching Gradient all the way around.

If you have got in a muddle, just download and open the file below to get back on track.

Section 3 - Converting into a Control

What I really want, is to add some colour to this Picture Frame, but I don't want to set this in the Gradients as I will have to adjust the Gradient Stops individually, which is fiddly and time consuming. Especially if I add more detail to the Picture Frame, by adding more Gradient Stops.

Instead, I want to control the colour of our Picture Frame in a single place. Just like we have done in the "Style" for my previous tutorials, (Building Better Buttons and Arcade Button)

But at the moment we do not have a Style, just a collection of element. So we need to group these elements in some way, so that they are all packaged up in a Template that has a Style!

This is really easy and all we need to consider is what sort of Control Template best fits our Picture Frame. Now if you have looked at my previous tutorials, we have played around with a Button Control, its Style and its Template.

For simplicity and because those tutorials were for beginners, I referred to the button as a UserControl, it is not! A button is a Control, and can be considered as part of a UserControl - That is all you need to know for now!

When we make a Control, we need to base it on an existing Control, so that Blend can give is some basic Properties. The Picture Frame is obviously not a button, and as such, does not need to have all the functionality that a button does. So even thought I could base this Picture Frame on a button Control, it would not be appropriate.

Instead, we should look for the most basic Control that meets the needs of our Picture Frame. For this, I have chosen to use the "ContentControl" as the basis for our Picture Frame.

So in the Objects and Timeline, right click on the LayoutRoot Grid and choose Make into Control.

Image 18

In the Search area of the popup window, type "content" to filter all the available Controls.

Then select the "ContentControl" in the results window.

In the Name (Key) field, call your new Control something like "PictureFrameControl" and hit OK.

Image 19

Note: I have purposely avoided calling it "FrameControl". as Blend already has a Control called "Frame"

(The FrameControl is a ContentControl with added navigation, which is not needed here.)

You can call your Control anything you like, but clarity for all who may use ore reference it, should be your primary concern!

Hopefully, you will have noticed that in the Objects and Timeline, we are now in the Template for our new Control. The top left corner of the Artboard has also changed, to show we are in the Template of our PictureFrameControl. We also now have a ContentPresenter as part of our Control, as this is a default component for a ContentControl.

In the Artboard, the ContentPresenter is currently obscured, as it is in the top left corner of our Control.

So select the ContentPresenter and in the Artboard, drag it into the centre section of Grid.

Reset the Margins, so that the ContentPresenter sits in the top left corner of the centre section of the Grid.

Image 20

And that is it, with regards to making your own Control!

Section 4 - Cosmetic Surgery and Bindings

Now we have a Style, can think about adding some colour to our Control, as well as controlling the thickness of our Picture Frame.

So firstly, go to the Style for our PictureFrameControl and set a nice colour for the Fill. (I have chosen Blue.)

Now back in the Template of our Control, select the Rectangle Tool from the left hand tool bar.

And in the Artboard, drag out a Rectangle to fill all three sections of the top row, as shown below.

Note: I have temporarily set the Fill of this Rectangle to Red for the clarity of this screenshot.

Image 21

Rename this Rectangle to "TopBGround" and remove the Stroke.

Now set the Fill to Template Binding > BorderBrush.

Repeat this for the bottom row and both side columns, naming them "BottomBGround", "LeftBGround" and "RightBGround".

In the Objects and Timeline, move these four new elements to the top of the list.

So that they are behind all the Gradient (Mask) elements.

Image 22

Now in the Objects and Timeline, select all the Gradient (Mask) elements and set the Opacity to 50%.

With a bit of luck, your Picture Frame should look something like the image below:

Image 23

Now this is all very pretty, but the Frame is rather chunky, so let us address that next. There is nothing wrong with us adjusting the Frame thickness, by adjusting the Row and Column dividers, that we set right at the start. But we can do a lot lot better than that!!!

How about setting the Frame thickness, so that it is controlled by the Border property of the Style? This would make it easier to control and adjust for our future needs. Sadly in Blend 3, we cannot visually just select the border property that we wish to Bind to (Link to). Instead we need to use a Custom Expression, to Bind to the Top, Bottom, Left and Right of the Border property of the Style.

So select the TopBGround element and in the Advanced Properties of the Height, select Custom Expression.

Image 24

In the popup window enter: {Binding BorderThickness.Top, RelativeSource={RelativeSource TemplatedParent}}

(Don't worry about understanding the notation of this Expression, just notice that we are Binding to the BorderThickness.Top.)

Repeat the process for the Height of the BottomBGround element, but change BorderThickness.Top, to BorderThickness.Bottom.

Now set the Width of the LeftBGround and RightBGround elements, to BorderThickness.Left and BorderThickness.Right respectively.

The Blue background of the Picture Frame has disappeared and this is because the Border Thickness of the Style is set to 0.

So go to the Style and set the Border Thickness to 50 on all sides.

This should give you a Picture Frame looking something like the image below:

Image 25

Not quite what we want yet, even though it is an interesting looking image.

We want our Blue background elements, to grow from the outside towards the centre.

So back in the Template, select the TopBGround element and set the VerticalAlignment to Top.

Image 26

Next select the BottomBGround element and set the VerticalAlignment to Bottom.

Now set the LeftBGround and RightBGround elements, to a HorizontalAlignment of Left and Right respectively.

This should now make your Picture Frame look like the image below:

Image 27

(Interesting, but not yet quite what we want.)

The Grid dividers are currently set to Star (Unlocked Padlock), which means they are proportional to the overall scale.

And if you have read my Arcade Button tutorial, you may remember that I said there were three states for these Grid dividers.

Star, Fixed and Auto-sized.

We want Auto-sized, so that the Grid divider will grow or shrink, depending on the needs of the elements within that portion/division.

So click twice on the 1st and 3rd Row and Column dividers, to change them to Auto-sized and match the image below.

Image 28

Very little (if anything) has changed and this is because Blend has automatically applied a Minimum value for these Row and Column sizes.

This can be very handy at times, but a bit of a pain for our needs...

To remove these Minimum settings, we need to either go to the Row and Column Definitions of the LayoutRoot element.

These can be found in the Advanced part of the Layout section and the Advanced part of the Definitions Editor.

Image 29

(I have shrunk this window down for this tutorial.)

But it is a lot easier to select the LayoutRoot element and edit the XAML directly.

Image 30

Delete the MinWidth and MinHeight parameters applied to the Column and Row Definitions.

So that your XAML looks like the image below:

Image 31

While we are here looking at the XAML, it is worth noting:

That the "Control" Template for this PictureFrame "Control", is based on a TargetType of "ContentControl".

But back to sorting out the Frame Thickness...

Now even though we have removed these Minimum setting for our Column and Row sizes, nothing has changed in the Artboard.

The Thickness of our Picture Frame has remained unchanged, and the reason why seems unclear...

Hmmmm.... Smile

Section 5 - Paths and Vector Graphic Data

From the title of this section, you have probably guessed what is preventing our Frame thickness from resizing. It is the Path elements that are stopping our Column and Row dividers, from resizing to fit the Border parameters of the Style. So let us have a look at these, and how they work.

Anyone who is familiar to Vector Graphics, will know that all the points or lines that make up a Vector Graphic are relative to one another. But you have probably never really cared, that these have a default size or scale. When you open a Vector Graphic, it will size based on the units described within that file. And Blend does exactly the same, so let us look at this data.

Select one of the Path elements, like the TopLeftMask element and look at the XAML code.

In the image below, I grabbed the snippet we are interested in.

Image 33

Look at: "Data="M0,0 L120,0 L120,120 z"

What does this all mean?

Well, basically from a starting point of "0,0" in the top left corner, extend a distance of "120" along the X axis and "0" along the Y axis. After defining this point, define the next point from the same starting point, at "120" along the X axis and "120" along the Y axis.

This defines 3 points if you include the start point, which is all that is needed to define a triangle. These distances are defined in Blend as pixels, and this specifies the default size of the Path that makes up our Triangle corners.

As such, Blend sees these as the Minimum sizes that these elements should be displayed or Drawn at. So it is the Paths, that are superficially preventing our Column and Row dividers, from following the Border thicknesses we are setting in the Style.

In this example, we do not care what Scale these Triangles are drawn at, just that they are triangles and proportionally correct. So we could set the first point at 1 pixel along the X axis and the second point at 1 pixel on the X axis and 1 pixel along the Y axis. Combined with our starting point, we have 3 points, and our triangle proportionally correct.

But this would mean, the Minimum size of our Border (Frame Thickness) would be 1 pixel. Not ideal, if we want 3 or less sides to our Picture Frame. Instead it would make more sense, if we set the Scale of our Triangle to 0.1 of a pixel, as this would equate to 0 when rendered in pixels. The proportions would be the same and this is all we care about in our Picture Frame.

So in the XAML, select "Data="M0,0 L120,0 L120,120"", and using the Replace feature from the Edit menu.

Change to "Data="M0,0 L0.1,0 L0.1,0.1 z"" and Replace All, as shown in the image below:

Image 34

As if by magic, the Picture Frame Thickness updates in the Artboard, to look like the image below:

Image 35

You now have a fully functional Picture Frame Control, that needs just a few touches to tidy it up.

But feel free to come out of the Template and play around with the Border Thickness of the Control.

Section 6 - Final Touches

In the Template, select the LayoutRoot element and Template Bind it to the "Background".

Select the ContentPresenter and Template Bind it to the Padding of the Style.

In the Style, set the Background and BorderBrush colours to suit your taste.

(I would suggest you set the Background to have "No Brush" (Transparent)).

If you desire, apply some Padding to space the ContentPresenter from the edge of the Frame.

And that is about it!

Section 7 - Additional Functionality

Hopefully you realize, that the Horizontal and Vertical Gradients can be adjusted to change the Frame profile. As well as the Alpha values of the Gradient Stops and the element Opacity's as a whole. But before you start playing and adjusting these, I suggest you make a copy of the Picture Frame "Style". This way you can have multiple Picture Frame "Styles", each with a different Frame profile.

To make a copy, come out of the Template and Style using "Return Scope".

Select the Picture Frame and from the Objects menu, select Edit Style > Edit a Copy.

This will leave all the parameters you set for the previous Style as they were. And any updates to the Style you make now, will only apply in the new Style. This however, will not apply to the Horizontal and Vertical Gradients, as they are not contained within the Style. These Resources are part of the UserControl, which in simple terms, is your whole page. So if change these Resources, the updates will be applied to both the Picture Frame "Styles", as they both reference them.

The obvious easy solution, would be to duplicate these Resources, and then rename and re-reference them, for one of the Styles. You may also be thinking that, it would be really cool if we could add additional Brushes to the Style. As then the Horizontal and Vertical Gradients, would be wrapped up nicely in the Style. And I wish it was just that simple, but it requires the help of a Developer, to get more brushes in the Style. What they would essentially do, is inherit from the ContentControl and define additional Functionality.

Making it a CustomContentControl and not a Style we can drop on any old ContentControl.

So you would have to judge, is it really worth it?

Well it was to Andrew Rissing!!! Who kindly stepped in and added these Styles to the PictureFrameControl (CustomContentControl) in the form of "Attached Properties". We had a few minor issues getting it to initialize correctly in Blend, so ensure you Rebuild (Or possible Run, Close and Re-Open) if you experience any initial issues. But I'm fairly sure it is all OK! :-)

PictureFrameControl/ExtraBrushesFrame.jpg 

PictureFrameControl/ExtraBrushesResources.jpg

Thanks very much Andrew, You are a star!!! And you can find Andrew Rissing's: Attached Dependency Properties" (Article/Tip) here.

(More discussion on these Extra Brushes can be found in the "Conjecture" section, later in this tutorial.)

There is another thing to consider, when I previously chose the ContentControl, as the basis for my Control. A ContentControl is a Base Class, and other Controls, like the FrameControl inherit from the ContentControl. (Imagine that a Base Class is the foundation, and Controls build on this foundation, additional functionality to the existing functionality). Meaning that most things that inherit from the ContentControl, will be able to have our PictureFrameControl Style applied to it. Put a Frame Control in the LayoutRoot (on the Page) and see if you can apply the Style we have created for the ContentControl.

Good, isn't it?

But if I had based my PictureFrameControl on a FrameControl, I may not have been able to property apply the Style to a ContentControl. As the FrameControl may have additional functionality defined in the Style, that is just not in the ContentControl Style. Probably not a problem in this example, but it can be and why I chose to base my Control on the Base Class.

Something else this allows, which I will describe in very simple terms to convey the concept is:

By placing the Style in the Base Class, we have that Style and the Style of the inherited object (FrameControl for example) to play with. So we can set the Style in the Base Class and override some of those Style properties in the inherited objects Style. (Great if all your objects/Controls share a similar "Theme" or Style, that can be tweaked at a higher level for individual types of Controls.)

(Hope that makes sense and not too deep!)

Conjecture: Modified from Original Article (Before Andrew Rissing Created Extra Brushes)

Consider this: How many times are you going to be reaching for the most funky Picture Frame Control on the market? I would only suggest it, if you really needed this functionality, like maybe an application for a Picture Gallery.

There is also the situation/problem of these Brushes "Attached Properties" appearing on every Control and all over your application. Which may be confusing to someone editing the application later. How will they know it is only the PictureFrameControl that will support these brushes?

While the extra brushes are great, I would probably not use them unless I really needed lots and lots of Styles of this Control. I don't know for certain how these extra brushes will react in a full scale project. So if you have only just a few gradients in you application, I would personally use Resources, just to be safe. As reducing any risk of breakage is a good thing and maybe an overhead worth taking. And with the greatest of respect to Andrew, I would recommend any designer speak to their developer, before implementing the advanced (ExtraBrushes). But as a design tool, the advanced Control is excellent and allows for really easy manipulation of the frame contour and colour hints.

Now the very last thing is to consider with these Gradient Resources, is light direction. Is the light coming from the standard 315 degrees (10:30 on a clock face)? If so, then we actually need 4 Gradient Resources for each Frame Style. As the Top and Bottom elements will need a highlight on opposite sides of the Gradient and so will the side Gradients.

And before we jump at four extra Brushes, we could think about setting up a whole new set of Overlays (Masks) on top of the existing overlays. (Just so we don't affect the existing Resources.) But depending on the profile we set in the existing Resources, it will change the profile point of the highlight or lowlights required in the additional Overlay.

So we are stuck with 4 Gradient Resources, if we want an angled light source.

Section 8 - Deployment and Reuse

Now there is nothing wrong, with keeping this PictureFrameControl where it is, sat in this project. We could just Copy and Paste it in to another project when we need it. But it would make a lot more sense, to store it away as a Resource of its own, by placing it in a Resource Dictionary. As then, when we want this type of Control, we just add the relevant Resource Dictionary to the project files. This is very simple, but it is all easy when you know how!

In the File menu, select New Item, and a popup window appears, as shown in the image below:

Image 38

Select Resource Dictionary, call it "PictureFrames.xaml" and hit OK.

Now go to the Resources tab on the right and ensure everything is expanded.

Now select the "PictureFrameControl" Style and drag it on to the PictureFrames.xaml.

The Gradient Resources automatically follow, as they are referenced by the Style.

You should now be able to delete the Style and Resources in the UserControl, using right click Delete.

Image 39

Look in the Project tab and notice you now have a file called "PictureFrames.xaml".

Image 40

This is where your Control is now stored and referenced by the project.

So if you want to add this Control to another project, just go to the Project menu and select Add Existing Item.

Image 41

Find your PictureFrames.xaml, which is inside your Project/PictureFrame/PictureFrame directory.

Your Control will now be available in any new project that uses it, as an Asset, available in the Assets menu.

Image 42

(It was actually available here before we moved it, but I didn't think of mentioning it.)

Hopefully you realize, that we can do the same with the previous two button tutorials as well.

So that they are packaged up in a Resource Dictionary, entitled something like Buttons.xaml.

The last thing to mention about Resource Dictionaries, is editing your Controls! Without going into depth, it is a lot easier to edit a Control Template when it is in the UserControl, rather than in a Resource Dictionary! (More on that later...)

That will do for now, hope that was useful and please rate my tutorials!

Your comments and support means a lot to all us article/tutorial writers!

As well as feedback on any errors, or points that need further clarification...

Cheers!

Section 9 - Source

Completed PictureFrameControl, with 2 Gradient Resources and Resource Dictionary reference:

Just the PictureFrames.xaml (2 Gradient Resources) as a Resource Dictionary:

Completed PictureFrameControl, with 2 additional "Style" reference Brushes:

History

  • 29th March, 2010: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)