views:

116

answers:

2

Hi everyone. I'd like to be able to create dynamic menus associated with certain object. Let’s say, I will have 3 listview container with one style where I also have a Menu. I need to generate different menu items from collection of the RoutetUICommands in relation on each listview. I was trying to solve this puzzle but took me a while and still have trouble making it work. I need to generate object specific menus, an unique menu for each listview. Any ideas are highly appreciated. Thank you!

XAML:

<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
x:Class="DynamicMenu.MainWindow"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480">
<Window.Resources>

    <Style x:Key="ListViewStyleTask" TargetType="{x:Type ListView}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ListView}">
                    <Grid>
                        <Menu x:Name="mainMenu">
                            <MenuItem x:Name="menuItem" Header="Tasks" ItemsSource="{Binding Commands}" >
                                <MenuItem.ItemContainerStyle>
                                    <Style TargetType="{x:Type MenuItem}">
                                        <Setter Property="Command" Value="{Binding}" />
                                        <Setter Property="Header" Value="{Binding Path=Text}" />
                                        <Setter Property="CommandParameter" Value="{Binding Path=Parameter}" />
                                    </Style>
                                </MenuItem.ItemContainerStyle>
                            </MenuItem>
                        </Menu>
                    </Grid>

                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</Window.Resources>

<Grid x:Name="LayoutRoot">
    <ListView x:Name="Container1" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" Style="{DynamicResource ListViewStyleTask}">
        <ListView.View>
            <GridView>
                <GridViewColumn/>
            </GridView>
        </ListView.View>
    </ListView>
    <ListView x:Name="Container2" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" Style="{DynamicResource ListViewStyleTask}">
        <ListView.View>
            <GridView>
                <GridViewColumn/>
            </GridView>
        </ListView.View>
    </ListView>
    <ListView x:Name="Container3" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" Style="{DynamicResource ListViewStyleTask}">
        <ListView.View>
            <GridView>
                <GridViewColumn/>
            </GridView>
        </ListView.View>
    </ListView>
</Grid>

I have added some commands which I need to associate to 3 different Listviews:

    public partial class MainWindow : Window
{

    // Container 1
    public static RoutedUICommand NameCommand = new RoutedUICommand("Name", "NameCommand", typeof(MainWindow));
    public static RoutedUICommand StreetCommand = new RoutedUICommand("Street", "StreetCommand", typeof(MainWindow));
    public static RoutedUICommand GroupCommand = new RoutedUICommand("Add to Group", "AddGroup", typeof(MainWindow));

    // Container 2
    public static RoutedUICommand ViewDetailsCommand = new RoutedUICommand("View Details", "ViewDetailsCommand", typeof(MainWindow));

    // Container 3
    public static RoutedUICommand StartCommand = new RoutedUICommand("Start", "StartCommand", typeof(MainWindow));
    public static RoutedUICommand StopCommand = new RoutedUICommand("Stop", "StopCommand", typeof(MainWindow));
    public static RoutedUICommand LoadCommand = new RoutedUICommand("Load", "LoadCommand", typeof(MainWindow));

    public MainWindow()
    {
        this.InitializeComponent();

        // Insert code required on object creation below this point.
    }
}

}

+2  A: 

You need to define a structure to group the data as you need in your control template. Something like this,

public class CommandCollection {
    public ObservableCollection<Command> Commands { get; set; }
}

public class Command {

    public ICommand Action { get; set; }

    public string Text { get; set; }

    public string Parameter { get; set; }
}

Have 3 members of CommandCollection each one having its commands and then assign those as datacontext to the ListViews

Updated,

After declaring the above structure you declare 3 members,

public CommandCollection Container1Commands { get; set; }

public CommandCollection Container2Commands { get; set; }

public CommandCollection Container3Commands { get; set; }

