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>

Sunday, October 29, 2006

DropDownButtons in WPF

Whilst Windows Presentation Foundation (WPF) provides good support for menus, one omission is a drop-down button. By this I mean a button, that when clicked will result in a menu dropping down from the control.

So I started to write my own. Easy I thought, just style a MenuItem to look like a button. Well, this doesn't work as the MenuItem control will only work if it is a child of a Menu control. I could just wrap all my DropDownButtons in Menus, but that becomes a little messy wrapping everything in extra menus. I tried a number of other approaches, none of which worked as I would like.


This was several months ago. Recently however I was informed of a couple of related blog articles at Sheva's Techspace and Lester's piece on WPF. In particular I liked Lester's approach. This is to use the Button's ContextMenu to apply add a context menu, and then to use code to open this on a left mouse click. A simple solution that works well. But what if I wanted to have a real context-menu too?


Solution: Well, in fact ContextMenu doesn't need to be attached to a control to work. All you need to do is create a new ContextMenu object, add your MenuItems, and set IsOpen to true. So I put together the control as below.



public class DropDownButton : ToggleButton
{
// *** Dependency Properties ***


public static readonly DependencyProperty DropDownProperty = DependencyProperty.Register("DropDown", typeof(ContextMenu), typeof(DropDownButton), new UIPropertyMetadata(null));

// *** Constructors ***

public DropDownButton()
{
// Bind the ToogleButton.IsChecked property to the drop-down's IsOpen property

Binding binding = new Binding("DropDown.IsOpen");
binding.Source = this;
this.SetBinding(IsCheckedProperty, binding);
}

// *** Properties ***

public ContextMenu DropDown
{
get
{
return (ContextMenu)GetValue(DropDownProperty);
}
set
{
SetValue(DropDownProperty, value);
}
}

// *** Overridden Methods ***

protected override void OnClick()
{
if (DropDown != null)
{
// If there is a drop-down assigned to this button, then position and display it

DropDown.PlacementTarget = this;
DropDown.Placement = PlacementMode.Bottom;

DropDown.IsOpen = true;
}
}
}


This control derives directly from ToggleButton so we inherit the full behavior and styling of a button. We then provide a DropDown dependency property that takes a ContextMenu to show for the drop-down. In the OnClick event we simply position the menu under the control, and open it by setting IsOpen to true. Finally to tidy up, we bind the ToggleButton's IsChecked property to the drop-down menu's IsOpen property to ensure that these are synchronized.


To use this in your own code you simply use,



<ctrl:DropDownButton Content="Drop-Down">
<ctrl:DropDownButton.DropDown>
<ContextMenu>
<MenuItem Header="Item 1"/>
<MenuItem Header="Item 2"/>
<MenuItem Header="Item 3"/>
</ContextMenu>
</ctrl:DropDownButton.DropDown>
</ctrl:DropDownButton>



Technorati tags: , ,

An Office 2007 style Ribbon in WPF?

For a while now I have been working on reproducing an Office 2007 Ribbon style application in Windows Presentation Foundation (WPF). This includes not just the ribbon itself, but also the full window chrome.

Whilst the official documentation has been in progress, blogs have been a great alternative source of information. However now I thought would be a good time to start my own blog discussing, amongst other things, how I am writing my Ribbon control.

Hope over the coming months you enjoy my posts and find something of interest to your own development needs. I'll focus on some of the issues that I have encountered, and will post some images of the fully working Ribbon control.

Technorati tags: ,