jwillmer
jwillmer

Reputation: 3790

MVVM Light: How to get data from usercontrols?

I have a class MyDataCollection that contains MyMetaData and MyData. In my application i have two usercontrolls that display input fields to the user. One for the MyMetaData and the other for MyData. Both usercontrols are included in the MainPage.

My Question is: How should i get the data from the usercontrols then the user klicks on the save-button (located on the mainpage)?

Update I have changed my code accordingly to blindmeis post but now the MetaDataView is not shown:

<UserControl.Resources>
    <DataTemplate x:Key="MetaDataTemplate">
        <view:MetaDataView/>
    </DataTemplate>
</UserControl.Resources>

<Grid>
    <ContentPresenter Content="{Binding MetaDataTemplate}"/>
</Grid>

Upvotes: 0

Views: 1541

Answers (6)

blindmeis
blindmeis

Reputation: 22445

why not doing mvvm the easy way?(viewmodel first). you say you have a mainpage - this means you also have a mainpageviewmodel. your mainpageviewmodel handles at least the save command. now you want to show MyMetaData and MyData on your mainpage. so the easy way would be to create one MyMetaData instance and one MyData instance in your mainviewmodel.

public class MainPageViewmodel
{
    public ICommand SaveCommand { get; set; }
    public MyDataViewmodel MyData { get; set; }
    public MyMetaDataViewmodel MyMetaData { get; set; }

    public MainPageViewmodel()
    {
        this.MyData = new MyDataViewmodel();
        this.MyMetaData = new MyMetaDataViewmodel();
    }

}

public class MyDataViewmodel
{}

public class MyMetaDataViewmodel
{}

your mainpage just need 2 datatemplates and 2 contentpresenter.

//resources

    <DataTemplate DataType="{x:Type Local:MyDataViewmodel}">
        <view:MyDataUserControl/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type Local:MyMetaDataViewmodel}">
        <view:MyMetaDataUserControl/>
    </DataTemplate>

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <ContentPresenter Content="{Binding MyData}" Grid.Column="0"/>
    <ContentPresenter Content="{Binding MyMetaData}" Grid.Column="1"/>
    <Button Content="Save" Command="{Binding SaveCommand}" Grid.Column="2"/>
</Grid>

because your mainpageviewmodel has both "child" viewmodel, you have all information you want on your savecommand.

if you have another scenario pls update your question, maybe post some code.

EDIT: i have no silverlight so that just a suggestion: maybe rachel can give you a better answer.

<Grid>
  <ContentPresenter Content="{Binding MyMetaData}" ContentTemplate="{StaticResource MetaDataTemplate}"/>
</Grid>

if silverlight cant handle datatemplates with datatype you could just put the usercontrol there directly.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <view:MyDataUserControl DataContext="{Binding MyData}" Grid.Column="0"/>
    <view:MyMetaDataUserControl DataContext="{Binding MyMetaData}" Grid.Column="1"/>
    <Button Content="Save" Command="{Binding SaveCommand}" Grid.Column="2"/>
</Grid>

Upvotes: 4

jwillmer
jwillmer

Reputation: 3790

I have two solutions now:

View:

<ContentPresenter Content="{Binding MyMetaDataView}" />

ViewModel:

public MetaDataViewModel MyMetaDataViewModel { get; set; }
public MetaDataView MyMetaDataView { get; set; }

public MainViewModel()
{
    MyMetaDataViewModel = new MetaDataViewModel();
    MyMetaDataView = new MetaDataView();

    MyMetaDataView.DataContext = MyMetaDataViewModel;
}

or ----

View:

<UserControl.Resources>
    <DataTemplate  x:Key="MetaDataViewTemplate">
        <view:MetaDataView />
    </DataTemplate>
</UserControl.Resources>
...
<ContentPresenter Content="{Binding MyMetaDataViewModel}" ContentTemplate="{StaticResource MetaDataViewTemplate}"/>

ViewModel:

public MetaDataViewModel MyMetaDataViewModel { get; set; }

public MainViewModel()
{
    MyMetaDataViewModel = new MetaDataViewModel();
}

Upvotes: 0

Batuu
Batuu

Reputation: 595

