Introduction
I created a WPF UI design that that put a dark shadow over a UI section when it was disabled, not just disabled the controls. This required a boarder who’s XAML that had to be after the definition of the controls. It was considered desirable to create a Control
that provided the container for the controls to be disabled, and also had the Border that did the shadow. I decided that the best solution was to create the control in C#, inheriting form the base control, in this case two controls: a Border
and a Grid
. To do this I had instantiate a Border
that would create the shadow and override the ArrangeOverride
, GetVisualChild
, and VisualChildrenCount
. I had done something like this before to create a numeric up-down box, which was a lot more complex than this. Its simplicity makes it a much better example to demonstrate how to implement these methods. The only issue as far as this being a good example on how to implement these methods is that the size of the base Control
is not adjusted, and the shadow Border
is the same size as the base Control
. Still it is a good starting example..
Implementation
The implementation of the overridden methods is as follows:
protected override Size ArrangeOverride(Size arrangeSize)
{
Rect rect = new Rect(new Point(0, 0), arrangeSize);
base.ArrangeOverride(arrangeSize);
_shadowBorder.Arrange(rect);
return arrangeSize;
}
protected override Visual GetVisualChild(int index)
{
return index < base.VisualChildrenCount ? base.GetVisualChild(index) : _shadowBorder;
}
protected override int VisualChildrenCount => base.VisualChildrenCount + 1;
The Border
defined in the Control
’s constructor and the shadowing is controlled by setting this Border
’s Visibility
between Hidden
and Visible
. A DependencyProperty
is with the name property “IsNotShadowed” is used to set this visibility along with setting the IsEnabled
property of the control. The property is named “IsNotShadowed” so that it mirrors the IsEnabled
true
and false
values. This is a personal preference, and also means that if a CheckBox
is used to enable/shadow the Control
can be used directly with standard on/off values. The DependencyProperty
is defined as follows:
public bool? IsNotShadowed
{
get { return (bool?)GetValue(IsNotShadowedProperty); }
set { SetValue(IsNotShadowedProperty, value); }
}
public static readonly DependencyProperty IsNotShadowedProperty =
DependencyProperty.Register("IsNotShadowed", typeof(bool?), typeof(ShadowGrid),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.AffectsRender, OnIsNotShadowedChanged));
private static void OnIsNotShadowedChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = (ShadowGrid)d;
var enabled = (bool?)e.NewValue ?? true;
control.IsEnabled = enabled;
control._shadowBorder.Visibility = enabled ? Visibility.Hidden : Visibility.Visible;
}
Note that this dependency property is of type Nullable<bool>
. That is because it is designed to interface
to the CheckBox
IsChecked
property, which is also Nullable<bool>. However, the effect of the null value is the same as the true
value. I had to make a number of changes to deal with Binding
IsNotShadowed
to an indeterminate value—the shadowing was not matching the bound IsChecked
value of a CheckBox
. Several clicks of the CheckBox
were required have the values of both the IsEnabled
and shadowed values correspond to the CheckBox
when the Checkbox
was not bound to a bool
value. Also had to set the initial value of the IsNotShadowed
to false in the constructor while the initial default value of the DependencyProperty
is false
.
What is interesting is that if the controls within the Panel
were not disabled when the Panel
is shadowed, they can be selected. This is very unlike the use of a Border
to do the shadow. The nice thing is that at design time a specific Control
can be selected, making navigation much easier.
Using the code
It is actually very simple to use this control once the namespace is defined:
<shadowedControlsExample:ShadowGrid
IsNotShadowed="{Binding ElementName=GridCheckBox, Path=IsChecked}">
<ToggleButton Width="80" Margin="3" Content="Button A" />
</shadowedControlsExample:ShadowGrid>
History
2015/11/21: Initial version.