0
votes

I've seen quite a few example of passing a parameter through a command using the RelayCommand class in MVVM Light, but there's one slight difference between what i want and what i have seen.

I want to create a few buttons where all have them have a ModuleType associated. And when their action is executed i want to know which ModuleType it is. And i wanted to do this in a code efficient way, so not having to create the RelayCommands manually, but instead do everything in a foreach loop, also because i don't know how many buttons i have to create at start.

So here is the code. In my ViewModel

public ModuleSelectionViewModel(MachineStatusModel model, int order, List<ModuleType> modules) : base(model)
{
    ........

    // create button for each of the modules
    foreach (ModuleType mod in modules)
    {
        _navBarButtons.Add(new NavButton(mod.ToString(), new RelayCommand<ModuleType>(exec => ButtonExecute(mod)), mod));
    }

    RaisePropertyChanged("NavBarButtons");
}


// Binding to the Model
public ObservableCollection<NavButton> NavBarButtons
{
    get { return _navBarButtons; }
}


// Execut Action
public void ButtonExecute(ModuleType mod)
{
    WriteToLog("Selected " + mod.ToString());
}


// Support class
public class NavButton
{
    public string ButtonContent { get; set; }
    public ICommand ButtonCommand { get; set; }
    public ModuleType ButtonModuleType;

    public NavButton(string content, ICommand relay, ModuleType moduleType)
    {
        this.ButtonContent = content;
        this.ButtonCommand = relay;
        this.ButtonModuleType = moduleType;
    }
}

I'm still learning about lambda expressions, so i guess i am doing something wrong on the initialization of the RelayCommand.

2
what exactly is the problem? - DevNewb
@DevNewb: in summary, i want to press a button and trigger the execute function with a parameter. Each button has a different value for that parameter - Luis Ferreira
it's not clear from your question what exactly doesn't work in the code you provided - DevNewb
@DevNewb take a look at the discussion on the answer H.B. gave. At this point this part is not working 'param => ButtonExecute(type))', the ButtonExecute won't execute. I want it to execute that function with the parameter that was passed at the time of creation '... new RelayCommand<ModuleType>(param => ButtonExecute(type)) ...' - Luis Ferreira

2 Answers

2
votes

If you do a foreach loop and use the loop variable inside a lambda expression you capture it. Unfortunately the variable is scoped incorrectly (at least in older versions of C#, this changes with C# 5 (thanks, Mafii)).

So you need to do something like:

foreach (ModuleType mod in modules)
{
    // New variable that is locally scoped.
    var type = mod;

    _navBarButtons.Add(new NavButton(mod.ToString(),
        new RelayCommand<ModuleType>(param => ButtonExecute(type)), type));
}
0
votes

Regarding the solution H.B. posted: not sure why it was not working with lambda functions, why the ButtonExecute was not being execute when the button was pressed. And also i don't understand the logic of defining a lambda function like 'foo => ButtonExecute(foo)' when foo is empty and being passed to the function. But anyway...

This is what i did and is working:

Model

<ItemsControl Name="NavButtonsItemsControl" ItemsSource="{Binding NavBarButtons}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Content="{Binding ButtonContent}" CommandParameter="{Binding ButtonModuleType}" Command="{Binding ButtonCommand}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>

</ItemsControl>

ViewModel (check the rest of the code in my initial question)

.........

_navBarButtons.Add(new NavButton(mod.ToString(), new RelayCommand<ModuleType>(ButtonExecute), type));

.........

private void ButtonExecute(ModuleType state)
{
    WriteToLog("Command with parameter " + state.ToString());
}

You can make this more generic by using Object instead of ModuleType. And the solution, instead of using a lambda function, is defining a CommandParameter binding in the button and getting that value as the parameter in the execute function. I don't think that binding is explicitly defined, that's why i was having a hard time understanding how the value was reaching 'ButtonExecute', but following the steps in Dominik Schmidt tutorial it worked.