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
Edward Tanguay
  • 189,012
  • 314
  • 712
  • 1,047

6 Answers6

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.

Mohammad Sepahvand
  • 17,364
  • 22
  • 81
  • 122
Matt Hamilton
  • 200,371
  • 61
  • 386
  • 320
  • 2
    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. – Edward Tanguay May 28 '09 at 11:28
  • 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. – Bob Sammers May 13 '15 at 10:19
  • 1
    Note that the generic `DelegateCommand` 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 (https://msdn.microsoft.com/en-us/library/gg431410%28v=pandp.50%29.aspx - see "Remarks" section). – Bob Sammers May 13 '15 at 10:23
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>
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.

Carlos
  • 915
  • 1
  • 7
  • 11
  • Not a complete answer but this pointed me in the right direction for using CommandParameter and specifying a type for DelegateCommand. Thanks – vdidxho Mar 05 '18 at 23:38
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;
}
Jeffrey Knight
  • 5,888
  • 7
  • 39
  • 49
  • 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. – sourcenouveau Apr 20 '10 at 14:12
  • 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. – Bassem Sep 25 '17 at 10:53
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)'.

ml_black
  • 371
  • 1
  • 2
  • 10
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());
    }
Ángel Ibáñez
  • 329
  • 1
  • 6