Wednesday, February 4, 2009

Creating a ColorFillToy in Silverlight – part 3

Click here to go to part 1 of the series
Click here to go to part 2 of the series

Click here to download the source
Click here to view a demo

 

Allright, now we’re finally going to finish the colorfilltoy by adding the ColorPicker found in the SilverlightContrib library.

To embed the ColorPicker in our application, the easy way would be to just throw it on our LayoutRoot. This would result in a large control which would cover a part of our drawing so this is not desirable. Terence used a ColorPickerButton thingy in his example so since there is no such control available in Silverlight 2 I guess we have to create one ourselves. We already created the button in XAML in part 1 of this series, so now we’ll make it work.

We also already created the button as a Control in the first part of the series, so let’s start by coding the Button control.

What we basically want to achieve is that, by pressing the ColorPickerButton the ColorPicker Control will collapse below the button. The ColorPicker control however, is a much bigger control than the button. So if the button is positioned along the right or left border of the hosting application, the ColorPicker will appear off screen.
I want the control to align on the right or the left side of the button depending on the position of the button and the space available.
To accomplish that we have to check the LayoutRoot element of the application and use that to do our measurements. Since we already have that object in our application, we will add the ColorPicker to that as well.

Get the LayoutRoot element of the Application

Once we have the LayoutRoot we’ll save it in a private field. We also need an instance of the ColorPicker control from the SilverlightContrib library.

In the loaded eventhandler we are going to check if the LayoutRoot of the application is actually a Grid or a Canvas. If it’s not, we cannot calculate where the ColorPicker control needs to be positioned. We’ll throw an exception if it’s not.

All this put together creates something like this:

private Panel __parentPanel;
private SilverlightContrib.Controls.ColorPicker _colorPicker = new SilverlightContrib.Controls.ColorPicker();

///
/// ColorPickerButton control to leverage the ColorPicker control
///

public ColorPickerButton()
{
// Required to initialize variables
InitializeComponent();

this.Loaded += ColorPickerButton_Loaded;
}

private void ColorPickerButton_Loaded(object sender, RoutedEventArgs e)
{
_parentPanel = this.Parent as Panel;
if(_parentPanel == null)
{
throw new Exception("Parent control should be a control derived from FrameworkElement. Examples: Grid, Canvas");
}
LayoutRoot.Children.Add(_colorPicker);
}


The SelectedColor Property


Since we’re using the ColorPickerButton control to show and hide the ColorPicker control we need some way to persist the value of the selectedcolor that is set in the ColorPicker control.

We’ll create a similar DependencyProperty for the ColorPickerButton like there is for the ColorPicker Control itself:


/// 
/// Event fired when a color is selected.
///

public event SilverlightContrib.Controls.ColorPicker.ColorSelectedHandler ColorSelected;
#region SelectedColor DependencyProperty
///
/// Gets or sets the currently selected color in the Color Picker.
///

public Color SelectedColor
{
get { return (Color)GetValue(SelectedColorProperty); }
set
{
SetValue(SelectedColorProperty, value);
}
}

///
/// SelectedColor Dependency Property.
///

public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register(
"SelectedColor",
typeof(Color),
typeof(ColorPickerButton),
new PropertyMetadata(new PropertyChangedCallback(SelectedColorChanged)));

private static void SelectedColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ColorPickerButton p = d as ColorPickerButton;
if (p != null && p.ColorSelected != null)
{
p.ColorSelected((Color)e.NewValue);
}
}
#endregion

Handling clicks


To finish up the control we need to handle the clicks on the ColorPickerButton. We want to prevent the ColorPicker control to disappear every time it is clicked. This time we’ll do it by handling the click on the LayoutRoot object and check if our control is hit by using the VisualTreeHelper and LINQ to query the results:


private void LayoutRoot_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
IEnumerable uiElements = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(null), LayoutRoot);
var elements = from elem in uiElements
where elem == this.ColorPickerButtonCanvas
select elem;

if (elements.Count() == 0)
return;

_colorPicker.Visibility = _colorPicker.Visibility ==
Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed;
}

Positioning the ColorPicker control


To position the control we’ll use the LayoutUpdated event of the ColorPickerButton. Note: I created the method only for left/right positioning calculations, not for the bottom/top one, I also only tested it in a Canvas control but it should work in a Grid as well:


private void ColorPickerButton_LayoutUpdated(object sender, EventArgs e)
{
double leftPos = 0;
double topPos = 0;
double leftPosInParent = 0;

_colorPicker.MinWidth = 200;
_colorPicker.MinHeight = 200;
if (_parentPanel != null)
{
if (_parentPanel.GetType() == typeof(Canvas))
{
leftPosInParent = Canvas.GetLeft(this);
}
if (_parentPanel.GetType() == typeof(Grid))
{
leftPosInParent = this.Margin.Left;
}
}

topPos = ColorPickerButtonCanvas.ActualHeight;

double maxWidth = double.Parse(App.Current.RootVisual.GetValue(FrameworkElement.WidthProperty).ToString());
if (leftPosInParent + _colorPicker.Width > maxWidth)
{
//align right
leftPos = -_colorPicker.Width + this.ActualWidth;
}
else
{
//small offset for the slider
topPos += 5;
}

Canvas.SetLeft(_colorPicker, leftPos);
Canvas.SetTop(_colorPicker, topPos);
}

With this code it makes it possible to position the button on the right or left side of any control and it will automatically switch to right or left alignment:


screenshot


Final adjustments


Now to use the color that was selected in the ColorPicker we need to remove the random color stuff we have now and replace it with a single line of code:


Color color = myColorPickerButton.SelectedColor;

Hope this helps!


Click here to download the source

Click here to view a demo