Skip to main content Skip to footer

The Making of WorkSpace Part 3: Using C1RadialMenu

In this blog post series I’m discussing the making of the ComponentOne WorkSpace app for Windows 8. This third post in the series can also be used as a getting started guide to the new C1RadialMenu control. So far I’ve covered the app structure and working with files in the WorkSpace app. Here are links to the previous parts.

  1. App View and ViewModel structure
  2. Opening and saving Files
  3. Using the C1RadialMenu control to format content
  4. Recent documents and application settings

In this post, I take the next step by using the new C1RadialMenu control to provide formatting functionality to my file editors. I’m not going to cover the exact code used by each formatting command (there are over 50), but rather show how I accomplished presenting the commands in a contextual menu.

Using C1RadialMenu

The C1RadialMenu control, which was just made public in the 2013 v2 update of Studio for WinRT XAML, provides an attractive menu system for any Windows Store app. Modeled after the popular OneNote app, C1RadialMenu gives you a unique and touch-friendly alternative to the traditional context menu. Its compact nature enables you to put a lot of commands into a tight, compact and movable menu that is ideal for touch-based applications. This control replaces the standard toolbar or ribbon in desktop apps. RadialMenu_WorkSpace First let’s look at how we can create a C1RadialMenu in XAML. The control can contain any number of C1RadialMenuItems which it will position evenly around a central button.


<c1:C1RadialMenu Icon="B">  
    <c1:C1RadialMenuItem Header="Item 1" Icon="A"/>  
    <c1:C1RadialMenuItem Header="Item 2" Icon="B"/>  
    <c1:C1RadialMenuItem Header="Item 3" Icon="C"/>  
    <c1:C1RadialMenuItem Header="Item 4" Icon="D"/>  
</c1:C1RadialMenu>  

The central button content is defined by the Icon property on the control. Each C1RadialMenuItem also has an Icon property. You can set the Icon property to any font character or graphic element you want. But for XAML brevity purposes, I’ll just use a single character for the icons. RadialMenu_1 As you can see, the menu items start at the left and rotate clockwise dividing up the space evenly among each other. We can customize the position of items using a couple properties. The SectorCount property tells the control how many empty sectors to create. And the DisplayIndex property lets us specify at which index to position each item. For example, let's make room for 8 items and position our existing 4 items where we want them.


<c1:C1RadialMenu x:Name="contextMenu" SectorCount="8" Icon="B">  
    <c1:C1RadialMenuItem DisplayIndex="2" Header="Item 1" Icon="A"/>  
    <c1:C1RadialMenuItem Header="Item 2" Icon="B"/>  
    <c1:C1RadialMenuItem Header="Item 3" Icon="C"/>  
    <c1:C1RadialMenuItem Header="Item 4" Icon="D"/>  
</c1:C1RadialMenu>  

Notice here I am just setting the DisplayIndex on the first item. It means that the following items will immediately follow clockwise. I could even set the DisplayIndex on “Item 3” to a higher position like 6 to create a gap between the second and third items. RadialMenu_2 Next, let’s add some nested items. Each C1RadialMenuItem acts like its own C1RadialMenu and can contain any number of child items. There’s no limit as to how deep you can nest menu items. The following snippet shows positioning some child items.


<c1:C1RadialMenu x:Name="contextMenu" SectorCount="8" Icon="B">  
    <c1:C1RadialMenuItem DisplayIndex="2" SectorCount="8" Header="Item 1" Icon="A">  
        <c1:C1RadialMenuItem DisplayIndex="2" Header="Item 5" Icon="E"/>  
        <c1:C1RadialMenuItem Header="Item 6" Icon="F"/>  
    </c1:C1RadialMenuItem>  
    <c1:C1RadialMenuItem SectorCount="8" Header="Item 2" Icon="B">  
        <c1:C1RadialMenuItem DisplayIndex="3" Header="Item 7" Icon="G"/>  
        <c1:C1RadialMenuItem Header="Item 8" Icon="H"/>  
    </c1:C1RadialMenuItem>  
    <c1:C1RadialMenuItem Header="Item 3" Icon="C"/>  
    <c1:C1RadialMenuItem SectorCount="8" Header="Item 4" Icon="D">  
        <c1:C1RadialMenuItem DisplayIndex="5" Header="Item 9" Icon="I"/>  
        <c1:C1RadialMenuItem Header="Item 10" Icon="J"/>  
        <c1:C1RadialMenuItem Header="Item 11" Icon="K"/>  
    </c1:C1RadialMenuItem>  