Fill these members,

    Container1Commands = new CommandCollection ();
    Container1Commands.Commands = new ObservableCollection<CommandParameters> ();
    Container1Commands.Commands.Add (new CommandParameters () { Action = NameCommand, Text = "Name" });
    Container1Commands.Commands.Add (new CommandParameters () { Action = StreetCommand, Text = "Street" });
    Container1Commands.Commands.Add (new CommandParameters () { Action = GroupCommand, Text = "Group" });

    Container2Commands = new CommandCollection ();
    Container2Commands.Commands = new ObservableCollection<CommandParameters> ();
    Container2Commands.Commands.Add (new CommandParameters () { Action = ViewDetailsCommand, Text = "ViewDetails" });

    Container3Commands = new CommandCollection ();
    Container3Commands.Commands = new ObservableCollection<CommandParameters> ();
    Container3Commands.Commands.Add (new CommandParameters () { Action = StartCommand, Text = "Start" });
    Container3Commands.Commands.Add (new CommandParameters () { Action = StopCommand, Text = "Stop" });
    Container3Commands.Commands.Add (new CommandParameters () { Action = LoadCommand, Text = "Load" });

Set data context,

    this.DataContext = this;
    this.Container1.DataContext = Container1Commands;
    this.Container2.DataContext = Container2Commands;
    this.Container3.DataContext = Container3Commands;

Update your control template to specify menu item command binding,

<Setter Property="Command" Value="{Binding Action}" />

Updated XAML

    <Window.Resources>

        <Style x:Key="ListViewStyleTask" TargetType="{x:Type ListView}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListView}">
                        <Grid>
                            <Menu x:Name="mainMenu" >
                                <MenuItem x:Name="menuItem" Header="Tasks" ItemsSource="{Binding Commands}">
                                    <MenuItem.ItemContainerStyle>
                                        <Style TargetType="{x:Type MenuItem}">
                                            <Setter Property="Command" Value="{Binding Action}" />
                                            <Setter Property="Header" Value="{Binding Path=Text}" />
                                            <Setter Property="CommandParameter" Value="{Binding Path=Parameter}" />
                                        </Style>
                                    </MenuItem.ItemContainerStyle>
                                </MenuItem>
                            </Menu>
                        </Grid>

                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

    </Window.Resources>

    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <ListView
            Grid.Row="0" x:Name="Container1" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" Style="{DynamicResource ListViewStyleTask}">
            <ListView.View>
                <GridView>
                    <GridViewColumn/>
                </GridView>
            </ListView.View>
        </ListView>
        <ListView
            Grid.Row="1" x:Name="Container2" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" Style="{DynamicResource ListViewStyleTask}">
            <ListView.View>
                <GridView>
                    <GridViewColumn/>
                </GridView>
            </ListView.View>
        </ListView>
        <ListView
            Grid.Row="2" x:Name="Container3" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" Style="{DynamicResource ListViewStyleTask}">
            <ListView.View>
                <GridView>
                    <GridViewColumn/>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>

Code behind

// Container 1
public static RoutedUICommand NameCommand = new RoutedUICommand ("Name", "NameCommand", typeof (Window1));
public static RoutedUICommand StreetCommand = new RoutedUICommand ("Street", "StreetCommand", typeof (Window1));
public static RoutedUICommand GroupCommand = new RoutedUICommand ("Add to Group", "AddGroup", typeof (Window1));

// Container 2
public static RoutedUICommand ViewDetailsCommand = new RoutedUICommand ("View Details", "ViewDetailsCommand", typeof (Window1));

// Container 3
public static RoutedUICommand StartCommand = new RoutedUICommand ("Start", "StartCommand", typeof (Window1));
public static RoutedUICommand StopCommand = new RoutedUICommand ("Stop", "StopCommand", typeof (Window1));
public static RoutedUICommand LoadCommand = new RoutedUICommand ("Load", "LoadCommand", typeof (Window1));

public Window1 () {
    InitializeComponent ();
    this.Loaded += new RoutedEventHandler (Window1_Loaded);
}

public CommandCollection Container1Commands { get; set; }

public CommandCollection Container2Commands { get; set; }

public CommandCollection Container3Commands { get; set; }

