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!
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.
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:
- Setting up the basic Frame
- Profiling the Frame
- Converting into a Control
- Cosmetic Surgery and Bindings
- Paths and Vector Graphic data
- Final Touches
- Additional Functionality and Thoughts
- Deployment and Re-use
- Source
- History
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:
(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.
(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.
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.
In the Objects and Timeline, rename the three new elements as shown in the image below:
(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.
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.)
Ensure all the Margins are set to 0 and rename the elements to suit their locations in the Grid
.
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:
(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.
In the popup window, call this new resource "VerticalGradient
".
Now select the rest of the Top and Bottom elements and apply this newly created resource, to the Fill of each.
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:
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
.
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.
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.
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.
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
.
And that is it, with regards to making your own Control
!
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.
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.
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:
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.
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:
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
.
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:
(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.
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.
(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.
Delete the MinWidth
and MinHeight
parameters applied to the Column and Row Definitions.
So that your XAML looks like the image below:
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....
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.
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:
As if by magic, the Picture Frame Thickness updates in the Artboard
, to look like the image below:
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.
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!
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! :-)
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!)
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.
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:
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.
Look in the Project tab and notice you now have a file called "PictureFrames.xaml".
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.
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.
(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!
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:
- 29th March, 2010: Initial version