Click or drag to resize
DigitalRuneControl Properties and Events
UI controls are game objects

A UIControl is derived from GameObject and therefore can have game object properties (see GamePropertyT) and game object events (see GameEventT). Have a look at Game Object System to learn more about game objects, game object properties and game object events.

If you are familiar with WPF/Silverlight, you might be familiar with the types DependencyObject and DependencyProperty. The game object system provides a similar functionality to WPF dependency objects. To compare WPF/Silverlight and the DigitalRune Game UI:

In WPF/Silverlight a FrameworkElement inherits (indirectly) from DependencyObject and has DependencyProperties.

In DigitalRune Game UI a UIControl inherits from GameObject and has game object properties (GamePropertyT).

But in contrast to WPF dependency objects and properties, the DigitalRune game objects and properties address needs specific to game programming.

Leveraging the strengths of the game objects system enables many important features, like UI themes, automatic invalidation, animation, and many more.

UI control properties

The UI control properties are extended game object properties: UI control properties must be created using the new CreatePropertyT and CreateEventT methods. For example, the IsEnabled property of a UIControl is created like this:

C#
class UIControl : GameObject
{
  ...
  public static readonly int IsEnabledPropertyId = CreateProperty(
    typeof(UIControl), "IsEnabled", GamePropertyCategories.Common, null, true,
    UIPropertyOptions.AffectsRender);
 
  public bool IsEnabled
  {
    get { return GetValue<bool>(IsEnabledPropertyId); }
    set { SetValue(IsEnabledPropertyId, value); }
  }
  ...
}

That is more complicated than a normal .NET property - but also a lot more powerful. Let's dissect this code lines:

Using the static method CreatePropertyT a new UI control property is created, and the integer ID of this property is stored for public access. Everyone can access this property using:

C#
bool isEnabled = myControl.GetValue<bool>(UIControl.IsEnabledPropertyId);

or (slower and less safe because you could make a typo):

C#
bool isEnabled = myControl.GetValue<bool>("IsEnabled");

After the CreatePropertyT call, a normal .NET property IsEnabled is added which uses the GetValue/SetValue methods. This IsEnabled .NET property is added to make property access easy for the user. That means you will nearly always use

C#
bool isEnabled = myControl.IsEnabled;

instead of the more complex GetValue/SetValue methods.

Next we have a look at the parameters of CreatePropertyT in detail. The first is the type of the class that owns this property. The second is the name of the property. The third GamePropertyCategoriesCommon is simply a string that can be used in a game editor to sort the game object properties into categories. The forth parameter is a description string, which we have left out because we think the property name is already pretty self-explanatory. Then comes the property default value true.

AffectsMeasure, AffectsArrange and AffectsVisual

The last parameter of CreatePropertyT is special for UI controls. It tells the UI system whether the property influences the size (AffectsMeasure), positioning (AffectsArrange) or visual appearance (AffectsRender) of the control.

To determine the GUI graphics, the UI system makes three traversals over all GUI controls. First, it measures each control to determine how much room each control desires – this is the measure pass. Then it tells each control its final position and allotted space on the screen – this is the arrange pass. And in each frame it the controls are drawn – this is the render pass. The results of each pass are cached and are re-used in each frame.

When you change a property of a UIControl, then you need to tell the UI system whether it needs to update its cached information. For this, you can manually call InvalidateMeasure, InvalidateArrange or InvalidateVisual. (Invalidating the measure data automatically invalidates arrange and visual data. Invalidating the arrange data automatically invalidates the visual data but keeps the measure data intact. InvalidateVisual only affects drawing data but no measure or arrange data.) If you change a UI control property, the property will automatically call the appropriate invalidate method for you.

The IsEnabled property has set the AffectsRender flag because the visual appearance will usually change to indicate that the UI control is enabled or not (e.g. using grayed-out button text). An example of a property with AffectsArrange is UIControlX. This property determines the X position of a control in a Canvas or in a UIScreen. It does not influence the size of the control. UIControlWidth is the perfect example for a property that AffectsMeasure.

Now, if you have an unusual case where you want to shrink a button when it is disabled (IsEnabled == false), then you would have to call InvalidateMeasure manually.

Styling and themes

