Friday, November 24, 2006

Ribbon controls are legal!

Here is the news that Microsoft have launched a licensing program for those who wish to clone the Office 2007 UI (from Jensen Harris' blog). This is a royalty-free scheme whereby anyone wishing to use part of the Office 2007 user interface (e.g. the ribbon) can recreate it in their applications (note that this offering does not provide any code).

There are some restrictions applied however, in particular that the interface must adhere to the supplied UI guidelines. These are very detailed so places quite a burden on the programmer implementing the controls; however this should at least ensure that the end user can confidently use a ribbon-enabled application with the existing knowledge of how the UI should work. Microsoft have done a great job in explaining the requirements - for example they describe exactly how the ribbon should resize - so I'm looking forward to having an accurate check-list of requirements to implement in my controls.

For more information check out the links in Jensen' blog post...

Sunday, November 05, 2006

From the shadows

When styling controls that have a Popup element (e.g. menus, combo boxes, etc.) a common visual cue is a drop-shadow below the pop-up. WPF contains a DropShadowBitmapEffect that will do this all for us, hence,

<Border ...>
<Border.BitmapEffect>
<DropShadowBitmapEffect Softness=".5" ShadowDepth="5" Color="Black"/>
</Border.BitmapEffect>
</Border>


The result is shown on the left below. However, the DropShadowBitmapEffect is a bit overkill for our needs. It is designed to add drop-shadows to arbitrary shapes (for example the text below) whereas a pop-up is generally rectangular.


If you inspect the default WPF styles you will find that a drop shadow is applied via a Decorator called Microsoft.Windows.Themes.SystemDropShadowChrome. This however is hidden away in PresentationFramework.Luna.dll and designed for use by the default styles.

For my controls I have developed my own decorator to do a similar job. Firstly I have decided to make some assumptions to improve performance - namely that the drop-shadow colour is always black, and that it is always to the bottom-right. All that is required then is to implement a custom "Decorator". Decorators are controls that add some additional rendering to a single element (of course that element may be a Panel that contains several other elements). We can then draw a drop-shadow in the OnRender method consisting of a set of linear and radial gradients to mimic the shadow.

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows

class ShadowChrome : Decorator
{
// *** Fields ***

private static SolidColorBrush backgroundBrush;
private static LinearGradientBrush rightBrush;
private static LinearGradientBrush bottomBrush;
private static RadialGradientBrush bottomRightBrush;
private static RadialGradientBrush topRightBrush;
private static RadialGradientBrush bottomLeftBrush;

// *** Constructors ***

static ShadowChrome()
{
MarginProperty.OverrideMetadata(typeof(ShadowChrome), new FrameworkPropertyMetadata(new Thickness(0, 0, 4, 4)));

CreateBrushes();
}

// *** Overriden base methods ***

protected override void OnRender(DrawingContext drawingContext)
{
// Calculate the size of the shadow

double shadowSize = Math.Min(Margin.Right, Margin.Bottom);

// If there is no shadow, or it is bigger than the size of the child, then just return

if (shadowSize <= 0 || this.ActualWidth < shadowSize*2 || this.ActualHeight < shadowSize * 2)
return;

// Draw the background (this may show through rounded corners of the child object)

Rect backgroundRect = new Rect(shadowSize, shadowSize, this.ActualWidth - shadowSize, this.ActualHeight - shadowSize);
drawingContext.DrawRectangle(backgroundBrush, null, backgroundRect);

// Now draw the shadow gradients

Rect topRightRect = new Rect(this.ActualWidth, shadowSize, shadowSize, shadowSize);
drawingContext.DrawRectangle(topRightBrush, null, topRightRect);

Rect rightRect = new Rect(this.ActualWidth, shadowSize * 2, shadowSize, this.ActualHeight - shadowSize * 2);
drawingContext.DrawRectangle(rightBrush, null, rightRect);

Rect bottomRightRect = new Rect(this.ActualWidth, this.ActualHeight, shadowSize, shadowSize);
drawingContext.DrawRectangle(bottomRightBrush, null, bottomRightRect);

Rect bottomRect = new Rect(shadowSize * 2, this.ActualHeight, this.ActualWidth - shadowSize * 2, shadowSize);
drawingContext.DrawRectangle(bottomBrush, null, bottomRect);

Rect bottomLeftRect = new Rect(shadowSize, this.ActualHeight, shadowSize, shadowSize);
drawingContext.DrawRectangle(bottomLeftBrush, null, bottomLeftRect);
}

// *** Private static methods ***

private static void CreateBrushes()
{
// Get the colors for the shadow

Color shadowColor = Color.FromArgb(128, 0, 0, 0);
Color transparentColor = Color.FromArgb(16, 0, 0, 0);

// Create a GradientStopCollection from these

GradientStopCollection gradient = new GradientStopCollection(2);
gradient.Add(new GradientStop(shadowColor, 0.5));
gradient.Add(new GradientStop(transparentColor, 1.0));

// Create the background brush

backgroundBrush = new SolidColorBrush(shadowColor);

// Create the LinearGradientBrushes

rightBrush = new LinearGradientBrush(gradient, new Point(0.0, 0.0), new Point(1.0, 0.0));
bottomBrush = new LinearGradientBrush(gradient, new Point(0.0, 0.0), new Point(0.0, 1.0));

// Create the RadialGradientBrushes

bottomRightBrush = new RadialGradientBrush(gradient);
bottomRightBrush.GradientOrigin = new Point(0.0, 0.0);
bottomRightBrush.Center = new Point(0.0, 0.0);
bottomRightBrush.RadiusX = 1.0;
bottomRightBrush.RadiusY = 1.0;

topRightBrush = new RadialGradientBrush(gradient);
topRightBrush.GradientOrigin = new Point(0.0, 1.0);
topRightBrush.Center = new Point(0.0, 1.0);
topRightBrush.RadiusX = 1.0;
topRightBrush.RadiusY = 1.0;

bottomLeftBrush = new RadialGradientBrush(gradient);
bottomLeftBrush.GradientOrigin = new Point(1.0, 0.0);
bottomLeftBrush.Center = new Point(1.0, 0.0);
bottomLeftBrush.RadiusX = 1.0;
bottomLeftBrush.RadiusY = 1.0;
}
}



This may then be used as shown in the following XAML. Note that the Margin property is used for two uses. Firstly it provides an area around the element to render the drop-shadow (this is important in a Popup since it ensures the drop-shadow is within the pop-up window), and also specifies to ShadowChrome the size to render the drop-shadow.

<ShadowChrome>
<Border Margin="0,0,5,5" .../>
</ShadowChrome>