I'll give you a little sample, how you can use the MVVM Light Messenger for ViewModel-to-ViewModel communication. Say you have an MyDataCollection class:

public class MyDataCollection
{
    public int MyData;
    public string MyMetaData;
}

On your MainViewModel you have a RelayCommand (from MVVM light toolkit) binded by your View's SaveButton. When the Connad is executed, you will have to send a Message with a callback action to request data from the subcriber. The callback takes MyDataCollection as parameter:

public class MainViewModel : ViewModelBase 
{
    public RelayCommand SaveCommand { get; private set; }

    //Ctor
    public MainViewModel()
    {
      SaveCommand = new RelayCommand(
                () =>
                Messenger.Default.Send<NotificationMessageAction<MyDataCollection>>(
                    new NotificationMessageAction<MyDataCollection>("SaveData", SaveCallback)));
    }

    private void SaveCallback(MyDataCollection dataCollection)
    {
        // process your dataCollection... 
    }
}

The UserControlViewModel has properties the InputTextBoxes are binded too. It just has to register to the message and call the callback with data properties:

public class UserControlViewModel : ViewModelBase
{
    //Properties
    public string UserControlMetaData { get; set; }
    public int UserControlData { get; set; }

    //Ctor
    public UserControlViewModel()
    {
        Messenger.Default.Register<NotificationMessageAction<MyDataCollection>>(this, MessageReceived);
    }

    // private Method to handle all kinds of messages.
    private void MessageReceived(MessageBase msg)
    {
        if(msg is NotificationMessageAction<MyDataCollection>)
        {
            var actionMsg = msg as NotificationMessageAction<MyDataCollection>;
            if(actionMsg.Notification == "SaveData")  // Is this the Message, we are looking for?
            {
                // here the MainViewModels callback is called.
                actionMsg.Execute(new MyDataCollection() {MyData = UserControlData, MyMetaData = UserControlMetaData});
            }
        }
    }

}

Upvotes: 1

Rachel
Rachel

Reputation: 132558

Since you tagged this question as MVVM, your ViewModels should contain both your SaveCommand and all the data needed to perform the actual save

Your MainViewModel should contain MyMetaData and MyData properties (which are bound to their respective UserControls), and each of those objects should contain properties for any data needed in the UserControl. For example, if your UserControl had a TextBox for Name, then your data object should have a property for the Name that the TextBox binds to.

If the Save button is located in one of those UserControls then the respective ViewModel should have a SaveCommand that gets executed when the Button is clicked. All the data needed for the Save is also located in that ViewModel, so you're good to go.

If your MainViewModel is in charge of saving the data, then it should be able to hook into your sub ViewModel's SaveCommand and attach it's own method, such as

this.MyData.SaveCommand = this.SaveCommand();

and all the data needed for the save can be found in this.MyData

If the SaveButton is located in your MainView, and not in one of the UserControls, then the SaveCommand should be part of MainViewModel, and all the data needed for the save can be found in this.MyData or This.MyMetaData.

Remember, with MVVM your ViewModels are your application. The View is just a pretty interface that allows users to interact with your ViewModels.

Upvotes: 4

Rumplin
Rumplin

Reputation: 2768

You will have to use messengers or you will have to set the properties over the ViewModelLocator

Messenger example of how I use it to set the UI language ViewModel A, I register a listener here with the "SetLanguage" token:

Messenger.Default.Register<string>(this, "SetLanguage", false, input =>
{
    SetLanguage(input);
});

ViewModel B, here I send the message with the "SetLanguage" token:

Messenger.Default.Send("en-EN", "SetLanguage");

ViewModelLocator example in ViewModel A, I access data in ViewModel B over the locator:

short value = 12;
var myFilteredDataList = ViewModelLocator.ViewModelBStatic.MyDataList.Any(m => m.code == value);

Upvotes: 0

alf
alf

Reputation: 18530

You should use Two-way bindings to automatically update the value in your controller. Take a look at this article.

Here's an example:

<TextBox Text="{Binding MyMetaData, Mode=TwoWay }" />
<TextBox Text="{Binding MyData, Mode=TwoWay }" />

Upvotes: 1

Related Questions