This is a migrated thread and some comments may be shown as answers.

RadMenu and HierarchicalDataTemplate

21 Answers 492 Views
Menu
This is a migrated thread and some comments may be shown as answers.
Nic
Top achievements
Rank 1
Nic asked on 16 Jan 2009, 10:56 AM
Hi,

Is it possible to setup some properties in the objects contained in HierarchicalDataTemplate so that when assigned to the RadMenu, the individual menu items are enabled/disabled and their click events are setup automatically?

Furthermore, can these properties by dynamically changed at runtime, and the menu change accordingly. I'm thinking enabling/disabling a given menu item based on some user context.

On the same note, even add (or remove) objects from the HierarchicalData at runtime and reflect this in the menu as well. Perhaps a list of recently opened files...

We're developing an enterprise app with modular components. Ideally, each component would add it's own menu items to the menu tree at the right insertion point, and take care of enabling/disabling its own menu items, as well as click events. Would something like this be possible via the HierarchicalDataTemplate? Would appreciate ideas on how to go about doing this.


Thanks,
Nic

21 Answers, 1 is accepted

Sort by
0
Hristo
Telerik team
answered on 19 Jan 2009, 01:27 PM
Hello Nic,

Sorry for the late response.
Yes you can create collection from some business objects and bind it to RadMenu.
If this collection implements INotifyCollectionChanged then RadMenu will update its items when you change the collection.
Also if the items in this collection implement INotifyPropertyChanged then the corresponding RadMenuItems will update its content.

You can see how to create and you RadMenu and HierarchicalDataTemplate from this example:
http://demos.telerik.com/silverlight/#Menu/DataSource

For my regret Silverlight 2 do not support Bindings in Style setters. So you wont be able to bind RadMenuItem IsEnabled property to some property of your business object. But we are evaluating a way to overcome this limitation. So stay tuned.

One way to workaround this limitation now is to inherit from RadMenu and override PrepareContainerForItemOverride method. Then you can bind your business object with the corresponding RadMenuItem in code.
 
As for the right insertion point - this is possible. All you need to do is to insert the item in the correct place in your collection (assuming that your collection implement INotifyCollectionChanged and you have set this collection as ItemsSource on RadMenu).

RadMenuItem expose routed Click event so you can handle all menuItem clicks in one place of your code.
You can achieve your scenario easily.

Let me know if you need more help.


Sincerely yours,
Hristo
the Telerik team

Check out Telerik Trainer, the state of the art learning tool for Telerik products.
0
Nic
Top achievements
Rank 1
answered on 19 Jan 2009, 10:54 PM
Hi Hristo,

Thank you for your response and advice on how to go about my dynamic menu issues.

I am not entirely sure how to make use of PrepareContainerForItemOverride though. Would you be able to provide an example project for me to build upon?

thanks,
Nic


0
Hristo
Telerik team
answered on 20 Jan 2009, 09:56 AM
Hi Nic,

Lets say that you have ObservableCollection<DataItem> as a source for RadMenu.ItemsSource.
The code for DataItem is as follow:

public class DataItem : INotifyPropertyChanged  
{  
    public DataItem()  
    {  
        this.Items = new ObservableCollection<DataItem>();  
    }
    #region IsEnabled  
 
    private bool isEnabled;  
    /// <summary>  
    /// Gets or sets IsEnabled.  
    /// </summary>  
    public bool IsEnabled  
    {  
        get 
        {  
            return this.isEnabled;  
        }  
        set 
        {  
            if (this.isEnabled != value)  
            {  
                this.isEnabled = value;  
                OnPropertyChanged("IsEnabled");  
            }  
        }  
    }
    #endregion  
 
    #region Header  
 
    private string header;  
    /// <summary>  
    /// Gets or sets Header.  
    /// </summary>  
    public string Header  
    {  
        get 
        {  
            return this.header;  
        }  
        set 
        {  
            if (this.header != value)  
            {  
                this.header = value;  
                OnPropertyChanged("Header");  
            }  
        }  
    }
    #endregion  
 
    #region Items  
 
