Home » Programming » WPF » WPF Ribbon » WPF Ribbon (CTP) – Part 6 – Creating a Ribbon Color Control

WPF Ribbon (CTP) – Part 6 – Creating a Ribbon Color Control

Creating a Ribbon Color Control

Let’s take Word for example: we can easily select the color for a piece of text or for a shape. The CTP version does not have any such control, so I would like to give you an example of how you can create one.

Entire Tutorial:

Summary

  • Step 1: Creating a new application
  • Step 2: Creating the custom RibbonColorButton
  • Step 3: Binding the RibbonColorButton to the TextBlock

Step 1: Creating a new application

Right now you should be able to create a new WPF application (WpfRibbonColorControl) and reference the RibbonControlsLibrary.dll in this application. If you need help with this part, you should look over WPF Ribbon (CTP) – Part 1 – Add Ribbon and Customize

So, let’s suppose that we have a main window which contains the ribbon and an additional TextBlock on which we will operate the color changes:

<ribbon:RibbonWindow x:Class="WpfRibbonColorControl.Window1"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:ribbon="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
 Title="Ribbon Color Control Example">
  <Window.Resources>
    <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/RibbonControlsLibrary;component/Themes/Office2007Blue.xaml"/>
      </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
  </Window.Resources>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"></RowDefinition>
      <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>
    <ribbon:Ribbon Grid.Row="0" Title="WPF Ribbon Color Example">
      <ribbon:RibbonTab Label="Text Properties" Name="TextPropertiesTab">
      </ribbon:RibbonTab>
    </ribbon:Ribbon>
    <TextBlock Name="sampleText" Grid.Row="1" Text="This is the text sample that will change color."
        Background="White" Foreground="Red" Padding="5,5,5,5"></TextBlock>
  </Grid>
</ribbon:RibbonWindow>

and the following code behind:

namespace WpfRibbonColorControl
{
  /// <summary>
  /// Interaction logic for Window1.xaml
  /// </summary>
  public partial class Window1 : RibbonWindow
  {
    public Window1()
    {
      InitializeComponent();
    }
  }
}

Step 2: Creating the custom RibbonColorButton

First, we need to create a class for the RibbonColorButton, and inside it we have to declare a dependency property for the selected color, like this:

