How do I pass the information from View to ViewModel with DelegateCommand?
Asked Answered
I

6

13

In my View, I have a button.

When the user clicks this button, I want to have the ViewModel save the context of the TextBlock in the database.

<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">
    <TextBlock Text="{Binding FirstName}"/>
    <TextBox Text="Save this text to the database."/>
    <Button Content="Save" Command="{Binding SaveCommand}"/>
</StackPanel>

However, in my DelegateCommand in my ViewModel, the "Save()" method doesn't pass any arguments, so how do I get data from the view at that point?

#region DelegateCommand: Save
private DelegateCommand saveCommand;

public ICommand SaveCommand
{
    get
    {
        if (saveCommand == null)
        {
            saveCommand = new DelegateCommand(Save, CanSave);
        }
        return saveCommand;
    }
}

private void Save()
{
    TextBox textBox = ......how do I get the value of the view's textbox from here?....
}

private bool CanSave()
{
    return true;
}
#endregion
Introvert answered 28/5, 2009 at 10:21 Comment(0)
C
20

Check out this MSDN article by Josh Smith. In it, he shows a variation of DelegateCommand that he calls RelayCommand, and the Execute and CanExecute delegates on RelayCommand accept a single parameter of type object.

Using RelayCommand you can pass information to the delegates via a CommandParameter:

<Button Command="{Binding SaveCommand}" 
        CommandParameter="{Binding SelectedItem,Element=listBox1}" />

Update

Looking at this article, it appears that there is a generic version of DelegateCommand which accepts a parameter in a similar way. You might want to try changing your SaveCommand to a DelegateCommand<MyObject> and change your Save and CanSave methods so that they take a MyObject parameter.

Chimkent answered 28/5, 2009 at 10:28 Comment(3)
I actually solved my problem by binding the TextBox to a ViewModel Property (INotifyPropertyChanged), the value of which of course the Save() command has access to, but your suggestions are very interesting, will check them out.Introvert
There's a specific (and reasonably common) use case where Matt's solution definitely wins over accessing a separate bound value: commands executed from within an ItemsControl. Here, a button, say, in a ListView can use its DataContext as a parameter and the item on which the button was clicked is passed along with the command. It's difficult to work out which of the many items had its button clicked otherwise.Poise
Note that the generic DelegateCommand<T> has an important restriction (which is not picked up until run time - the compiler won't complain): you cannot use a value type as the parameter. The official advice is to use a nullable value type (which is allowed) and check before use that it contains a value (msdn.microsoft.com/en-us/library/gg431410%28v=pandp.50%29.aspx - see "Remarks" section).Poise
G
13

here is the elegant way.

Give a name to your textbox, then bind the CommandParameter in the button to it's Text property:

<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">
    <TextBlock Text="{Binding FirstName}"/>
    <TextBox x:Name="ParameterText" Text="Save this text to the database."/>
    <Button Content="Save" Command="{Binding SaveCommand}"
            CommandParameter="{Binding Text, ElementName=ParameterText}"/>
</StackPanel>
Guardsman answered 3/9, 2009 at 15:13 Comment(0)
T
12

In your VM:

private DelegateCommand<string> _saveCmd = new DelegateCommand<string>(Save);

public ICommand SaveCmd{ get{ return _saveCmd } }

public void Save(string s) {...}

In you View, use CommandParameter like Matt's example.

Tungstate answered 28/5, 2009 at 17:41 Comment(1)
Not a complete answer but this pointed me in the right direction for using CommandParameter and specifying a type for DelegateCommand. ThanksIxion
P
5

You're asking about passing data via the button Command.

What you actually want, I think, is to bind your Textbox's text to a public property in your ViewModel:

<!-- View: TextBox's text is bound to the FirstName property in your ViewModel -->
<TextBox Text="{Binding Path=FirstName}" />
<Button Command="{Binding SaveCommand}"/>

<!-- ViewModel: Expose a property for the TextBox to bind to -->
public string FirstName{ get; set; }
...
private void Save()
{
    //textBox's text is bound to --> this.FirstName;
}
Penetrate answered 28/5, 2009 at 17:59 Comment(2)
This is what I typically do, since the TextBox value is already bound to a property in the viewmodel. There's no need to pass it as a parameter to the command.Thermae
It makes more sense (code-readability) to pass it as a parameter if you use the text for only one purpose. Like for example passing activation key to your VM.Stroud
I
1

I'm not allowed to make comments yet, I guess. I'm responding to Carlos' suggestion because I tried it out. While it's a great idea, DelegateCommand would need to be modified in some way because otherwise you'll get this error: A field initializer cannot reference the non-static field, method, or property 'MyViewModel.Save(string)'.

Ivette answered 14/9, 2010 at 14:28 Comment(0)
P
0

textblock inside grid, binding in view:

                            <telerik:GridViewDataColumn Header="Anunciante">
                            <telerik:GridViewDataColumn.CellTemplate>
                                <DataTemplate>
                                    <Grid Background="{Binding AnuncianteColor}" Margin="0,7,0,7" VerticalAlignment="Center"  >
                                        <TextBlock Text="{Binding Anunciante}" 
                                                   Padding="5,10,5,10" 
                                                   HorizontalAlignment="Stretch" 
                                                   TextAlignment="Center"
                                                   Tag="{Binding Index}"
                                                   MouseDown="AnuncianteMouseDown" />
                                    </Grid>
                                </DataTemplate>
                            </telerik:GridViewDataColumn.CellTemplate>
                        </telerik:GridViewDataColumn>

and a hidden button biding for command

<Button Visibility="Collapsed" x:Name="AnunciantedoubleClick" Command="{Binding AnuncianteClickCommand}"></Button>

code behind

        private void AnuncianteMouseDown(object sender, MouseButtonEventArgs e) 
    {
        //if (e.ClickCount == 2)
        //{
            this.AnunciantedoubleClick.Command.Execute(((TextBlock)sender).Tag);
        //}
    }

and finally code in ModelView:

public ICommand AnuncianteClickCommand { get; }

in a constructor:

this.AnuncianteClickCommand = new DelegateCommand<object>(this.AnuncianteClickDelegate);

and the delegate shows a message with the row index (custom messagebox service, sorry)

        private void AnuncianteClickDelegate(object v) 
    {
        MessageBoxService.Show(v.ToString());
    }
Penetrance answered 11/7, 2022 at 10:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.