Showing posts with label Button. Show all posts
Showing posts with label Button. Show all posts

Monday, June 28, 2010

A Chrome and Glass Theme- Part 8

Introduction

In this post I’m going to show you how to make the radio buttons I blogged about previously. These buttons have gone through many iterations and I’m still not sure I’m quite happy with them just yet, but I’ll cover them anyway since they introduce a few interesting attached properties. Here is the sample I’ve been building up, now with the radio buttons:

You can grab the source here.

This post is the 8th in a series. If you are not familiar with styling controls in Blend, or working with Resource Dictionaries then I would recommend that you start at the beginning of the series.

The Radio Button

When I first attempted this button I ended up with horribly complicated arrangement of shapes to get it to scale properly. I had placeholders and spacers and grids within grids, with some of them having their height bound to their widths (which doesn’t work very well). And I had half circles on the ends with gradient blends merging seamlessly into the gradient blend on a rectangle in the middle. It was pretty awful.

The previous blog about Automatic Rectangle Radius X and Y should give you an idea of a simpler way to achieve the look. In the end I went with the attached properties since they work nicely both at run-time and design-time. If you grab the source from the link above, you may have to build it in Blend to get it to use the attached properties on the design surface.

The RadioButton inherits from the ToggleButton, as does the CheckBox. They all have a common theme of the button being checked, unchecked, or indeterminate; The radio button just has a different visual appearance, and adds the GroupName property. This style could be adapted for the ToggleButton too, but it wouldn’t really work as a style for a CheckBox.

The contents for this button are grouped in a grid with two columns; the left column contains the orb, the right column contains the ContentPresenter element.

Creating a custom style template for a radio button shouldn’t be difficult at all if you have been following along with the series, but I thought it might be worthwhile to look at some of the elements that make up this button.

Scalable Rounded Ends

A rectangle has the RadiusX and RadiusY properties that let you turn the sharp corners into rounded corners. For this button I want the rounded ends to stay rounded, at the right proportion, regardless of the size of the button. Unfortunately, there is no easy way to set those properties to be half of the height of the rectangle.

As mentioned in the post about Automatic Rectangle Radius X and Y I used an attached property to achieve the rounded ends since that gave me the best result when working with Blend. I use 3 rectangles in the button: one for the rim using the ChromeBorder resource brush, one for the main body using the ChromeFill resource brush, and one the same size that uses the ChromeDarkeningLinear resource brush to make the white text stand out better.

The ellipse is used on the right end of the button to add some dark shading to make the button look rounder, and the Orb grid uses the same principle as this post.

I created an attached property called IsRounded that, when true, uses another attached property called RoundCapsRatio (which defaults to 0.5 if unset) that it multiplies against height or width (whichever is smaller) and applies to the RadiusX and RadiusY properties of the rectangle it is attached to. You may have to build the solution once in Blend to have it applied properly, but then the corner radius automatically updates in blend as the rectangle it is applied to changes size.

Scalable Orb

The other attached properties I created are WidthRatio and HeightRatio (not used in this example). I use the WidthRatio attached property on the Orb grid to keep it perfectly square so the ellipses inside it stay round. I can’t have this happen without an attached property because there is no other way (that works in Blend) to set the width of an element to be relative to the height. I set the WidthRatio to the value “1” to achieve this. I could have just used a Boolean attached property, but having the ratio makes it more reusable.

