Nathan Dunlap

Nathan Dunlap

Customizing button using Avalon styling...

Creating a customized GelButton

A project for this article is located at http://www.dunlap.cc/lhblog/styles/GelButtons.zip

Creating visuals for custom designed controls in markup is really powerful with Avalon. It might seem a little overwhelming at first because there are so many things you need to know about. But in the end the developer ends up with more power than they have ever had for creating these controls. This article walks through somewhat step by step how to get rid of the existing visuals of a button and how to replace them with your own. The button visuals that I create in this article are capable of stretching and resizing to the button's content without distorting, and will render well on a high definition monitor because they use vectors. I also make it so the button is easily color themed as well.

Getting Started

First we need to create the button...

<Button>Push me!</Button>

It's easy to customize button by just setting the background property to it. So if you want button to be themed to your  website you can say:

<Button Background=”Red”>Push me!</Button>

This is actually more cool than it appears at first. The button element is actually made up of multiple vector shapes that have their fills aliased to the background property on button. So in effect you are setting the Fill value of a bunch of rectangles to red. In the PDC “slate” version of the buttons there are a few rectangles positioned near the edges of the main rectangle that have a transparent white fill so the button gets the beveled effect. The result is a nicely themed button that still maintains the visual style that the designer intended but allows for color themes. If you set Background on a button in IE you will lose the style from the current theme and end up with only a red rectangle.

But for my crazy  themed application/web site I want to create buttons that don't look anything like the standard windows style. I want my buttons to be juicy fruity gel buttons that really brand the experience and stick out. (Caveat: I'm not condoning abandoning the standard windows button styles. The AERO style is gorgeous and your app will benefit greatly by sticking to AERO design guidelines. Read those here: http://msdn.microsoft.com/Longhorn/understanding/ux/default.aspx). However if you want to know how to get ultimate control over your button style, read on.

Destroying Buttons with VisualTree

So to get complete control over the button's visuals you need to use a VisualTree in your style. This effectively wipes out all the elements that currently make up a control and let's you start from scratch.

<Style def:Name="GelButton">
     <Button />
     <Style.VisualTree>
    </Style.VisualTree>
</Style>

<Button Style="{GelButton}" />

If you render that you will see nothing. That is because you have removed all the VisualTree. Now we just need to add some visuals.

First I set up the VisualTree's root element to be a Canvas. This way I still get layout for the button container itself. But I can absolutely position the shape elements that will make the visuals.

<Style def:Name="GelButton">
     <Button Width="150" Height="30" />
     <Style.VisualTree>
<!-- Canvas to absolutely position the shapes that will draw the button -->
        <Canvas Width="100%" Height="100%">
<!-- This is a rounded rectangle that draws the background of the button -->
          <Rectangle RadiusX="15" RadiusY="15" Width="100%" Height="100%" Fill="red"  />
        </Canvas>
    </Style.VisualTree>
</Style>

So if you render this you will see you button show up as a red round rectangle.

Try setting the button to different heights and widths and notice that you the rectangle resizes appropriately without distorting corners. This is cool because now your text string in your button can be any length and your button will resize to match with out any stretching (look Ma no ninegrids or weird table layout).

Where's the beef? I mean where's the text?

But hey wait. Something is still missing... Where is the text string for my button? Well since I destroyed the VisualTree of the standard style button, I also destroyed the text element in the button's tree that rendered the content. So I need to put that back in there. The way I do this is by adding a ContentPresenter element. I then alias the ContentPresenter.Content

<Style def:Name="GelButton">
     <Button />
     <Style.VisualTree>
        <Canvas Width="100%" Height="100%">
          <Rectangle RadiusX="15" RadiusY="15" Width="100%" Height="100%" Fill="red"  />
<!-- This is the element that renders the text in the button -->
            <ContentPresenter ContentControl.Content="*Alias(Target = Content)" />
        </Canvas>
    </Style.VisualTree>
</Style>

Ok, now you should be able to see the text in the button.

But its pretty ugly since the text is just going to sit to the left. Lets put the ContentPresenter in a FlowPanel so we can set HorizontalAlignment and VerticalAlignment. And then we will create a new style so that we can change the font properties on the ContentPresenter.

<!-- This is the style  that defines the font properties on the ContentPresenter element in the GelButton style -->
<Style def:Name="GelButtonText">
<ContentPresenter />
   <Style.VisualTree>
     <Text FontFamily="arial" Foreground="white" FontWeight="bold" FontSize="13pt" >
       <Text.TextContent>
          <Bind/>
       </Text.TextContent>
     </Text>
   </Style.VisualTree>
