Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Scaling (visual) between a min and max value (e.g., level bar)

0.00/5 (No votes)
9 Apr 2010 1  
If you need some kind of level bar / meter, take a look!
MinMaxScaling

Introduction

In many projects that are related to process automation, it is necessary to display equipments and the current state in a visual representation. Many equipments, for example, a tank, have some kind of meter that should display a current value between a range (min / max). Let's have a look at how we can do that in a dynamic way using the WPF Viewbox and some simple calculations.

Structure

For this example, I used the following structure:

  • The image we see above is a rendered WPF UserControl that should represent a tank. It does the following:
    • It has code-behind properties for min and max parameters and for the current level.
    • There is also a property that scales the current level from min / max to 0 / 1 (so 0...100%).
  • The control is instantiated several times in the main window of the application.

The Equipment User Control

First, let's have a look at the XAML:

<UserControl x:Class="Technewlogic.Samples.MinMaxScaling.Equipment"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:converter="clr-namespace:Technewlogic.Samples.MinMaxScaling"
    x:Name="me">

<Grid Margin="5" DataContext="{Binding ElementName=me}">
    <Rectangle x:Name="backgroundRect" Fill="LightGray" 
      Stroke="DarkGray" StrokeThickness="2" 
      RadiusX="5" RadiusY="5" />
    
    <DockPanel LastChildFill="True" Margin="5">
        
        <!-- Enter Data Section -->
        <Grid Margin="3" DockPanel.Dock="Left">
            <StackPanel Orientation="Horizontal" 
                       VerticalAlignment="Top" 
                       HorizontalAlignment="Right">
                <TextBlock Text="Max Level:" Margin="2" 
                       VerticalAlignment="Center" />
                <TextBox Text="{Binding Max, UpdateSourceTrigger=PropertyChanged}" 
                       Width="30" Margin="2" />
            </StackPanel>
            <StackPanel Orientation="Horizontal" 
                   VerticalAlignment="Center" 
                   HorizontalAlignment="Right">
                <TextBlock Text="Current Level:" FontWeight="Bold" 
                   Margin="2" VerticalAlignment="Center" />
                <TextBox Text="{Binding CurrentLevel, 
				UpdateSourceTrigger=PropertyChanged}" 
                         Width="30" Margin="2" />
            </StackPanel>
            <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" 
                          HorizontalAlignment="Right">
                <TextBlock Text="Min Level:" Margin="2" 
                          VerticalAlignment="Center" />
                <TextBox Text="{Binding Min, UpdateSourceTrigger=PropertyChanged}" 
                          Width="30" Margin="2" />
            </StackPanel>
        </Grid>

        <!-- Level Section -->

        <Border x:Name="LevelSection" CornerRadius="3" 
                   BorderBrush="DarkGray" Background="Gray" 
                   BorderThickness="1" Margin="3">
            <Viewbox Stretch="Fill">
                <StackPanel Orientation="Horizontal">
                    <Rectangle Fill="Red" Height="1" Width="0" />
                    <Rectangle Fill="Blue" Width="1" 
                            VerticalAlignment="Bottom" Height="{Binding ScaledValue}" />
                </StackPanel>
            </Viewbox>
        </Border>
    </DockPanel>
</Grid>
</UserControl>

Quite simple!

In the "Enter Data Section", we just bind some textboxes to the Min, Max, and CurrentLevel properties of our control, so that we can play a little bit with the values and see what happens.

Let's have a look at what happens in the "Level Section": here, we have a border that is the last element of its parent DockPanel, so this means that the border completely fills the remaining space of the DockPanel. Inside the border, we have a Viewbox that should stretch its content to fill the remaining space (in other words, the content of the Viewbox should cover the complete remaining space of our UserControl).

What we want to achieve is that the level meter has a Dark Gray background (coming from the border) and then have a blue bar showing the scaled value.

Mechanism.png

The trick is that inside the Viewbox, we "limit" everything to 1 (Height and Width) to have a "logical" or "normalized" size inside the Viewbox. There is an invisible Rectangle with Height = 1 to limit the height, and our blue level Rectangle with Width = 1 (to limit the width). The Viewbox then scales everything to "real" size.

The only thing left open is the scaling in the code-behind of our UserControl.

private double _min = 0d;
public double Min
{
    get { return _min; }
    set
    {
        _min = value;
        OnPropertyChanged("Min");
        OnPropertyChanged("ScaledValue");
    }
}

private double _max = 100d;
public double Max
{
    get { return _max; }
    set
    {
        _max = value;
        OnPropertyChanged("Max");
        OnPropertyChanged("ScaledValue");
    }
}

private double _currentLevel = default(double);
public double CurrentLevel
{
    get { return _currentLevel; }
    set
    {
        _currentLevel = value;
        OnPropertyChanged("CurrentLevel");
        OnPropertyChanged("ScaledValue");
    }
}

public double ScaledValue
{
    get
    {
        var scaledValue = CurrentLevel / (Max - Min) - (Min / (Max - Min));
        return scaledValue;
    }
}

Notice that on each change of Min, Max, or CurrentLevel, we also notify that the ScaledValue has changed, because this property depends on those. Mathematically, we normalize from Min / Max to 0 / 1:

Math.png

That's it!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here