If you use both the attached properties on a visual element, the handler will prefer the WidthRatio over the HeightRatio. Here is the method that applies the property values. It only applies the values if they have actually been set:

   1: private static void UpdateSizeRatio(FrameworkElement element)
   2: {
   3:     double ratio;
   4:     if (element.ReadLocalValue(WidthRatioProperty) != DependencyProperty.UnsetValue)
   5:     {
   6:         ratio = (double)element.GetValue(WidthRatioProperty);
   7:         double height = element.ActualHeight;
   8:         if (!double.IsNaN(height) && height > 0)
   9:         {
  10:             element.Width = height * ratio;
  11:         }
  12:     }
  13:     else if (element.ReadLocalValue(HeightRatioProperty) != DependencyProperty.UnsetValue)
  14:     {
  15:         ratio = (double)element.GetValue(HeightRatioProperty);
  16:         double width = element.ActualWidth;
  17:         if (!double.IsNaN(width) && width > 0)
  18:         {
  19:             element.Height = width * ratio;
  20:         }
  21:     }
  22: }

Conclusion


As far as radio buttons go, these are fairly limited in their application since they are so big. They are too big to use in the same way as you would normally use a radio button, but they do make quite good navigation buttons instead of using tabs.


The use of Attached Properties seems the best solution for adding behavior to a visual element that works at design time in Blend, but it’s not ideal – I would really rather have a way to do this using Blend that didn’t need me to swap into the XAML.


References:


The icons are from the Tango Desktop Project.

Thursday, May 6, 2010

Quick and Easy Scalable Rounded Bevels

Too Much Photoshop
I was watching a UI design video yesterday where the presenter imported a Photoshop design into Blend and turned it into a Silverlight GUI. One thing I noticed was that he kept a lot of the raster layers from the Photoshop image as graphical elements inside the Silverlight app. If you have a carefully crafted design that uses filters and effects that aren't available in Blend then you have no choice if you want to retain the fidelity of the design mock-up. That usually means, though, that the Silverlight app is going to be at a fixed width/height to match the Photoshop design - since raster graphics don't usually scale well.

I don't like fixed width and height Silverlight apps. I kept thinking "there must be a way to do that in nothing but XAML". Some of the graphics were understandably left as raster since there would be no equivalent way to achieve the same result in Blend. But the elements that bothered me were usually simple panels or buttons that had a common visual theme - they had rounded corners, and they had some kind of bevel that made them appear raised or lowered. This is the sort of thing I mean:


Let's pretend it was a proof done in Photoshop (it was actually an old copy of Paint Shop Pro) and we brought it into Blend. The gradients are easy enough to do in Blend, but what about the shading around the edges of the buttons and panel, giving them a raised appearance?

A lot of the button styles that I've seen people blog about tend to follow 2 or 3 themes: glassy styles with inner glows and highlights; ellipses with radial gradients; or linear gradients. These buttons are either all elliptical buttons, or they don't bother trying to give rounded shading at the edges like the image above.

The problem is the corners. If it weren't for the rounded corners on those rectangles, you could just use thin rectangles hard up against each edge with an appropriate linear gradient. But even then you run into difficulty at the corners where those rectangles meet. There's no easy way to make the gradients from each edge's rectangle meet smoothly.

You could probably do it on a fixed size button with paths shaped specifically for the corners and edges, but then it wouldn't scale well.

This got me thinking about how to achieve the kind of look in the top image, in a way that scales to any kind of size, or roundedness. While the method described below doesn't have the flexibility of the filters and effects that you find in Photoshop, it is a fairly good approximation, and is easy to achieve.

Fuzzy Borders
The approach I came up with is to use Border controls with two sides missing, and the Blur effect applied to make it fuzzy. Here is a working example:


The idea is to use a Border control with a White BorderBrush, the top and left edge thickness set to 3, and the bottom and right edge thicknesses set to 0. Then add another Border control, but with a Black BorderBrush, and the opposite edges showing. The blur effect is set at 4 and the borders are offset by 1 (using the Margin) from the edges. The Opacity of the borders is down at about 30%. That's it - just two borders on top of whatever background you want. And it scales to any size.

The example above uses the same technique for the buttons and for the panel containing the bigger buttons. The only difference in the object tree for the buttons is that there are a couple of extra rectangles to manage States (eg MouseOver, Disabled etc). The buttons swap the colors from black to white and vice-versa for the MouseDown state, and offsets the ContentPresenter element too.