namespace WpfRibbonColorControl
{
  class RibbonColorButton : RibbonDropDownButton
  {
     public static readonly DependencyProperty SelectedColorProperty =
        DependencyProperty.Register("SelectedColor", typeof(Color),
        typeof(RibbonColorButton));

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

Next, we have to create the drop down menu, like this:

/// <summary>
/// The background color for the menu item header
/// </summary>
public static Color HeaderBackgroundColor = new Color { A = 255, R = 221, G = 231, B = 238 };

/// <summary>
/// The foreground color for the menu item header
/// </summary>
public static Color HeaderContentColor = new Color { A = 255, R = 50, G = 65, B = 133 };

/// <summary>
/// The predefined colors
/// </summary>
public static List<Color> basicColors = new List<Color>() { Colors.Red, Colors.Orange, Colors.Yellow, Colors.Green, Colors.Blue, Colors.Indigo, Colors.Violet, Colors.White, Colors.Black };

/// <summary>
/// The size of the basic colors buttons
/// </summary>
protected static int colorButtonSize = 16;

/// <summary>
/// Creates an instance of the RibbonColorButton
/// </summary>
public RibbonColorButton()
{
  SetDropDownStyle();
  CreateDropDownMenu();
}

/// <summary>
/// Sets the style of the button (RibbonCommand and RibbonControlSizeDefinitions)
/// </summary>
private void SetDropDownStyle()
{
  // create and attach command
  RibbonCommand cmd = new RibbonCommand();
  this.Command = cmd;
  // enable button
  cmd.CanExecute += (sender, args) => args.CanExecute = true;

  // set image and label not visible
  RibbonControlSizeDefinition sizeDef = new RibbonControlSizeDefinition();
  sizeDef.ImageSize = RibbonImageSize.Small;
  sizeDef.IsImageVisible = false;
  sizeDef.IsLabelVisible = false;
  this.SetValue(RibbonGroup.ControlSizeDefinitionProperty, sizeDef);
}

/// <summary>
/// Creates the drop down menu with the color options
/// </summary>
private void CreateDropDownMenu()
{
  // create and add the header for the standard colors
  AddMenuHeader("Standard Colors");

  // create and add the standard colors menu item
  AddStandardColorsMenuItem();

  // create and add separator
  AddSeparator();

  // create and add the 'No Color' menu item
  AddNoColorMenuItem();

  // create and add the 'More Colors' menu item
  AddMoreColorsMenuItem();
}

private void AddMoreColorsMenuItem()
{
  MenuItem moreColors = new MenuItem();
  // set header
  moreColors.Header = "More Colors...";
  // add event handler
  moreColors.Click += new RoutedEventHandler(moreColors_Click);
  // add on control
  this.Items.Add(moreColors);
}

void moreColors_Click(object sender, RoutedEventArgs e)
{
  // because WPF does not have its own color picker
  // we will use the WinForms Color picker
  System.Windows.Forms.ColorDialog colorPicker = new System.Windows.Forms.ColorDialog();
  colorPicker.FullOpen = true;
  if (colorPicker.ShowDialog() == System.Windows.Forms.DialogResult.OK)
  {
    // convert from System.Drawing.Color to System.Windows.Media.Color
    SelectedColor = new Color() { R = colorPicker.Color.R, G = colorPicker.Color.G, B = colorPicker.Color.B, A = colorPicker.Color.A };
  }
}

private void AddNoColorMenuItem()
{
  MenuItem noColor = new MenuItem();
  // set header
  noColor.Header = "No Color";
  // add event handler
  noColor.Click += new RoutedEventHandler(noColor_Click);
  // add on control
  this.Items.Add(noColor);
}

void noColor_Click(object sender, RoutedEventArgs e)
{
  SelectedColor = Colors.Transparent;
}

private void AddSeparator()
{
  this.Items.Add(new RibbonSeparator());
}

/// <summary>
/// Creates the menu item with the standard colors
/// </summary>
private void AddStandardColorsMenuItem()
{
  StackPanel panel = new StackPanel();
  panel.Orientation = Orientation.Horizontal;

  foreach (Color color in basicColors)
  {
    panel.Children.Add(CreateColorButton(color));
  }

  this.Items.Add(panel);
}

/// <summary>
/// Creates an instance of a standard color button
/// </summary>
/// <param name="color">The color inside of the button</param>
/// <returns></returns>
private Button CreateColorButton(Color color)
{
  // create a button at the specified size
  Button colorBtn = new Button();
  colorBtn.Width = colorBtn.Height = colorButtonSize;
  // remove border, margin, padding
  colorBtn.BorderBrush = Brushes.Transparent;
  colorBtn.Margin = new Thickness(1);
  colorBtn.Padding = new Thickness(0);
  colorBtn.BorderThickness = new Thickness(0);
  // create event handler for click
  colorBtn.Click += new RoutedEventHandler(standardColorBtn_Click);
  // set the color inside the 'Tag' property, to retrieve it easier when the button is pressed
  colorBtn.Tag = color;

  // create a square filled with the specified color
  Rectangle square = new Rectangle();
  square.RadiusX = 0;
  square.RadiusY = 0;
  square.Width = square.Height = colorButtonSize;
  square.Fill = new SolidColorBrush(color);
  square.HorizontalAlignment = HorizontalAlignment.Stretch;
  square.VerticalAlignment = VerticalAlignment.Stretch;
  // place the square inside the entire button
  colorBtn.Content = square;

  return colorBtn;
}

/// <summary>
/// The event handler triggered when one of the standard color buttons is pressed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void standardColorBtn_Click(object sender, RoutedEventArgs e)
{
  SelectedColor = (Color)((Button)sender).Tag;
}

/// <summary>
/// Creates a menu header with the specified content
/// </summary>
/// <param name="content"></param>
public void AddMenuHeader(string content)
{
  MenuItem headerLabel = new MenuItem();
  headerLabel.IsHitTestVisible = false;
  headerLabel.Header = content;
  headerLabel.Background = new SolidColorBrush(HeaderBackgroundColor);
  headerLabel.Foreground = new SolidColorBrush(HeaderContentColor);
  headerLabel.FontWeight = FontWeights.Bold;
  headerLabel.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
  this.Items.Add(headerLabel);
}

As you could see, we are using the WinForms’ color picker, so we need to add references to System.Windows.Forms and to System.Drawing.

Now, the RibbonColorButton looks like this:

The menu looks alright, but the RibbonDropDown button doesn’t have any image or text on it, so we have to improve its appearance. The next step is to color the RibbonDropDown itself with the selected color. Besides the SelectedColor, we will need a Rectangle that will be drawn inside the RibbonDropDown control, and which will be bound to the SelectedColor dependency property. So, let’s add an handler for the Loaded event, inside the RibbonColorButton class:

/// <summary>
 /// The colored rectangle wich will be displayed in the drop down button
 /// </summary>
 protected Rectangle colorRectangle;

private void BindRectangleToSelectedColor()
 {
Binding binding = new Binding();
binding.Source = this;
binding.Mode = BindingMode.OneWay;
binding.Path = new PropertyPath("SelectedColor");
binding.NotifyOnSourceUpdated = true;
binding.NotifyOnTargetUpdated = true;
binding.Converter = new ColorToBrushConverter();
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
colorRectangle.SetBinding(Rectangle.FillProperty, binding);
 }