</Style>

<Style def:Name="GelButton">
     <Button />
     <Style.VisualTree>
        <Canvas Width="100%" Height="100%">
          <Rectangle RadiusX="15" RadiusY="15" Width="100%" Height="100%" Fill="red"  />
<!-- This is the element that renders the text in the button -->
         <FlowPanel HorizontalAlignment="center" VerticalAlignment="center"
Width="100%" Height="100%">
            <ContentPresenter ContentControl.Content="*Alias(Target = Content)" />
         </FlowPanel>
        </Canvas>
    </Style.VisualTree>
</Style>

Now the button renders with white 13pt arial text that will always center itself no matter what size the button is.

If you go crazy you can add alot of embellishments. For example I have added a few more rectangles to create a dropshadow effect and to create glossy effect.

Why so many DockPanels?

If you are wondering why I have my shapes inside of DockPanels, it is because I am using the Margin property to squeeze the shape according to the layout. If you set a shape to 100% height and width and then set a margin on it the shape actually measures the size first then applies the margin so it shifts itself over in the layout. The way I get around this is by wrapping my shape in a DockPanel and then instead of setting 100% width and height I set DockPanel.Dock="Fill". Then I can use margin to squeeze my shape according to what I want for the layout.

Adding Interactivity

Now what I want to do is give the button some interactivity. I do a couple of tricks here. One I put a rectangle in the mix that has a radial gradient that is white at the center an radiates to transparent. To create the hover effect I want to set the opacity of that rectangle to "0" as the default, but when the mouse is over the button I want to raise the opacity of that rectangle.

To do this what I want to do is give the button a def:StyleID so that I can target that specific element in the style with a VisualTrigger.

<Rectangle def:StyleID="RadialGradientShine" RadiusX="15" RadiusY="15" DockPanel.Dock="Fill" Fill="RadialGradient #99ffffff transparent" Opacity="0"  />

Now my def:StyleID is specified here is how I target that element.

<Style.VisualTriggers>
    <PropertyTrigger Property="IsMouseOver" Value="true">
        <Set Target="RadialGradientShine" PropertyPath="Opacity" Value="1" />
    </PropertyTrigger>
</Style.VisualTriggers>

This is using a property trigger so you basically change the value of one property when another property is set to an expected value. In this case IsMouseOver is true so we set the opacity value on RadialGradientShine from 0 to 1.

In the GelButton design I create shifting movement of the button to create an effect for the pressed state. I do this by off-setting the Canvas.Top and Canvas.Left values by a few pixels and then I turn off the RadialGradient shine again by setting the opacity back to 0.

Customizing the button's color

Now to get some of the original buttons default behavior again, I am going to use alias again to replace the Fill on the rectangle and alias it to the Background property.

<Style def:Name="GelButton">
     <Button />
     <Style.VisualTree>
        <Canvas Width="100%" Height="100%">
<!-- This rectangle now is aliased to the background property so it will be whatever color is set on the button inline. -->
          <Rectangle RadiusX="15" RadiusY="15" Width="100%" Height="100%" Fill="*Alias(Target = Background)"  />

         <FlowPanel HorizontalAlignment="center" VerticalAlignment="center"
Width="100%" Height="100%">
            <ContentPresenter ContentControl.Content="*Alias(Target = Content)" />
         </FlowPanel>
        </Canvas>
    </Style.VisualTree>
</Style>

Now the buttons background described inline will update the rectangle accordingly. So we can have different color buttons.

<Button Style="{GelButton}" Background="red">Push Me!</Button>
<Button Style="{GelButton}" Background="blue">Push Me!</Button>
<Button Style="{GelButton}" Background="yellow">Push Me!</Button> ...

This is what it looks like:

So now I get a button that is completely vector drawn. It will handle transforms like scale and rotate well, it stretches and resizes gracefully and is easy to customize the color on. Here is a snapshot of what my custom Gel Button looks like on my latest build with ImageEffectBlur on the DropShadow and the glossy parts of the button. This image also shows the buttons with a TransformDecorator wrapped around them so we can see how well they handle being scaled and rotated. Also in this version notice that I put multiple ContentPresenters so I could simulated an embossed look with the text. In some ways, I like the hard edge of the gloss effect in the PDC version better than the blur effect below. But I still think its amazing that you can apply blur effects on shape elements to control the definition on the gloss.

 

PostTypeIcon
47,869 Views