    private ObservableCollection<DataItem> items;  
    /// <summary>  
    /// Gets or sets Items.  
    /// </summary>  
    public ObservableCollection<DataItem> Items  
    {  
        get 
        {  
            return this.items;  
        }  
        set 
        {  
            if (this.items != value)  
            {  
                this.items = value;  
                OnPropertyChanged("Items");  
            }  
        }  
    }
    #endregion  
 
    #region INotifyPropertyChanged Members  
 
    /// <summary>  
    ///     Called when the value of a property changes.  
    /// </summary>  
    /// <param name="propertyName">The name of the property that has changed.</param>  
    protected virtual void OnPropertyChanged(String propertyName)  
    {  
        if (String.IsNullOrEmpty(propertyName))  
        {  
            return;  
        }  
        if (PropertyChanged != null)  
        {  
            PropertyChanged(thisnew PropertyChangedEventArgs(propertyName));  
        }  
    }  
 
    /// <summary>  
    ///     Raised when the value of one of the properties changes.  
    /// </summary>  
    public event PropertyChangedEventHandler PropertyChanged;
    #endregion  

This class have three properties: IsEnabled - controls whether RadMenuItem is enabled, Header - the string for RadMenuItem Header property and Items - RadMenuItem child items.

When you set the ObservableCollection<DataItem> as a ItemsSource to RadMenu, for every item it this collection PrepareContainerForItemOverride method will be called.
There you have the RadMenuItem and the DataItem and you can bind them like this:

public class RadMenuExtended : RadMenu  
{  
    protected override void PrepareContainerForItemOverride(System.Windows.DependencyObject element, object item)  
    {  
        base.PrepareContainerForItemOverride(element, item);  
        RadMenuItem menuItem = element as RadMenuItem;  
 
        if (!object.ReferenceEquals(menuItem, item))  
        {  
            Binding binding = new Binding("IsEnabled");  
            binding.Source = item;  
            menuItem.SetBinding(RadMenuItem.IsEnabledProperty, binding);  
        }  
    }  

The Header and Items property can be bound from the HierarchicalDataTempalte.

This is the way for now. Please keep in mind that this way only the root MeunItems will be bound to IsEnabled property. If you want to bind all menuItems you should inherit from RadMenuItem and RadMenu and change them like follows:

public class RadMenuExtended : RadMenu  
{  
    protected override bool IsItemItsOwnContainerOverride(object item)  
    {  
        return (item is RadMenuItemExtended);  
    }  
 
    protected override System.Windows.DependencyObject GetContainerForItemOverride()  
    {  
        return new RadMenuItemExtended();  
    }  
 
    protected override void PrepareContainerForItemOverride(System.Windows.DependencyObject element, object item)  
    {  
        base.PrepareContainerForItemOverride(element, item);  
        RadMenuItem menuItem = element as RadMenuItem;  
 
        if (!object.ReferenceEquals(menuItem, item))  
        {  
            Binding binding = new Binding("IsEnabled");  
            binding.Source = item;  
            menuItem.SetBinding(RadMenuItem.IsEnabledProperty, binding);  
        }  
    }  
}  
 
public class RadMenuItemExtended : RadMenuItem  
{  
    protected override bool IsItemItsOwnContainerOverride(object item)  
    {  
        return (item is RadMenuItemExtended);  
    }  
 
    protected override System.Windows.DependencyObject GetContainerForItemOverride()  
    {  
        return new RadMenuItemExtended();  
    }  
 