 void RibbonColorButton_Loaded(object sender, RoutedEventArgs e)
 {
Image img = Utils.FindTemplateControl<Image>(this, false);
if (img != null && img.Visibility != Visibility.Collapsed)
{
// hide the image
img.Visibility = Visibility.Collapsed;

// get the RibbonToggleButton
RibbonToggleButton toggleBtn = Utils.FindTemplateControl<RibbonToggleButton>(this, false);

// get the border
Border border = (Border)img.Parent;

// create the square which will be displayed in the drop down button
colorRectangle.HorizontalAlignment = HorizontalAlignment.Right;
colorRectangle.Width = colorRectangle.Height = colorButtonSize;

// create arrow path
Path arrow = new Path();
arrow.Margin = new Thickness(1);
arrow.Data = StreamGeometry.Parse("M0,0L2.5,3 5,0z");
arrow.StrokeThickness = 1;
arrow.HorizontalAlignment = HorizontalAlignment.Stretch;
arrow.VerticalAlignment = VerticalAlignment.Center;
arrow.Fill = new SolidColorBrush(Color.FromArgb(0xFF, 0x56, 0x7D, 0xB1));

// create grid
Grid grid = new Grid();
grid.VerticalAlignment = VerticalAlignment.Center;
grid.HorizontalAlignment = HorizontalAlignment.Left;
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(18) });
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(7) });

// add grid in border
border.Child = grid;

// add rectangle and arrow in grid
grid.Children.Add(colorRectangle);
grid.Children.Add(arrow);
colorRectangle.SetValue(Grid.ColumnProperty, 0);
arrow.SetValue(Grid.ColumnProperty, 1);

// resize control to fit the new inner elements
toggleBtn.Width = colorRectangle.Width + arrow.Width;
this.Height = toggleBtn.Height = 25;
this.Margin = new System.Windows.Thickness(5, 5, 0, 0);

BindRectangleToSelectedColor();
}
 }

Don’t worry, I borrowed the arrow path from the previous part of the tutorial, from the RibbonDropDown’s template.

As you can see, we also use the Utils file from the previous part of the tutorial:

 class Utils
 {
 /// <summary>
 /// Tries to find the first occurence of the specified type (T) inside the specified element's template. Returns null if no control of that type was found.
 /// </summary>
 /// <typeparam name="T">The type of the inner control you are looking for</typeparam>
 /// <param name="element">The dependency object in which template you want to search</param>
 /// <param name="includeCurrentElement">A boolean value indicating that the current element (actually starting element) should be included or not in the search</param>
 /// <returns></returns>
 public static T FindTemplateControl<T>(DependencyObject element, bool includeCurrentElement) where T : DependencyObject
 {
   // if the current element is of type T and we want to include the current element in the search
   // then we found out inner control
   if (element is T && includeCurrentElement)
     return (T)element;

   // otherwise, look recursively in the inner children of the element
   for (var i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
   {
     // we exclude only the starting element from the search, all children are included
     var result = FindTemplateControl<T>(VisualTreeHelper.GetChild(element, i), true);
     if (result != null)
       return result;
   }

   // if no element was found, return null
   return null;
 }
}

and the ColorToBrushConverter:

namespace WpfRibbonColorControl
{
  class ColorToBrushConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
       if (value is Color)
       {
          Color color = (Color)value;
          return new SolidColorBrush(color);
       }
       return new SolidColorBrush(Colors.Black);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
       if (value is SolidColorBrush)
       {
          SolidColorBrush brush = (SolidColorBrush)value;
          return brush.Color;
        }
        return Colors.Transparent;
     }
  }
}

Now, the control looks more like a RibbonColorButton:

Step 3: Binding the RibbonColorButton to the TextBlock

The last step we have to make is to bind the RibbonColorButton to the TextBlock, on the Window1 class. For this, we need to create a binding, like this:

private void BindTextBlockToRibbonColorButton(RibbonColorButton colorBtn)
{
  Binding binding = new Binding();
  binding.Source = colorBtn;
  binding.Mode = BindingMode.OneWay;
  binding.Path = new PropertyPath("SelectedColor");
  binding.NotifyOnSourceUpdated = true;
  binding.NotifyOnTargetUpdated = true;
  binding.Converter = new ColorToBrushConverter();
  binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
  sampleText.SetBinding(TextBlock.ForegroundProperty, binding);
}

Now we have a functional RibbonColorButton, and as you can see, the text color changes together with the color selection

Source Code

You can download the source code for part 6 of the tutorial from here.

Posted in WPF Ribbon and tagged as , , , ,

One comment on “WPF Ribbon (CTP) – Part 6 – Creating a Ribbon Color Control

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.