XamRibbon–Creating a Color Gallery the MVVM Way

Brian Lagunas / Friday, September 7, 2012

If you are a user of Infragistics NetAdvantage for WPF and have used the xamRibbon control, then chances are you have probably had the need to create a gallery of some type.  In this post I will show you how to create a gallery of colors, but instead of doing it in code behind, we will populate our gallery items from within a ViewModel.  Yes, that’s right!  We will MVVM-ify our gallery items.

First thing we need to do it create our xamRibbon with a GalleryTool.  It will be a simple view with just a ribbon, a single tab, and a single group that contains our GalleryTool.  Here is what our markup looks like:

<igRibbon:XamRibbon>
    <igRibbon:XamRibbon.ApplicationMenu>
        <igRibbon:ApplicationMenu />
    </igRibbon:XamRibbon.ApplicationMenu>
    <igRibbon:XamRibbon.QuickAccessToolbar>
        <igRibbon:QuickAccessToolbar />
    </igRibbon:XamRibbon.QuickAccessToolbar>
    <igRibbon:RibbonTabItem Header="Home">
        <igRibbon:RibbonGroup Caption="Gallery Group">
            <igRibbon:MenuTool Caption="Menu" ShouldDisplayGalleryPreview="True">
                <igRibbon:GalleryTool ItemBehavior="StateButton">
                    <igRibbon:GalleryTool.ItemSettings>
                        <igRibbon:GalleryItemSettings TextDisplayMode="Always" TextPlacement="BelowImage" />
                    </igRibbon:GalleryTool.ItemSettings>
                </igRibbon:GalleryTool>
            </igRibbon:MenuTool>
        </igRibbon:RibbonGroup>
    </igRibbon:RibbonTabItem>
</igRibbon:XamRibbon>

Let’s go ahead and create a VIewModel.

public class MainViewModel : INotifyPropertyChanged
{
    private ObservableCollection<GalleryItem> _galleryItems;
    public ObservableCollection<GalleryItem> GalleryItems
    {
        get { return _galleryItems; }
        set
        {
            _galleryItems = value;
            NotifyPropertyChanged("GalleryItems");
        }
    }

    public MainViewModel()
    {
        GalleryItems = GenerateColorGalleryItems();
    }

    /// <summary>
    /// Generates a collection of GalleryItems with all available colors.
    /// </summary>
    ObservableCollection<GalleryItem> GenerateColorGalleryItems()
    {
        ObservableCollection<GalleryItem> items = new ObservableCollection<GalleryItem>();

        PropertyInfo[] properties = typeof(Colors).GetProperties(BindingFlags.Static | BindingFlags.Public);
        foreach (PropertyInfo info in properties)
        {
            Color c = (Color)info.GetValue(typeof(Colors), null);
            ImageSource src = CreateImage(c, new Size(60, 20));
            items.Add(CreateItem(info.Name, src, c));
        }

        return items;
    }

    /// <summary>
    /// Creates a color gallery item item.
    /// </summary>
    /// <param name="caption">The caption.</param>
    /// <param name="image">The image.</param>
    /// <param name="color">The color.</param>
    /// <returns></returns>
    static GalleryItem CreateItem(string caption, ImageSource image, Color color)
    {
        GalleryItem item = new GalleryItem { Key = string.Format("Color_{0}", caption), Image = image, Text = caption, Tag = color };
        return item;
    }

    /// <summary>
    /// Creates the image for a color gallery item
    /// </summary>
    /// <param name="color">The color.</param>
    /// <param name="size">The size.</param>
    /// <returns></returns>
    static ImageSource CreateImage(Color color, Size size)
    {
        DrawingVisual v = new DrawingVisual();
        DrawingContext c = v.RenderOpen();
        c.DrawRectangle(new SolidColorBrush(color), null, new Rect(0, 0, size.Width, size.Height));
        c.Close();

        RenderTargetBitmap rtb = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32);
        rtb.Render(v);
        return rtb;
    }


    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }

As you can see, this ViewModel implements the INotifyPropertyChanged interface, just like all ViewModels should.  We have a property called GalleryItems which we will use to data bind our GalleryTool.Items to.  We also have a couple of methods that are responsible for creating the GalleryItems.  The CreateImage method is repsonsible for turning a Color object into a visual color block that will be displayed in the GalleryTool.

