What is this “DataContext” you speak of?

July 14, 2012

I frequently see new WPF users confused about what the DataContext is, and how it is used. Hopefully, this can help clarify what the DataContext is, and how it is used.

What is the DataContext?

In WPF, there are two layers to an application: the UI layer and the Data layer.

The Data layer for an application starts out as null, and you can set it using the DataContext property. All UI objects will inherit their DataContext from their parent unless you specify otherwise.

When using the Model-View-ViewModel (MVVM) Design Pattern, the DataContext (Data Layer) is your application, while UI objects, like Buttons, Labels, DataGrids, and even Windows, are all just user-friendly items that allow a user to easily interact with the DataContext, which is your actual application and is typically comprised of ViewModels and Models.

How is it used

Whenever you do a basic binding in WPF, you are binding to the DataContext.

For example, when you write

<Label Name="myLabel" Content="{Binding Path=Name}" />

you are binding to myLabel.DataContext.Name, and not to myLabel.Name.

Other binding properties, such as ElementName or RelativeSource, can be used to tell the binding to lookup the property in something other than the current DataContext.

An Example

Lets start with a regular Window. Without setting the DataContext, the window still displays but there is no data behind it.

<Window x:Name="MyWindow" ...>
   ...
</Window>

Now suppose we set the DataContext to an object of type ClassA in the code-behind when this Window initializes:

public partial class MyWindow: Window
{
    public MyWindow()
    {
       InitializeComponent();
       this.DataContext = new ClassA();
    }
}

Now the data layer behind that the Window is an object of type ClassA.

If ClassA has a property called Name, I could add a Label to the window and bind it to Name property of the DataContext, and whatever value is stored in ClassA.Name would get displayed.

<Window x:Name="MyWindow" ...>
   <Label Content="{Binding Name}" />
</Window>

Now, suppose ClassA has a property called ClassB, and both classes have a property called Name. Here is a block of XAML which illustrates how the DataContext works. It also includes an example of how a control would refer to a property not in its own DataContext.

<!-- DataContext set to ClassA in initialization code -->
<Window x:Name="MyWindow"> 

    <!-- DataContext here is not specified, so it's inherited 
         from its parent's DataContext, which is ClassA -->
    <StackPanel> 

        <!-- DataContext inherited from parent, which is 
             ClassA, so this will display ClassA.Name -->
        <Label Content="{Binding Name}" />

         <!-- DataContext is still ClassA, however we are 
              setting it to ClassA.ClassB with a binding -->
        <StackPanel DataContext="{Binding ClassB}">

            <!-- DataContext inherited from parent, which is 
                 ClassB, so this will display ClassB.Name -->
            <Label Content="{Binding Name}" />

            <!-- DataContext is still ClassB, but we are 
                 binding to the Window's DataContext.Name, 
                 which is ClassA.Name -->
            <Label Content="{Binding 
                       ElementName=MyWindow, 
                       Path=DataContext.Name}" /> 
        </StackPanel>

        <!-- We've left the StackPanel with its DataContext 
             bound to ClassB, so this Label's DataContext 
             is ClassA (inherited from parent StackPanel), 
             and we are binding to ClassA.ClassB.Name -->
        <Label Content="{Binding ClassB.Name}" />
    </StackPanel>
</Window>

As you can see, all the basic bindings look for their value in the data layer (DataContext) of the UI object

Summary

So to summarize, WPF applications have two layers: the UI layer and the Data layer. The data layer for an application starts out as null, and can be set using the DataContext property. UI objects without a DataContext set will inherit their data layer from their parent object. Bindings are used to look up values in the data layer, and display them in the UI layer.

When using the MVVM design pattern, the data layer is your application, while the UI layer just provides a user-friendly way to access the Data layer.


The Math Converter

August 20, 2011

Quite often I have wanted to adjust a bound value in my XAML by a mathematical formula.

For example, I might want to size a control to 50% of it’s parent’s size. Or I’ll want to position it 30% down and 20% left of a parent panel. Or I might even want to make a control’s width something like 50% of (WindowHeight – 200). Of course, I could always write converters for this, but I got tired of doing that repeatedly so sat down one day to write one big MathConverter.

Using the Math Converter

Using the MathConverter is simple. All you have to do is pass the Converter a math equation as the ConverterParameter, substituting @VALUE for your bound value. The equation can consist of numbers, basic operators (+, -, *, /, %), parentheses, and @VALUE.

For example, if you wanted a control whose height was 50% of the current Window Size, you would create a binding that looked like this:

Height="{Binding ElementName=RootWindow, Path=ActualHeight,
                 Converter={StaticResource MathConverter},
                 ConverterParameter=@VALUE/2}"