    protected override void PrepareContainerForItemOverride(System.Windows.DependencyObject element, object item)  
    {  
        base.PrepareContainerForItemOverride(element, item);  
 
        RadMenuItem menuItem = element as RadMenuItem;  
 
        if (!object.ReferenceEquals(menuItem, item))  
        {  
            Binding binding = new Binding("IsEnabled");  
            binding.Source = item;  
            menuItem.SetBinding(RadMenuItem.IsEnabledProperty, binding);  
        }  
    }  

I hope this helps.
Let me know if you need more help.

Sincerely yours,
Hristo
the Telerik team

Check out Telerik Trainer, the state of the art learning tool for Telerik products.
0
Nic
Top achievements
Rank 1
answered on 22 Jan 2009, 03:53 PM
Hi Hristo,

Thank you very much for the detailed code. It got me thinking...

I was looking at the logic for PrepareContainerForItemOverride() and wondering if it was possible to have another property in DataItem named IsVisible. When set to false, the idea would be for that menuitem's visibility to be collapsed and not visible at all in the menu tree.

The bigger picture would be an application with plugins that could be enabled/disabled. One scenario is to add/remove a corresponding plugin's menu items to the ObservableCollection. The other, would be just to set/unset the IsVisible property for the plugin's menu items, and in PrepareContainerForItemOverride() bind this property accordingly to the UI element's visiblity property. Would this work?

Thanks again,
Nic

0
Nic
Top achievements
Rank 1
answered on 22 Jan 2009, 10:30 PM
Hristo,

I got the Visibility working using the same technique. Thanks for showing how the approach works!

I do have another issue now and that is how to put a separator (RadSeparator?) between two menu items using the HierarchicalDataTemplate model.

--Nic
0
Hristo
Telerik team
answered on 23 Jan 2009, 07:46 AM
Hello Nic,

I'm happy I was able to help. I hope that in Silverlight 3 you will be able to bind RadMenuItem to the data item so you won't need the code behind.
As for the RadSeparator - right now the only way of doing it is to add RadSeparator objects in your source collection.

We are thinking of other ways to do this - like adding IsSeparator property to RadMenuItem but eventually you will need to add data item that represent separator. So why using binding that will change the control template to separator when you can put directly the separator item?

Let me know if this works for you.

Sincerely yours,
Hristo
the Telerik team

Check out Telerik Trainer, the state of the art learning tool for Telerik products.
0
Nic
Top achievements
Rank 1
answered on 23 Jan 2009, 10:05 AM
Hi Hristo,

I'm not quite following your last point. If I'm using a HierarchicalDataTemplate, how do I now go about adding RadSeparator objects into DataItem's ObservableCollection<DataItem>?

Do I change this to ObservableCollection <Object> and override some method in RadMenu and/or RadMenuItem to inspect the actual type of each item in the ObservableCollection? Can you please provide an example?

Thanks,
Nic



0
Hristo
Telerik team
answered on 23 Jan 2009, 11:53 AM
Hello Nic,

Just change the DataItem Items collection to ObservableCollection<object>. There is no need to override anything in RadMenu on RadMenuItem. The logic is already there.

Please let me know if you need more help.

All the best,
Hristo
the Telerik team

Check out Telerik Trainer, the state of the art learning tool for Telerik products.
0
Nic
Top achievements
Rank 1
answered on 23 Jan 2009, 04:24 PM
Hello Hristo,

I am not able to get this separator bit working. I have tried two approaches.

1. Having DataItem's Item collection being an ObservableCollection<object> and inserting a RadSeparator. When the menu displays, then at the location of where the separator should be, I get this strange behaviour where it opens up as a submenu containing the same content. This occurs recusrively so at the position of the separator, it keeps having the same submenu open up several levels deep.

2. Add a bool IsSeparator to DataItem. Then implement a DataTemplateSelector class to select the right HierarchicalDataTemplate. I have it select between these two...

<core:HierarchicalDataTemplate x:Key="MenuItemHierarchicalDataTemplate" ItemsSource="{Binding Items}" >
            <TextBlock Text="{Binding Header}" Margin="4" HorizontalAlignment="Left" VerticalAlignment="Bottom" />
        </core:HierarchicalDataTemplate>

