The code for this part of the tutorial is in the Constraint.cs file of the Tutorial project.

A constraint in Bling ensures that a property is kept equal with an expression even as the expression's value changes. Consider the following Bling code:

CanvasBl canvas = ...;
SliderBl slider = new SliderBl(canvas) {
  Width = canvas.Width,
  Left = 0, 
  Bottom = canvas.Height,
  Minimum = 0, Maximum = 360,
  Value = 0,
};

This code creates a slider that is as wide as its containing canvas and is positioned at the bottom of this canvas. The assignments in this code create constraints, meaning that the left-hand assigned to property will be updated as the assigned from expression changes in value. In this example, the slider will expand in width as the canvas is resized to make it wider, and it will be moved lower as the canvas is made taller. By expressing size and position through constraints, its possible to create custom UI layouts with very little code. Consider the following addition to the above code:

CheckBoxBl checkBox = new CheckBoxBl(canvas) {
  Height = slider.Height,
  LeftTop = slider.RightTop,
};
slider.Width = canvas.Width - checkBox.Width;

This code creates a check box that exists to the right of the slider at the bottom of the canvas. The height of the check box is the same as slider's, while the slider's width is reduced by the check box's width to make room for the check box. Beyond being used to create custom layouts, constraints are also useful in encoding dynamic UI behavior. Consider the following code:

checkBox.Content = checkBox.IsChecked.Condition("Red", "Blue");
LabelBl label = new LabelBl(canvas) {
  Foreground = checkBox.IsChecked.Condition(Brushes.Red, Brushes.Blue),
  Content = "Rotate: ".Bl() + slider.Value + " degrees",
  RenderTransform = { Rotate = slider.Value.ToDegrees },
  CenterPosition = canvas.CenterSize,
  Font = { Size = 80, Weight = FontWeights.SemiBold },
};

The four assignments in this code create four dynamic behaviors. The first assignment causes the check boxes content to be "Red" when the check box is checked and "Blue" otherwise. The condition method in a boolean bling is analogous in behavior to ?/: syntax in C#; e.g., checkBox.IsChecked ? "Red" : "Blue" would have been the preferred syntax if C# supported overloading of ?/:. Conditional assignment through if statements is not supported as the truth value of a boolean bling can change over time. The second assignment then uses a condition on the check box's IsChecked property to color the label's foreground red when checked, and blue otherwise.

The third assignment causes the content of a label to include the current value of a slider. If the current value of the slider is zero, the content of the label will be "Rotate 0 degrees"; and if the current value of the slider is 137, the content of the label will be "Rotate 137 degrees". The fourth assignment makes this assertion true: the label is rotated via a render transform by the degree value of the slider. Angle properties in Bling are typed as degree or radian angle blings, so the slider value is transformed into a degree angle via the ToDegree property. As a result, the label rotates as the slider thumb is moved back and forth by the user.

Here is what the result looks like: c0.jpg

Animation

We can also add some animation to our example:

ButtonBl button = new ButtonBl(canvas) {
  LeftBottom = slider.LeftTop,
  Content = "Animate",
  Click = () => {
    slider.Value.Animate().Duration(500).AutoReverse().Forever().To = 360;
  },
};
slider.OnMouseEnter = () => slider.Value.Stop(true);

This code creates a button that animates the slider's value when clicked. The duration of the animation is 500 milliseconds, it will reverse after the destination is reached, and the animation will run forever until stopped manually. When the user clicks the button, the slider then goes back in forth from its current value to 360, causing the label to rotate along with the slider. The animation is stopped as soon as the mouse enters the slider, and the current value of the animation is captured by the slider's value. Key frame animation is also easy to express:

ButtonBl button2 = new ButtonBl(canvas) {
  Content = "Animate2",
  LeftBottom = button.RightBottom,
  Click = () => {
    slider.Value.Animate().Duration(1000).AutoReverse().Forever().Percents(0, 0.10, 1.0).Keys(0, 180, 360);
  },
};

This code animates the slider value so that it starts at zero, quickly moves to 180 degrees in 100 milliseconds (10% of one second), then slowly moves to 360 degrees in the remaining 900 milliseconds.

You can also tween animations using a custom equation (of type Func<DoubleBl,DoubleBl>) or a pre-defined Penner Easing equation. Penner easing equations are members of DoubleBl's Ease property, and are each separated into In, Out, InOut, and OutIn options. Example:


ButtonBl button3 = new ButtonBl(canvas) {
  LeftBottom = button2.RightBottom,
  Content = "Animate3",
  Click = () => {
    slider.Value = 0;
    slider.Value.Animate().Tween(t => t.Ease.Circ.Out).Duration(500).AutoReverse().Forever().To = 360;
  },
};

Now assignment

A constraint is a declarative and persistent assignment that ensures a relationship is held until the property is re-assigned. Because a constraint is declarative, it cannot be used to encode discrete assignment behavior; e.g., incrementing the value of a double property. Consider:

ButtonBl button4 = new ButtonBl(canvas) {
  LeftBottom = button3.RightBottom,
  Content = "Increase",
  Click = () => slider.Value += 1,
};

The last assignment in the above code, slider.Value += 1, will throw an exception because it introduces a loop into the value of the slider: the current value of the slider depends on its current value. Instead, a discrete assignment needs to be performed through the Now operator:

ButtonBl button3 = new ButtonBl(canvas) {
  LeftBottom = button2.RightBottom,
  Content = "Increase",
  Click = () => slider.Value.Now += 1,
};

The Now operator causes the value of the assigned property to be set to the value of the assignment as it is computed at the time of the assignment. In this case, the slider's current value will be computed, one will be added to it, and this will become the slider's new current value.

Constraints, animation, and discrete assignments are translated into uses of WPF's data binding and animation engine. A constraint is realized as WPF data binding, an animation is realized by a WPF animation, and a discrete assignment is realized as a WPF local assignment. WPF prioritizes what the value of a property is if it is set in different ways: animation has priority over a discrete assignment, while discrete assignment has priority over constraints. Otherwise, an animation/now assignment/constraint that targets the same property of an earlier animation/now assignment/constraint will override the earlier one. Otherwise, an assignment can be explicitly cleared through one of the following method calls:
  • ClearConstraint() will clear a bling value's current constraint; e.g., button.LeftTop.ClearConstraint() will remove the constraints that affect a button's Left and Top properties.
  • ClearNow() will clear a bling value's current now assignment; e.g., button.LeftTop.ClearNow() will remove the now assignment that effect a button's Left and Top properties.
  • Stop(bool Capture) will clear a bling value's current animation. If Capture is true, the last value of the animation becomes a now assignment on the target bling value; e.g., button.LeftTop.Stop(true) will remove the animatino of a button's Left and Top properties, where the properties will retain the current value of the animation as a now assignment.

Last edited Jul 20, 2009 at 6:40 AM by mcdirmid, version 13

Comments

No comments yet.