void Window1_Loaded (object sender, RoutedEventArgs e) {
    Container1Commands = new CommandCollection ();
    Container1Commands.Commands = new ObservableCollection<Command> ();
    Container1Commands.Commands.Add (new Command () { Action = NameCommand, Text = "Name" });
    Container1Commands.Commands.Add (new Command () { Action = StreetCommand, Text = "Street" });
    Container1Commands.Commands.Add (new Command () { Action = GroupCommand, Text = "Group" });

    Container2Commands = new CommandCollection ();
    Container2Commands.Commands = new ObservableCollection<Command> ();
    Container2Commands.Commands.Add (new Command () { Action = ViewDetailsCommand, Text = "ViewDetails" });

    Container3Commands = new CommandCollection ();
    Container3Commands.Commands = new ObservableCollection<Command> ();
    Container3Commands.Commands.Add (new Command () { Action = StartCommand, Text = "Start" });
    Container3Commands.Commands.Add (new Command () { Action = StopCommand, Text = "Stop" });
    Container3Commands.Commands.Add (new Command () { Action = LoadCommand, Text = "Load" });

    this.CommandBindings.Add (new CommandBinding (NameCommand, ExecuteNameCommand, CanExecuteNameCommand));
    this.CommandBindings.Add (new CommandBinding (StreetCommand, ExecuteStreetCommand, CanExecuteStreetCommand));
    this.CommandBindings.Add (new CommandBinding (GroupCommand, ExecuteGroupCommand, CanExecuteGroupCommand));

    this.DataContext = this;
    this.Container1.DataContext = Container1Commands;
    this.Container2.DataContext = Container2Commands;
    this.Container3.DataContext = Container3Commands;
}

private void ExecuteNameCommand (object inSender, RoutedEventArgs inE) {
    MessageBox.Show ("Name command Executed");
}

private void CanExecuteNameCommand (object inSender, CanExecuteRoutedEventArgs inE) {
    inE.CanExecute = true;
}

private void ExecuteStreetCommand (object inSender, RoutedEventArgs inE) {
    MessageBox.Show ("Street command Executed");
}

private void CanExecuteStreetCommand (object inSender, CanExecuteRoutedEventArgs inE) {
    inE.CanExecute = true;
}

private void ExecuteGroupCommand (object inSender, RoutedEventArgs inE) {
    MessageBox.Show ("Group command Executed");
}

private void CanExecuteGroupCommand (object inSender, CanExecuteRoutedEventArgs inE) {
    inE.CanExecute = true;
}

Other classes

public class CommandCollection {

    public ObservableCollection<Command> Commands { get; set; }
}

public class Command {

    public ICommand Action { get; set; }

    public string Text { get; set; }

    public string Parameter { get; set; }
}

I Hope now you get it working.

Updated for RoutedUICommand description, The idea should be to have these menu items in the outer container (like shell) which will have other pages in it (like a frame/canvas), like for example if you see MS Visual Studio the menu items (Save) are part of the application shell and the files openeed are within the shell (shell has a container tabcontrol maybe, where the files are loaded as they are opened). So the routed commands (Save) are defined by the application shell and all the other pages inside the shell's container add those commands in there command binding collection (this.CommandBindings.Add(cmdname, actionname, predicatename)) so each page performs its own respective action and the command is invoked for them only when they are in focus.

bjoshi
Thank you for looking at my thread. I believe that your suggestion is to define 3 propeties for menu item in Command class Right? You are also suggested to create unique array of items for each Listview. Is that correct? Are you saying to define 3 ObservableCollections for each Listview? It will be nice to see any working sample. Thank you again.
vladc77
Updated the answer
bjoshi
Thank you for the update. It is very close. I just need one more tip t finally figure it out. Please look at the code I added as the answer. I am highly appreciate your help.
vladc77
Check the updated answer
bjoshi
Everything is working perfect. I am highly appreciate your help and time. It is really helping me to understand how it should be working. I am wondering if you wouldn't mind to look at another post in case if you are interested. I feel it is something that will be easy for you to give an idea on how to solve the problem. I have another question which I recently posted here: http://stackoverflow.com/questions/3975069/how-to-update-frame-source-from-two-listboxes-on-selected-item-value.
vladc77
I have one more question. Is that hard to apply these menus to other XAML pages without repetitive code in each code behind. I'd like to use these menus with the same content in them but in other xaml pages. I'd like not to duplicate all of the code above in each code behind file but rather centralize it. Any clues?
vladc77
Couldn't write that part in comment due to characters limit, see it in updated answer.
bjoshi
Thank you for you suggestion. I am little bit new in C#. I am trying my first steps. I created new project the way you described. I was able to cast RountedUICommands, but for some reason I cannot initiate command to be running. I am able to bind all names of menuitems. I am wondering what can be wrong. I opened new thread to illustrate it better. The problem is that I cannot add CommandBinding into app level. I am wondering if you can look at it. It is here: http://stackoverflow.com/questions/3994435/cannot-pass-commands-with-commandbinding-into-app
vladc77
A: 