        <core:HierarchicalDataTemplate x:Key="SeparatorHierarchicalDataTemplate">
            <StackPanel>
                <telerik:RadSeparator BorderBrush="Black" Background="Black" IsEnabled="False" />
                <TextBlock Text="test text"/>
            </StackPanel>            
        </core:HierarchicalDataTemplate>

I can see it use the SeparatorHierarchicalDataTemplate template but I guess i'm not using it correctly. Firstly, I need to add dummy text for the RadSeparator to draw out when it should just be the width of the largest menu item. It also highlights on mouseover and is clickable.

Can you please advise further?

thanks,
Nic


0
Accepted
Hristo
Telerik team
answered on 27 Jan 2009, 09:44 AM
Hi Nic,

Sorry for the late response.

I the attachments you will find a modified version of RadMenuExtended and RdaMenuItemExtended that allow you to put RadSeparator control. The change in both classes is in the IsItemItsOwnContainerOverride where I'm now checking for both RadMenuItemExtended and RadSeparator. If the item is one of these then no container is created.
Also I have added TemplateSelector class that inherit from DataTemplateSelector and return HierarchicalDataTemplate from Page resources if the item is not RadSeparator. This is needed because you cannot put DataTemplate on UIElements (you can not put DataTemplate on RadSeparator).
In the RadMenuExtended I have set the ItemTemplateSelector property instead of directly ItemTemplate to avoid setting ItemTemplate on RadSeparator.
Also one more check in PrepareContainerForItemOverride method in both classes. I am checking if the cast to RadMenuItemExtended is not null now to avoid setting bindings on RadSeparator.

The DataItem Items collection is now ObservableCollection<object> like discussed in the previous post.

Please let me know if you need more help.

Regards,
Hristo
the Telerik team

Check out Telerik Trainer, the state of the art learning tool for Telerik products.
0
Nic
Top achievements
Rank 1
answered on 27 Jan 2009, 07:11 PM
Hi Hristo,

Thank you very much - this solves the problem I was trying to fix.

--Nic

0
Rohit
Top achievements
Rank 1
answered on 10 Feb 2009, 03:06 PM

Hi

I want to know how to write the Click Event Handler for the menu items if I am using the
HierarchicalDataTemplate. In this case all the menu items are created dynamicaly and I donot see a Click Event in RadMenu control. Please suggest how to attache event handler in this case. May be you can simply tell me if I want to do specific thing on click of

"Item 2.3", may be I can get the DataItem in eventargs or something like that.

Regards
Rohit

0
Rohit
Top achievements
Rank 1
answered on 10 Feb 2009, 03:07 PM

Hi

I want to know how to write the Click Event Handler for the menu items if I am using the
HierarchicalDataTemplate. In this case all the menu items are created dynamicaly and I donot see a Click Event in RadMenu control. Please suggest how to attache event handler in this case. May be you can simply tell me if I want to do specific thing on click of

"Item 2.3", may be I can get the DataItem in eventargs or something like that.

Regards
Rohit

0
Hristo
Telerik team
answered on 12 Feb 2009, 07:46 AM
Hello Rohit,

You can use AddHandler extension method to handle RadMenuItem.Click event.
using Telerik.Windows;  
 
public class Page1()  
{  
   public Page1()  
   {  
      InitializeComponent();  
      radMenu1.AddHandler(radMenu1.AddHandler(RadMenuItem.ClickEvent, new RoutedEventHandler(OnMenuItemClick));  
   }  
 
   private void OnMenuItemClick(object sender, RoutedEventArgs e)  
   {  
    RadRoutedEventArgs args = e as RadRoutedEventArgs;  
    RadMenuItem menuItem = args.OriginalSource as RadMenuItem;  
   }  

Let me know if you need more help.

All the best,
Hristo
the Telerik team

Check out Telerik Trainer, the state of the art learning tool for Telerik products.
0
Rohit
Top achievements
Rank 1
answered on 01 Apr 2009, 02:50 PM
Hi Hristo,

I have another problem. Lets say I have a top level menu and under it I have 3 sub menus and all these 3 are not visible. This may happen if my data is dynamic and its condional. Now I want, in this scenario, it should hide all the sub menu and should not display any arrow on the top level menu. Top level menu should be clickable now and should raise the Routed Click Event.

Something like following which has only one child item and that is not visible, in this case it should allow user to click on Resize Canvas menu item.

 

 

 

<

 

telerik:RadMenu Name="MainMenu1" VerticalAlignment="Center" Height="31">

 

 

 

    <

 

telerik:RadMenuItem Header="Resize Canvas" Click="Resize_RadMenuItem_Click" x:Name="ResizeMenu">

 

 

 

        <telerik:RadMenuItem Header="Sub menu Resize Canvas" Click="Resize_RadMenuItem_Click" Visibility="Collapsed" x:Name="ResizeMenu2"/>

 

 

 

    </telerik:RadMenuItem>

 

</

 

telerik:RadMenu>

 

 

 

 

 

 
Please help me with it.

Regards
Rohit

 

0
Hristo
Telerik team
answered on 02 Apr 2009, 07:02 AM
Hello Rohit,

I see several problems with this approach:
  1. MenuItems that have children do not raise Click event (but you can use SubmenuOpened event instead). This is by design.
  2. You cannot have x:Name set to subItems (this is Silverlight 2 problem). If you are using ItemsSource to populate the Menu then this is not an issue.

You can edit the control template and remove the Path (that is the arrow). But I'm not sure about dynamically changing the Path visibility (we should add more visual states to be able to handle this).

Basically you can achieve this by removing (instead of hiding) the items that are not visible. This way you won't have arrow, and you will get Click event.

I hope this help. Let me know if you need more information.


All the best,
Hristo
the Telerik team

Check out Telerik Trainer , the state of the art learning tool for Telerik products.
0
Debbie
Top achievements
Rank 1
answered on 13 May 2009, 10:00 AM
Hello

I also have a RadMenu bound to 2 HierarchicalDataTemplates (each containing their own containerBindingCollection) and want to be able to bind my menuitems' Click event to my object's OnClick property. I've tried this in the ContainerBinding but to no avail... So now I've found this post and have tried to AddHandler to my RadMenu but that option is not available. Is this an old implementation? How would I be able to capture or set the MenuItems' Click events?

Thanks!
Debbie
0
Valeri Hristov
Telerik team
answered on 13 May 2009, 10:39 AM
Hi Debbie,

The AddHandler() is an extension method from the Telerik.Windows namespace. You need to add the namespace to your usings at the beginning of the code behind - please, check Hristo Hristov's post from 2/12/2009 in this thread for a sample.

Sincerely yours,
Valeri Hristov
the Telerik team

Instantly find answers to your questions on the new Telerik Support Portal.
Check out the tips for optimizing your support resource searches.
0
Debbie
Top achievements
Rank 1
answered on 13 May 2009, 10:54 AM
Great, thanks! The Telerik.Windows was the missing key!
0
Preethi Gracia
Top achievements
Rank 1
answered on 30 Jul 2009, 06:38 AM
Hello,

    I am using Telerik RadControls for Silverlight Q2 2009. I have written code to dynamically to generate RadMenuItems in a RadMenu. Now to track the ClickEvent of the RadMenuItem, I have added a AddHandler as explained by Hristo. I have a added like
appMenu.AddHandler(RadMenuItem.ClickEvent, new RoutedEventHandler(OnRadMenuItemClick), false); 
where appMenu is the Name of the Telerik RadMenu. But I am getting an error at the new RoutedEventHandler(OnRadMenuItemClick) telling "No overload for 'OnRadMenuItemClick' matches delegate 'System.Windows.RoutedEventHandler'. Please help me and let me know what I am missing. I have also added reference to Telerik.Windows.

Thanks,
Preethi
0
Rohit
Top achievements
Rank 1
answered on 30 Jul 2009, 09:26 AM
Hello Preethi,

I guess you need to write a method as below: be specific about the type of two arguments of the event handler. Mentioned error relates that your types of the event handler arguments not matching with the delegate RoutedEventHandler.

 private void OnRadMenuItemClick(object sender, RoutedEventArgs e)  
 {  
    //Add Code to Event Handler  
 } 

If I am wrong then please mention which telerik version you are using as I am using Q1 2009 and it is working fine in that version.

thanks
Rohit
Tags
Menu
Asked by
Nic
Top achievements
Rank 1
Answers by
Hristo
Telerik team
Nic
Top achievements
Rank 1
Rohit
Top achievements
Rank 1
Debbie
Top achievements
Rank 1
Valeri Hristov
Telerik team
Preethi Gracia
Top achievements
Rank 1
Share this question
or