Or if you wanted to make the control’s width equal to 30% of (WindowWidth – 200) your binding might look like this:

Width="{Binding ElementName=RootWindow, Path=ActualWidth,
                Converter={StaticResource MathConverter},
                ConverterParameter=((@VALUE-200)*.3)}"

The Math Converter

Here’s the actual converter code.

It takes the ConverterParameter, substitutes the bound value for @VALUE, then starts reading the equation from left-to-right. If it encounters an operator, it performs the specified operation on the previous number and the next number in the list. If it encounters a parentheses, it handles the equation inside the group first, then replaces the grouped equation in the string with the result of the grouped equation. Any character it finds that is not a number, operator, or parentheses throws an exception.

// Does a math equation on the bound value.
// Use @VALUE in your mathEquation as a substitute for bound value
// Operator order is parenthesis first, then Left-To-Right (no operator precedence)
public class MathConverter : IValueConverter
{
    private static readonly char[] _allOperators = new[] { '+', '-', '*', '/', '%', '(', ')' };

    private static readonly List<string> _grouping = new List<string> { "(", ")" };
    private static readonly List<string> _operators = new List<string> { "+", "-", "*", "/", "%" };

    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        // Parse value into equation and remove spaces
        var mathEquation = parameter as string;
        mathEquation = mathEquation.Replace(" ", "");
        mathEquation = mathEquation.Replace("@VALUE", value.ToString());

        // Validate values and get list of numbers in equation
        var numbers = new List<double>();
        double tmp;

        foreach (string s in mathEquation.Split(_allOperators))
        {
            if (s != string.Empty)
            {
                if (double.TryParse(s, out tmp))
                {
                    numbers.Add(tmp);
                }
                else
                {
                    // Handle Error - Some non-numeric, operator, or grouping character found in string
                    throw new InvalidCastException();
                }
            }
        }

        // Begin parsing method
        EvaluateMathString(ref mathEquation, ref numbers, 0);

        // After parsing the numbers list should only have one value - the total
        return numbers[0];
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion

    // Evaluates a mathematical string and keeps track of the results in a List<double> of numbers
    private void EvaluateMathString(ref string mathEquation, ref List<double> numbers, int index)
    {
        // Loop through each mathemtaical token in the equation
        string token = GetNextToken(mathEquation);

        while (token != string.Empty)
        {
            // Remove token from mathEquation
            mathEquation = mathEquation.Remove(0, token.Length);

            // If token is a grouping character, it affects program flow
            if (_grouping.Contains(token))
            {
                switch (token)
                {
                    case "(":
                        EvaluateMathString(ref mathEquation, ref numbers, index);
                        break;

                    case ")":
                        return;
                }
            }

            // If token is an operator, do requested operation
            if (_operators.Contains(token))
            {
                // If next token after operator is a parenthesis, call method recursively
                string nextToken = GetNextToken(mathEquation);
                if (nextToken == "(")
                {
                    EvaluateMathString(ref mathEquation, ref numbers, index + 1);
                }

                // Verify that enough numbers exist in the List<double> to complete the operation
                // and that the next token is either the number expected, or it was a ( meaning
                // that this was called recursively and that the number changed
                if (numbers.Count > (index + 1) &&
                    (double.Parse(nextToken) == numbers[index + 1] || nextToken == "("))
                {
                    switch (token)
                    {
                        case "+":
                            numbers[index] = numbers[index] + numbers[index + 1];
                            break;
                        case "-":
                            numbers[index] = numbers[index] - numbers[index + 1];
                            break;
                        case "*":
                            numbers[index] = numbers[index] * numbers[index + 1];
                            break;
                        case "/":
                            numbers[index] = numbers[index] / numbers[index + 1];
                            break;
                        case "%":
                            numbers[index] = numbers[index] % numbers[index + 1];
                            break;
                    }
                    numbers.RemoveAt(index + 1);
                }
                else
                {
                    // Handle Error - Next token is not the expected number
                    throw new FormatException("Next token is not the expected number");
                }
            }

            token = GetNextToken(mathEquation);
        }
    }

    // Gets the next mathematical token in the equation
    private string GetNextToken(string mathEquation)
    {
        // If we're at the end of the equation, return string.empty
        if (mathEquation == string.Empty)
        {
            return string.Empty;
        }

        // Get next operator or numeric value in equation and return it
        string tmp = "";
        foreach (char c in mathEquation)
        {
            if (_allOperators.Contains(c))
            {
                return (tmp == "" ? c.ToString() : tmp);
            }
            else
            {
                tmp += c;
            }
        }

        return tmp;
    }
}

One day I plan on expanding this further to use an IMultiValueConverter that accepts multiple bindings, but I haven’t had a need for that yet so it hasn’t gotten done.