Thank you for the update. It looks very very close to what I am looking for. I have trouble correctly defining members. Do you mind to tell me what I am doing wrong. I am getting errors.

namespace DynamicMenu {
public partial class MainWindow : Window
{
    public class CommandCollection
    {
        public ObservableCollection<Command> Commands { get; set; }
    }

    public class Command
    {

        public ICommand Action { get; set; }
        public string Text { get; set; }
        public string Parameter { get; set; }
    }

    public CommandCollection Container1Commands { get; set; }
    public CommandCollection Container2Commands { get; set; } 
    public CommandCollection Container3Commands { get; set; }

    public MainWindow()
    {
        this.InitializeComponent();

        Container1Commands = new CommandCollection();
        Container1Commands.Commands = new ObservableCollection<CommandParameters>();
        Container1Commands.Commands.Add(new CommandParameters() { Action = NameCommand, Text = "Name" });
        Container1Commands.Commands.Add(new CommandParameters() { Action = StreetCommand, Text = "Street" });
        Container1Commands.Commands.Add(new CommandParameters() { Action = GroupCommand, Text = "Group" });

        Container2Commands = new CommandCollection();
        Container2Commands.Commands = new ObservableCollection<CommandParameters>();
        Container2Commands.Commands.Add(new CommandParameters() { Action = ViewDetailsCommand, Text = "ViewDetails" });

        Container3Commands = new CommandCollection();
        Container3Commands.Commands = new ObservableCollection<CommandParameters>();
        Container3Commands.Commands.Add(new CommandParameters() { Action = StartCommand, Text = "Start" });
        Container3Commands.Commands.Add(new CommandParameters() { Action = StopCommand, Text = "Stop" });
        Container3Commands.Commands.Add(new CommandParameters() { Action = LoadCommand, Text = "Load" }); 

        // Insert code required on object creation below this point.
        this.DataContext = this;
        this.Container1.DataContext = Container1Commands;
        this.Container2.DataContext = Container2Commands;
        this.Container3.DataContext = Container3Commands; 

    }
}

}

vladc77
What are the errors?
bjoshi
I am getting errors starting from defining Container1Commands. VS description is "Error 2 Cannot implicitly convert type 'System.Collections.ObjectModel.ObservableCollection<CommandParameters>' to 'System.Collections.ObjectModel.ObservableCollection<DynamicMenu.MainWindow.Command>' C:\Users\VC\Desktop\WPF Dynamic Menus\DynamicMenu\DynamicMenu\MainWindow.xaml.cs 54 43 DynamicMenu"
vladc77
Ah my wrong, I had renamed the class 'CommandParameters' to 'Command' while posting the answer but forgot to do that during the creation of Container1Commands, Container2Commands, Container3Commands. Please replace 'CommandParameters' to 'Command'
bjoshi
It is much better now, but still there is a problem. The action for each Commands is underlined. I added method for each action but it is still does not fix it.
vladc77
Do you mean it is disabled? Did you all the routed commands in the command bindings of the control you want to address the events? Define the command's action and predicate like, private void ExecuteNameCommand (object inSender, RoutedEventArgs inE) { } private void CanExecuteNameCommand (object inSender, CanExecuteRoutedEventArgs inE) { inE.CanExecute = true; }and then add the command in command binding collection like, this.CommandBindings.Add (new CommandBinding (NameCommand, ExecuteNameCommand, CanExecuteNameCommand));
bjoshi
I tried that but something is still not working completely. Perphaps, I am setting CommandBinding incorrectly. It will be super great if you could post it as a full working sample since it is so close. I can bind text now, but not action yet. Nevertheless, I will post this as the answered question since it is so close. I hope you might have a chance to help to complete with the action. Thank you again.
vladc77
Can you put your code? In the meantime I will try to compile and send the sample
bjoshi
I added the code. Thank you for looking.
vladc77
Everything is working perfect. I am highly appreciate your help and time. It is really helping me to understand how it should be working. I am wondering if you wouldn't mind to look at another post in case if you are interested. I feel it is something that will be easy for you to give an idea on how to solve the problem. I have another question which I recently posted here: http://stackoverflow.com/questions/3975069/how-to-update-frame-source-from-two-listboxes-on-selected-item-value.
vladc77