Monday, 28 September 2009

Styling a WPF Volume Meter

In WPF, the complete separation of behaviour from appearance means that you can take a control such as the ProgressBar, and style it to look like any kind of meter. For a recent project, I wanted to make a audio volume meter. The look I wanted was a gradient bar, starting with green for low volumes and going to red for the highest volume:

wpf-volume-meter-1

When the volume is at half way, the progress bar should just show the left hand portion of the gradient:

wpf-volume-meter-2

Styling a ProgressBar in WPF is actually very easy. You can start with the very helpful “Simple Style” template that comes with kaxaml. You simply need to define the visuals for two parts, called PART_Track and PART_Indicator. The first is the background, while the second is the part that dynamically changes size as the current value changes.

At first it would seem trivial to create the look I am after – just create two rectangles on top of each other. The trouble is, that I only want the whole gradient to appear if the value is at maximum. Here’s it incorrectly drawing the entire gradient when the volume is low:

wpf-volume-meter-3

To work around this, I painted the entire gradient on the PART_Track. Then the PART_Indicator was made transparent. This required one further rectangle to cover up the part of the background gradient that I don’t want. I do this with a DockPanel. This allows the PART_Indicator to use its required space, while the masking rectangle fills up the remaining space on the right-hand side, covering up the background gradient.

<style targettype="{x:Type ProgressBar}" x:key="{x:Type ProgressBar}">
  <setter property="Template">
    <setter.value>
      <controltemplate targettype="{x:Type ProgressBar}">
        <grid minheight="14" minwidth="200">
          <rectangle name="PART_Track" stroke="#888888" strokethickness="1">
            <rectangle.fill>
              <lineargradientbrush startpoint="0,0" endpoint="1,0">
                <gradientstop offset="0" color="#FF00FF00"/>
                <gradientstop offset="0.9" color="#FFFFFF00"/>
                <gradientstop offset="1" color="#FFFF0000"/>
              </lineargradientbrush>
            </rectangle.fill>
          </rectangle>
          <dockpanel margin="1">
            <rectangle name="PART_Indicator">
            </rectangle>
            <rectangle name="Mask" minwidth="{TemplateBinding Width}" fill="#C0C0C0"/>
          </dockpanel>
        </grid>
      </controltemplate>
    </setter.value>
  </setter>
</style>

I think there may be an even better way to solve this using a VisualBrush, but I can’t quite get it working at the moment. I’ll post with the solution once I’ve worked it out.

Post a Comment