Another advantage of UI control properties is that when a controls is initialized, it will ask the IUIRenderer for default values. The standard UIRenderer class reads the data of the Theme.xml file to initialize the UI control properties. The UIRenderer can only do this for UI control properties - not for normal .NET properties. And properties will only be changed if they have not been touched by the user. That means, if you create dialog with a button and you set the Foreground color to green manually in your code, then this will have priority over the foreground color defined in the theme.

Overriding default values

Game object properties have only one global default value. UI control properties can have a default value per type of control. For example, if a control like a Button, should have class-specific default values, the method OverrideDefaultValueT can be used. Here is an example:

The UIControl base class creates a UI control property called Focusable which determines whether a control can receive input focus. The UIControl defines this property including the default value for this property. The default value is false, since most controls, like images, text blocks, panels, cannot receive the input focus. If this default value is not suitable for a control, then the control can override the default value. For example, buttons, text boxes and a lot more controls can get input focus and therefore need a different default value:

C#
// Static constructor of the ButtonBase class. 
static ButtonBase() 
{ 
  OverrideDefaultValue(typeof(ButtonBase), FocusablePropertyId, true); 
}

At first it might look like we could simply call Focusable = true in the ButtonBase constructor. However, using OverrideDefaultValueT we register a new default value in the internal metadata. So when a new button is created, it uses this default value. If the theme defines another value, the value of the theme is used. But if we call Focusable = true , then we have changed the actual value of the property. This actual value has priority over the value defined in the theme – hence the control user cannot change this value anymore using the Theme.xml. Using the OverrideDefaultValueT method the default value can be changed, and the property can still be modified in the theme.

Animation

Each UI control property can be animated using DigitalRune Animation. For example, here is a fade-in animation that animates the Opacity UI control property from to 0 to its default value over 0.3 seconds:

C#
var animation = new SingleFromToByAnimation 
{ 
  TargetProperty = "Opacity",  
  From = 0,                    
  Duration = 0.3f,             
};

The animation can be started on a control using

C#
animationService.StartAnimation(animation, myControl);
Events

Each UI control property is automatically an event. Do you want to be notified when a property is disabled? No problem, just get the UI control property from the control:

C#
GameProperty<bool> isEnabledProperty= myControl.Properties.Get<bool>(UIControl.IsClickedPropertyId);

GamePropertyT is a structure that represents the game object property. It has a Changing and a Changed event which you can subscribe to, for example:

C#
isEnabledProperty.Changed += (s, e) => 
{ 
  if (e.NewValue == true) 
  { 
    /* React to enabling of control */
  }
};

The GamePropertyT structure also has a Change property, which is an event handler. You can use this to connect two UI control properties. For example, the ScrollViewer control uses a ScrollBar control internally. When the ScrollViewer is loaded it connects its own HorizontalOffset property with the Value property of the ScrollBar:

C#
protected override void OnLoad() 
{ 
  ...   
  // Connect ScrollBar.Value with HorizontalOffset (two-way connection). 
  var scrollBarValue = _horizontalScrollBar.Properties.Get<float>(RangeBase.ValuePropertyId); 
  var horizontalOffset = Properties.Get<float>(HorizontalOffsetPropertyId); 
  scrollBarValue.Changed += horizontalOffset.Change; 
  horizontalOffset.Changed += scrollBarValue.Change; 
  ... 
}

This connects the properties: Changing one property automatically changes the other property.

Other features of UI control properties

UI control properties have several other nice features.

However, UI control properties are slower than normal .NET properties. Only use UI control properties if really needed. The controls in DigitalRune Game UI have UI control properties as well as normal .NET properties. If you want to set a property in the UI theme XML or if you want to animate a property, then you need a UI control property. Otherwise a normal .NET property might do.

UI control events

The game object system also supports game object events, which are very similar to game object properties. The ButtonBaseClick event is such an event:

C#
public static readonly int ClickEventId = CreateEvent( 
  typeof(ButtonBase), "Click", GamePropertyCategories.Default, null, EventArgs.Empty); 
      
public event EventHandler<eventargs> Click 
{ 
  add 
  { 
    var click = Events.Get<eventargs>(ClickEventId); 
    click.Event += value; 
  } 
  remove 
  { 
    var click = Events.Get<eventargs>(ClickEventId); 
    click.Event -= value; 
  } 
}

This looks very similar to the declaration of a UI control property. The CreateEventT call defines the event. The Click event is a normal .NET event that is only there for convenience and uses the UI control event.

UI control events are useful to wire up events, e.g. in a game editor.