</c1:C1RadialMenu>  

When a C1RadialMenuItem has children, it appears with an arrow that the user can tap or swipe to open up. This toggles the control to display only the child items with an arrow now as the central icon to go back. RadialMenu_3 In many scenarios you may not actually need a parent item but rather just a group of items with no specific parent command. Let’s call these orphaned items. The C1RadialMenu control can display the last selected orphan at the root level. For example, let’s modify “Item 2” to show this effect by setting the AutoSelect and ShowSelectedItem properties to True.


<c1:C1RadialMenu x:Name="contextMenu" SectorCount="8" Icon="B">  
    <c1:C1RadialMenuItem DisplayIndex="2" SectorCount="8" Header="Item 1" Icon="A">  
        ...  
    </c1:C1RadialMenuItem>  
    <c1:C1RadialMenuItem AutoSelect="True" ShowSelectedItem="True" SectorCount="8" >  
        <c1:C1RadialMenuItem DisplayIndex="3" Header="Item 7" Icon="G"/>  
        <c1:C1RadialMenuItem Header="Item 8" Icon="H"/>  
    </c1:C1RadialMenuItem>  
    <c1:C1RadialMenuItem Header="Item 3" Icon="C"/>  
    <c1:C1RadialMenuItem SectorCount="8" Header="Item 4" Icon="D">  
        ...  
    </c1:C1RadialMenuItem>  
</c1:C1RadialMenu>  

Now, rather than seeing “Item 2” we see that the C1RadialMenu control has automatically selected a child item and is showing it at the root level. This is accomplished by setting those two properties on the C1RadialMenuItem. We could also set the SelectedIndex property if we need to be even more specific on which item is initially selected. RadialMenu_4

Handling Menu Logic

A menu is useless if we can’t determine when the user is tapping or clicking on an item. There are three approaches to actually handling the menu logic.

  1. Commanding - Each C1RadialMenuItem has a Command and CommandParameter properties you can use as you would on any Button. I already covered how you can use C1Command for all your commanding needs in part 1.
  2. Individual Events – Each C1RadialMenuItem also has Tapped and Click events you can use to individually handle each item. Both events fire on both mouse and touch input.
  3. ItemClick Event – The root C1RadialMenu control has an ItemClick event which you can use to propagate the logic based on the source item.

Here’s an example of using the ItemClick event.


private void c1RadialMenu1_ItemClick(object sender, C1.Xaml.SourcedEventArgs e)  
{  
    txt.Text = "Item Clicked: " + ((C1.Xaml.C1RadialMenuItem)e.Source).Header.ToString();  

}  

Displaying the Menu

Lastly, we need to show our radial menu. Like most things there are multiple ways to do this. One way is to use the C1ContextMenuService class to attach our C1RadialMenu as the context menu for any other element. For instance, here I am making it the context menu on a Border element.


<Border>  
    <c1:C1ContextMenuService.ContextMenu>  
        <c1:C1RadialMenu .../>  
    </c1:C1ContextMenuService.ContextMenu>  
</Border>  

The C1ContextMenuService is a handy tool for giving any element a context menu. For a more traditional menu you can even use the C1ContextMenu control here instead of C1RadialMenu. When you create a context menu using C1ContextMenuService, the work required to show the menu is handled for you. The user can activate the menu in any of three different ways:

  1. Right-clicking on the element with a mouse
  2. Holding down on the element with touch (an indicator displays while doing so)
  3. Pressing the context menu key from a keyboard

C1RadialMenu works a bit differently than a regular context menu because one more click or tap is required to expand the menu. We can force the C1RadialMenu to expand once the context menu is activated by calling the Expand method inside the Opened event.


private void c1RadialMenu1_Opened(object sender, EventArgs e)  
{  
    // expand menu immediately  
    contextMenu.Expand();  
}  

That’s basically how you can use C1RadialMenu as a context menu in your app. In the case of WorkSpace, it’s a little more complicated because I have more than one radial menu. There are three radial menus in use with the C1RichTextBox control; one for plain text, one for rich text and one for tables. Also, since it’s a text editor I decided to always show the collapsed C1RadialMenu near the caret position, thus allowing the user to expand it whenever he wants. To accomplish this I simply call the Show method in code when I want it to open. In this case, whenever the PointerPressed event is fired on my editor.