Next we need to create a binding to the GalleryTool.Items property.  Now, I know what you are going to say, “but Brian, the GalleryTool.Items property is not a DependencyProperty so you can’t data bind to it”.  That is true, but it doesn’t mean we can’t find a way around it.  You have probably seen me blog about an AttachedProperty in the past.  Well, we are going to us the same approach here in order to create a property that we can use to data bind our GalleryItems property in our ViewModel to the GalleryTool.Items property in our View.  Create a class, name it whatever you want, and then add the following AttachedProperty definition.

public class GalleryTool
{
    public static readonly DependencyProperty ItemsProperty =
        DependencyProperty.RegisterAttached("Items", typeof(ObservableCollection<GalleryItem>), typeof(GalleryTool), new UIPropertyMetadata(null, new PropertyChangedCallback(OnItemsPropertyChanged)));

    public static ObservableCollection<GalleryItem> GetItems(DependencyObject obj)
    {
        return (ObservableCollection<GalleryItem>)obj.GetValue(ItemsProperty);
    }

    public static void SetItems(DependencyObject obj, ObservableCollection<GalleryItem> value)
    {
        obj.SetValue(ItemsProperty, value);
    }

    static void OnItemsPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        Infragistics.Windows.Ribbon.GalleryTool tool = obj as Infragistics.Windows.Ribbon.GalleryTool;
        if (tool == null)
            throw new ArgumentException("Items can only be set on a GalleryTool.");

        ObservableCollection<GalleryItem> items = e.NewValue as ObservableCollection<GalleryItem>;
        if (items != null)
        {
            tool.Items.AddRange(items);
        }
    }
}

The part that is doing the work here is the OnItemsPropertyChanged callback method that is defined in the AttachedProperty’s UIPropertyMetadata declaration.  Here we are taking the items that are being used as the source, found in the e.NewItems property of the event args, and adding them to the GalleryTool.Items property.  Once you have created this AttachedProperty yo need to add a namespace to your view and then create the binding on the GalleryTool as follows.

Namespace first:

<Window x:Class="XamRibbonColorGalleryTool.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:XamRibbonColorGalleryTool"

Now define the AttachedProperty on the GalleryTool and then set the binding to the GalleryItems from the ViewModel.

<igRibbon:MenuTool Caption="Menu" ShouldDisplayGalleryPreview="True">
    <igRibbon:GalleryTool ItemBehavior="StateButton" local:GalleryTool.Items="{Binding GalleryItems}">
        <igRibbon:GalleryTool.ItemSettings>
            <igRibbon:GalleryItemSettings TextDisplayMode="Always" TextPlacement="BelowImage" />
        </igRibbon:GalleryTool.ItemSettings>
    </igRibbon:GalleryTool>
</igRibbon:MenuTool>

If you are use to MVVM, you should know that we need to do one last step before hitting F5, and that is set the DataContext of our View to an instance of our ViewModel:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainViewModel();
    }
}

Now you can hit F5 and run the application.  Here is what you get:

image

And when the gallery opens you can see all the wonderful colors to chose from.

image

Selection

So, there is one last thing that bugs me about the GalleryTool.  When you make a selection, the selected item does not scroll into view.  Weird, I know.  I will get that fixed in the product, but until then you can do this little trick.  First make sure you give your xamRibbon an x:Name.  Next add an event handler for the GalleryTool.ItemSelected event.

<igRibbon:GalleryTool ItemBehavior="StateButton" local:GalleryTool.Items="{Binding GalleryItems}" ItemSelected="ColorGalleryItemSelected">
    <igRibbon:GalleryTool.ItemSettings>
        <igRibbon:GalleryItemSettings TextDisplayMode="Always" TextPlacement="BelowImage" />
    </igRibbon:GalleryTool.ItemSettings>
</igRibbon:GalleryTool>

Use the following code to bring the newly selected gallery item into view:

private void ColorGalleryItemSelected(object sender, Infragistics.Windows.Ribbon.Events.GalleryItemEventArgs e)
{
    //workaround to bring the selected color into view until this is supported by the gallerytool
    Infragistics.Windows.Utilities.DependencyObjectSearchCallback<GalleryItemPresenter> callback = (presenter) => presenter.IsInPreviewArea && presenter.Item == e.Item;
    var itemPresenter = Infragistics.Windows.Utilities.GetDescendantFromType(xamRibbon1, true, callback);
    if (null != itemPresenter)
        itemPresenter.BringIntoView();
}

Now when you select a gallery item, it will be brought into view just like your customer expects.

image

Hope you enjoyed this post.  Be sure to download the source code and let me know if you have any questions.