0

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.

4
  • what exactly is the problem? Commented Jul 8, 2016 at 13:07
  • @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 Commented Jul 8, 2016 at 13:36
  • it's not clear from your question what exactly doesn't work in the code you provided Commented Jul 8, 2016 at 13:50
  • @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)) ...' Commented Jul 8, 2016 at 13:55

2 Answers 2

2

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));
}
Sign up to request clarification or add additional context in comments.

7 Comments

This is due to the closure principle. And it is still relevant AFAIK.
I think this has been fixed in .net 4.0, but I'm unsure. It is for sure fixed in .net 4.5
@WicherVisser ok, it's been fixed in c#5: blogs.msdn.microsoft.com/ericlippert/2009/11/12/…
There's still a detail missing i guess. If i initialize with 'foo => ButtonExecute(foo)' (same variable on both sides, my ButtonExecute function is triggered, but with a default value 0. However if i do like you said 'exec => ButtonExecute(type)' then the function isn't triggered. Why is that?
@LuisFerreira: Lambda expressions are functions, any value on the left is assigned upon execution of the function, e.g. x => x * x squares whatever value is used as input. The function you pass here has the CommandParameter passed to it as input by the control that uses the command. So that parameter will be assigned accordingly. This is a prime use case for the CommandParameter by the way, just set it to you type and then you can use it in the function.
|
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.