private void c1RichTextBox1_PointerPressed(object sender, PointerRoutedEventArgs e)  
{  
    LastLocation = e.GetCurrentPoint(rtb).Position;  
    UpdateContextMenu();  
}  

// cache last tap point, so that to show updated context menu in the correct position  
private Point LastLocation  
{  
    get;  
    set;  
}  

The LastLocation point stores the menu location so that when I need to switch menus on the fly, I can position the new menu where the old one was. The UpdateContextMenu method below does two things: sets the appropriate radial menu, and shows it at the updated screen position. When positioning the menu I take care of whether or not it should display on the left or right side of the user’s selection, so it does not overlap the selection.


private void UpdateContextMenu()  
{  
    // Set correct context menu based on conditions  
    // ...  
    // ...  

    // Position and show menu  
    if (LastLocation.X > 0)  
    {  
        C1.Xaml.C1RadialMenu menu = (C1.Xaml.C1RadialMenu)rtb.ContextMenu;  
        if (menu != null)  
        {  
            // adjust menu offset an additional 150 pixels horizontally  
            Point offset = new Point(150, 0);  
            if (LastLocation.X > this.ActualWidth - 300)  
            {  
                // if menu is close to the right side of the screen, it will be automatically moved so that  
                // all menu is visible on the screen. In such case it will overlap current pointer position.  
                // So, use additional offset to top or bottom  
                offset.Y = LastLocation.Y > this.ActualHeight - 300 ? -150 : 150;  
            }  
            menu.Offset = offset;  
            // show menu in collapsed state, with some offset to the right, so that it doesn't overlap current selection  
            menu.Show(c1RichTextBox1, new Point(LastLocation.X + 100, LastLocation.Y), false);  
        }  
    }  
}  

I broke out the code which determines which menu to show based on conditions and I show it below. There are three different instances of C1RadialMenu for editing text documents in the WorkSpace app. Before the menu is shown, I determine which menu to use by checking a few conditions like document type and text selection.


if (this.Document.DocumentType == DocumentTypeEnum.Text)  
{  
    // the document is plain text  
    c1RichTextBox1.ContextMenu = textMenu;  

    textMenu.Visibility = Visibility.Visible;  
    contextMenu.Visibility = Visibility.Collapsed;  
    contextMenu.Hide();  
    tableMenu.Visibility = Visibility.Collapsed;  
    tableMenu.Hide();  

}  
else if (c1RichTextBox1.Selection.Cells.Count<C1TableCell>() > 0 && string.IsNullOrEmpty(c1RichTextBox1.Selection.Text))  
{  
    // a table is selected, show table menu  
    c1RichTextBox1.ContextMenu = tableMenu;  

    tableMenu.Visibility = Visibility.Visible;  
    contextMenu.Visibility = Visibility.Collapsed;  
    contextMenu.Hide();  
    textMenu.Visibility = Visibility.Collapsed;  
    textMenu.Hide();  
}  
else  
{  
    // show standard menu  
    c1RichTextBox1.ContextMenu = contextMenu;  

    contextMenu.Visibility = Visibility.Visible;  
    tableMenu.Visibility = Visibility.Collapsed;  
    tableMenu.Hide();  
    textMenu.Visibility = Visibility.Collapsed;  
    textMenu.Hide();  

}  

You’ll notice that when you toggle between multiple C1RadialMenus, you should set the Visibility property on each, and call the Show or Hide method. The C1RichTextBox has a ContextMenu property which makes it easy to get and set our menu. If you are using any other control you can use the C1ContextMenuService to help. I should also point out that you don’t have to use the context menu approach. You can simply have multiple C1RadialMenu controls in your page and Show/Hide them using the same code above.

Conclusion

In this post I covered the basics on using the new C1RadialMenu control. It serves a very important role in the WorkSpace app by enabling rich text and spreadsheet formatting capability. You can download a free trial of Studio for WinRT XAML and use the C1RadialMenu control in your own apps today. When you download the studio you get samples as well. In the next post I will discuss how recent file management is done in WorkSpace.

ComponentOne Product Manager Greg Lutz

Greg Lutz

comments powered by Disqus