Here are the object trees for the panel and for the button:



You can see that I have copied the elements from the button template into the grid on the right to be used as a panel (should have renamed the element "PanelBorder" instead of "ButtonBorder").

You could also use this as a Control Template for a ContentControl and have a nice reusable 3D panel style.

Here are the files in case you want to play with it.

Wednesday, April 7, 2010

A Chrome and Glass Theme - Part 3

In the first two parts I covered how to use Blend to create reusable resources for brushes and styles, and we styled a Border control and a Button control.

In this post we are going to style two more relatively simple controls: a TextBox, and a CheckBox. So start by re-opening your solution from last time (you can grab it here if you haven't been following along). This is what the final styles will look like:


You can grab the solution containing these styles here.

First: Improve the Button
But first, I want to quickly revisit the Button style we created last time - after playing around with it some more, I don't like the way it looks when it's disabled. We didn't change the default disabled state appearance, which grays-out the control by changing the opacity of the "DisabledVisualElement" rectangle. The problem is that when the button is disabled, it actually stands out more than any enabled buttons around it - which it shouldn't.

To fix it, we are going to give it a glassy look when it's disabled.

Select the button we created last time and use the bread crumb trail to edit the control template. Open up the Object tree and select the grid control under the "Background" Border control. Click the Advanced Properties Options button on the right of the Background brush and select "Reset" to get rid of the link to the template background color.

Select the "DisabledVisualElement" and change it's Fill to a linear gradient with the following markers:
  1. #FFFFFFFF Position: 12.7%
  2. #FFCDCDCD Position 32.8%
  3. #FF898989 Position 35.2%
  4. #FFC2C2C2 Position 39.6%
  5. #FFB3B3B3 Position 49.3%
  6. #FFFFFFFF Position 52.4%
  7. #FF252525 Position 55.6%
  8. #FF808080 Position 100%
And click the little down arrow below the brush editor to show the advanced properties of the gradient brush, and change the Opacity of the whole brush (not the rectangle) to 31%. Now convert that to a new resource called "ChromeGlassFill" in our ChromeGlass resource dictionary.

Finally, select the "Disabled" state, set the Opacity of the BackgroundGradient rectangle to 0%, the Opacity of the DisabledVisualElement rectangle to 100%, and the Opacity of the ContentPresenter to 50%. And set the margins of the DisabledVisualElement to 2. Use the Bread Crumb Trail to exit the style template back into the MainPage. The image to the left shows the button now in it's different enabled states.

The TextBox Control
Before we create a TextBox control we have to group our existing button into a Grid since a Border can only contain one control. Right click the button and press Ctrl + G to group it into a Grid control. Blend will have set the margins on the grid to fit exactly around the button, and changed the button horizontal and vertical alignments to Stretch so reset the margins of the new Grid back to 0, make sure the horizontal and vertical alignments are both Stretch, and the Width and Height are both Auto. Of course now our button takes up the entire grid, so change it's width back to 140, and it's height back to 30. And drag it back into place.

Now add a TextBox control to the Grid, make it about the same size as the button. From the Object menu, select "Edit Style" | "Edit Copy..." and save it as "TextBloxGlassyStyle" in our ChromeGlass resource dictionary. Set it's Background brush to our new "ChromeGlassFill" brush, and it's border to the "ChromeBorder" fill. Set the Foreground brush to a white solid color, the CaretBrush to a white solid color, and the SelectionForeground to black.

Now use the Breadcrumb Trail to edit the current template and open up the object tree. Select the self-named "Border" control and reset it's Background brush to be empty - we are going to animate the opacity of the background, but if we do it on the Border then it will also affect it's child controls.

Create two new rectangles inside the Grid called "UnfocusedBackground" and "EditingBackground" positioned as the first children of the grid as shown in the image to the left. Make sure the margins of the rectangles are reset to 0, the horizontal and vertical alignments are set to Stretch, and the Width and Height set to Auto.

Select the UnfocusedBackground rectangle and use the Advanced Property Options button on it's Fill to use Template Binding to the Template's Background property. Set it's stroke to No Brush.

Select the EditingBackground rectangle, set it's Fill to #4CFFFFFF, and it's Opacity to 0%.

Make the following changes to the Focused State:
  • Set the UnfocusedBackground Opacity to 0%
  • Set the EditingBackground Opacity to 100%
  • Set the FocusVisualElement Opacity to 50%
  • Set the FocusVisualElement Margins all to 0
And these changes to the Disabled State:
  • Set the Border Opacity to 75%
  • Set the ContentElement Opacity to 50%
  • Set the DisabledVisualElement Opacity to 0%

That's our TextBox done. Exit the Style Template and run the solution to try it out.


The CheckBox Control
Create a CheckBox control and use the same technique as for the TextBox control to create a style called "CheckBoxGlassyStyle" in our resource dictionary.

While editing the style, set the BorderBrush to our ChromeBorder LinearBrush resource and set the Foreground to White. Now use the Breadcrumb Trail to edit the control template and make the following changes to the controls inside the template (you can ignore the warnings about animations being removed):
  • Set the Background control's Fill to our GlassFill resource
  • Set the BoxMiddleBackground control's Fill to our ChromeFill resource (you will have to Reset it first)
  • Set the BoxMiddle control's Fill to our ChromeGlassFill resource and it's Stroke to our GlassBorder resource
  • Set the CheckIcon control's Fill to white
  • Set the IndeterminateIcon control's Fill to our ChromeFill resource
You can see the benefit of saving our linear gradient brushes as resources - that saves us a lot of time!

Now we just need to update the states:

MouseOver State
  • Set the BackgroundOverlay Opacity to 30%
  • Set the BoxMiddleBackground Opacity to 30%
Pressed State
  • Set the BackgroundOverlay Opacity to 0%
  • Set the BoxMiddleBackground Opacity to 100%
Focused State
  • Set the ContentFocusVisualElement Opacity to 50%
And that's it! Exit the template back into the Main Page and run your solution.

In the sample at the top of the post I've bound the IsEnabled property of the TextBox and Button to the CheckBox so you can see what they look like disabled.

You can grab the solution containing the above styles here.

Saturday, April 3, 2010

A Chrome and Glass Theme - Part 2

Well - it's a long weekend here in New Zealand due to the Easter holidays, so I have some spare time to do another post in this series earlier than I would otherwise have had.

In the last post we created a resource dictionary and defined some brush and style resources to create a glassy panel. In this post we are going to define some more brush and resource styles for a chrome looking button. Here is what the button will look like:

Open up the solution from the last post - if you haven't been following along so far, you can grab the solution so far here.

Editing the Button Style
Select the Border control and create a button inside it about 140x30 in size. Click the "[Button]" button on the breadcrumb trail bar and edit a copy of the button template. Call the new style "ButtonChromeStyle" and be sure to select the "Resource Dictionary" option to create it in our ChromeGlass resource dictionary.

Blend has created a copy of the default style and control template of the button. But before we change the appearance of the elements inside the control template, click the "artists palette" icon in the breadcrumb trail so that we are editing the style itself, and not the control template inside the style.

We are going to define the chrome-looking border so select the BorderBrush property on the Properties tab. The button already has a linear gradient so add 3 more markers to the three already there and define the locations and colors as follows:
  1. #FFDCDCDC Position 0%
  2. #FF656565 Position 44.8%
  3. #FFD8D8D8 Position 68.9%
  4. #FF2B2B2B Position 92.6%
  5. #FFE9E9E9 Position 94.8%
  6. #FF494949 Position 97.2%
We are going to want to reuse this brush again so click the Advanced Properties Options button on the right of the BorderBrush property and select "Convert to new resource...". Call it "ChromeBorder".

Select the Foreground property on the style and change it to white - for some reason, the Button control doesn't seem to inherit the white foreground we set on the UserControlStyle from Part 1 in this series, so we must set it on the style.

Editing the Control Template
Now click the "Template" button on the breadcrumb trail to go back to editing the template which looks like the picture on the left.

Select the Background border element in the object tree and select it's BorderBrush. The BorderBrush is bound to the template style that we defined on the actual style - which is the chrome border. Blend sometimes gets confused so if it still shows the old blue linear gradient, don't worry; when we compile and run the solution it will do the right thing.

Now select the Background property and change the "A" (Alpha) value to zero, which should leave a transparent gap inside the button border.

We're going to define our Chrome gradient for the inside of the button so select the BackgroundGradient rectangle in the Object tree and select it's Fill property which is already a linear gradient. The button states (which we will get into later) contain some animation settings for the markers in this existing gradient. We want to clear them and the easiest way to do that is to change it to a solid color, and then change it back to a linear gradient. When you do this you will see a warning appear briefly at the top of the design area warning you that some animations have been deleted. Which is what we want.

Change the color and position of the 5 markers on the linear gradient to these values:
  1. #FFFFFFFF Position 12.7%
  2. #FF878787 Position 51.6%
  3. #FF393939 Position 53.2%
  4. #FF858585 Position 94.8%
  5. #FFFFFFFF Position 100%
And change the RadiusX and RadiusY properties both to 2. Also change the CornerRadius of the BackgroundAnimation border element to 2.

Again, we are going to reuse this gradient later so select the BackgroundGradient element (if it's not already selected), click the Advanced Property Options button and convert it to a local resource called "ChromeFill".

Although we are going to reuse this gradient, it's a little bit to bright for the button since we have specified White as the foreground (text) color on the style. We are going to use another rectangle in the button template to correct this rather than change the gradient brush resource we just created.

Select the [Grid] control inside the Background border control in the object tree, and double click the Rectangle icon on the tool palette. Reset it's margin property (which Blend has probably changed) all to 0s and set it's Stroke property to NoBrush. Also, change it's RadiusX and RadiusY values to 2.

Select it's Fill property and set it to be a linear gradient brush with the following 3 markers:
  1. #4C5F5F5F Position 0%
  2. #4CDCDCDC Position 23.4%
  3. #4C000000 Position 65.7%
We may need to use this gradient again so convert it to a local resource called "ChromeDarkeningLinear" (yup - we are going to create a radial one in a later post). The white text back on the button will now show clearly against the background.

Changing the Visual States
The last thing to do is to change the appearance for the various states that the button can be in. Click the "States" tab and you will see all the various states that a button can be in.

Select the "MouseOver" state - any changes we make to element properties now will only take effect when the mouse is over the control (in the running application). The BackgroundAnimation element already has an animation to change the opacity from 0 to 100 (it has the little red circle on it's icon in the object tree), so select the BackgroundGradient element in the Object tree and change it's opacity to 85%. This will allow a bluish color to show through.

Select the "Pressed" state. There will already be an animation for the Background border element (near the top of the object tree), but we want to change it:

  • Select the Background border element and set it's background brush to black, but change the A value to 50%.
  • Select the BackgroundAnimation element and change it's Opacity to 50%
  • Select the BackgroundGradient element and change it's Opacity to 50%
The last thing to do is to introduce a small animation to move between the Normal and MouseOver states a little more smoothly. Click the down arrow on the "Normal" state and choose the "Normal -> MouseOver" menu item. Set the transition duration that appears to 0.2. Now do the same for the MouseOver state (choosing the "MouseOver -> Normal" item).

Make sure you save your progress, and click the "[Button]" button on the breadcrumb bar to go back out to our main design area. That's the button finished, so run your application and try it out. The final design area should look something like this:

You can download a project containing the above resources here.

In the next post we are going to