This demo extends my previous sample, that demonstrated how to add a right click context menu on a TextBox. Here I will show how to create context sensitive main menu and toolbar, in addition to the context menu. The demo will also demonstrate some advanced enhancements that we added to the Silverlight 2 Framework, such as merged dictionaries and container bindings. The application enables/disables the Cut and Copy commands when there is no selection and also disables the Paste command when there is no clipboard content. As in the previous blog post, the clipboard methods work only in IE. Here is how the application looks:

simpleeditor

All controls are data-bound to a single view model:

<telerikNavigation:RadMenu telerik:RadDockPanel.Dock="Top" ItemsSource="{Binding Items}" IsTabStop="False" ItemTemplate="{StaticResource GroupTemplate}" ItemClick="MenuItemClick" /> <telerikNavigation:RadToolBar telerik:RadDockPanel.Dock="Top" Height="30" ItemsSource="{Binding EditItems}" ItemTemplate="{StaticResource ToolbarTemplate}" /> <TextBox x:Name="TextContainer" SelectionChanged="TextBoxSelectionChanged" AcceptsReturn="True" TextWrapping="Wrap" Text="This is a sample text content"> <telerikNavigation:RadContextMenu.ContextMenu> <telerikNavigation:RadContextMenu ItemsSource="{Binding EditItems, Source={StaticResource CommandsModel}}" ItemTemplate="{StaticResource MenuItemTemplate}" ItemClick="MenuItemClick" Closed="ContextMenuClosed" /> </telerikNavigation:RadContextMenu.ContextMenu> </TextBox>

 

Note the different ItemsSource binding of RadContextMenu. Since the context menu is declared through an attached property, the DataContext of its owner will not be inherited, so we need to explicitly specify the Source of the binding.

The main purpose of the commands view model is to take care of the enabling/disabling the commands, depending on the selection:

public void UpdateState(TextBox source)
{
    bool hasSelection = source.SelectionLength > 0;

    this.EditItems.First((item) => item.Text == "Cut").IsEnabled = hasSelection;
    this.EditItems.First((item) => item.Text == "Copy").IsEnabled = hasSelection;
    this.EditItems.First((item) => item.Text == "Paste").IsEnabled = Clipboard.HasContent;
}

 

The CommandItem class contains the data (text, icon, etc.) and the state (enabled or disabled) of the command items. In this example they are declared in the XAML, but you could get them from a web service, or generate them from the code-behind:

<local:CommandsModel x:Key="CommandsModel"> <local:CommandGroup Text="File"> <local:CommandItem Text="Open..." /> <local:CommandItem Text="Save..." /> </local:CommandGroup> <local:CommandGroup Text="Edit"> <local:CommandItem Text="Cut" IconSource="/SimpleTextEditor;component/cut.png" /> <local:CommandItem Text="Copy" IconSource="/SimpleTextEditor;component/copy.png" /> <local:CommandItem Text="Paste" IconSource="/SimpleTextEditor;component/paste.png" /> </local:CommandGroup> <local:CommandGroup Text="Help"> <local:CommandItem Text="About" /> </local:CommandGroup> </local:CommandsModel>

 

To enable the proper binding of the Icon property of RadMenuItem, there is a simple hack in the CommandItem class – the Icon property always returns a new image and when the IconSource property is changed, it raises a notification about itself and the Icon property:

public ImageSource IconSource
{
    get
    {
        return this.iconSource;
    }
    set
    {
        if (this.iconSource != value)
        {
            this.iconSource = value;
            this.OnPropertyChanged("IconSource");
            this.OnPropertyChanged("Icon");
        }
    }
}

public Image Icon
{
    get
    {
        return new Image() { Source = this.IconSource };
    }
}

 

Here is how I bind the Icon and the IsEnabled properies of RadMenuItem to the corresponding properties of the CommandItem class:

<telerik:ContainerBindingCollection x:Name="MenuItemBindings"> <telerik:ContainerBinding PropertyName="IsEnabled" Binding="{Binding IsEnabled}" /> <telerik:ContainerBinding PropertyName="Icon" Binding="{Binding Icon}" /> </telerik:ContainerBindingCollection> <DataTemplate x:Name="MenuItemTemplate" telerik:ContainerBinding.ContainerBindings="{StaticResource MenuItemBindings}"> <TextBlock Text="{Binding Text}" /> </DataTemplate>

 

The ContainerBinding class is a placeholder for a binding that has to be applied on the specified property. This is automatically done by all Telerik ItemsControls and is a temporary way to workaround the inability of the Silverlight 2 styles to set bindings. In two words, the XAML above will instruct the ItemsControl (RadMenu, RadContextMenu or RadMenuItem in the example) to apply the bindings in the Binding property on the properties with name PropertyName in the RadMenuItem containers, that are generated for each CommandItem.

Last, but not least, is the RadToolbar:

<DataTemplate x:Name="ToolbarTemplate"> <Button IsEnabled="{Binding IsEnabled}" ToolTipService.ToolTip="{Binding Text}" Style="{StaticResource ToolBarButton}" Click="ToolbarButtonClick" IsTabStop="False"> <Image Source="{Binding IconSource}" /> </Button> </DataTemplate>

The trick here is this line, that sets an undefined style:

Style="{StaticResource ToolBarButton}"

This style is from the RadToolbar theme and is imported using the following XAML:

<telerik:ResourceDictionary.MergedDictionaries> <telerik:ResourceDictionary Keys="ToolBarButton" Source="/Telerik.Windows.Controls.Navigation;component/themes/generic.xaml" /> </telerik:ResourceDictionary.MergedDictionaries>

The code snippet above “injects” the resources with the specified keys (comma separated) from the specified XAML file. This is very handy when you need a specific style from a Telerik (or custom) theme to be applied on a single control. Of course, if you need to set a theme on a button or other standard control I would recommend using the StyleManager.Theme attached property.

 

Here is the source code:


Related Posts

Comments

Comments are disabled in preview mode.