views:

66

answers:

3

I'm experimenting with MVVM and I can't quite wrap my mind around it.

I have a View (windows) that has several repeated controls.

Let's say I have 4 textbox - button pairs. Their behavior should be the same, after pressing a button paired textbox says "Hello World!"

I have tried several options I could think of:

  1. One ViewModel, 4 string properties binded to textboxes, 1 command
    • When I bind each button to the same command I can't tell which property needs to be set.
    • Passing enum to CommandParameter feels awkward.
  2. One ViewModel and UserControl that hosts a textbox and a button repeated 4 times.
    • Now I need to expose all the properties like Command, CommandParameter, Text etc.. Seems like a lot of work.
    • Still can't say which property needs to be updated after click.
  3. Each UserControl has a ViewModel
    • This solves button clicking and setting property, but now I have no clue how to extract texts from nested ViewModels to the window ViewModel.

Is there any other way? I suspect DataTemplates could be of use, but I'm not sure how.

+1  A: 

Number 3. Except there would just be one UserControl with viewmodel and then the Main page that would have multiple instances of that UserControl on it. If the main window needs info from the UserControl you could pass it through events or use something like MVVM Light and it's messenger class.

nportelli
+2  A: 

What you describe is such an abstract and contrived idea that it doesn't warrant MVVM. You're talking about TextBoxes and Buttons, which are all 'View', and not the MVVM way of thinking. You'd almost always start with a Model.

There is no 'Model' per-se here though; your specification is literally to set the value of a TextBox on a Button click. The seemingly random list of '4' items (picked out of nowhere) and a seemingly useless TextBox mean nothing.

Putting that aside and assuming that you have a set of 4 business entities, each with a field on them that is user-editable, and an action that the user can trigger, you'd do this:

  • Create a ViewModel class to represent an item - eg MyItemModel
  • Create a ViewModel class to represent the set of items (likely it will just return a Collection of the first) - eg AllMyItemsListModel

Then for the View:

  • Create an ItemsControl, with ItemsSource bound to an instance of the 'collection' of the second ViewModel class
  • For each ItemTemplate, have a template or UserControl for each item
  • Within the template or UserControl, bind the TextBox's Text property to the appropriate property of the first class
  • Bind the Button's Command property to a property on the first class returning an ICommand - using RelayCommand for example

I don't know what you mean about 'extracting texts from nested ViewModels to the window ViewModel' - what does that mean and why would you want to do it?

Hope that helps.

Kieren Johnstone
@Kieren Johnstone: +1 for the well-presented answer. But I don't think the statement of creating a ViewModel class to represent a Model Item is true. Instead the ViewModel class is created according to the View and View only. If the View requires some Model data, then the particular property in the model is used by the ViewModel to serve the View's need. Hence the first ViewModel could contain the textbox text(from Model if required) and the Icommand and the second could contain the `Collection<FirstViewModel>`
Veer
Thanks for the comment, but I was suggesting that you would never start the *app* (not the ViewModel) with a View; you'd never write an app 'to put text in boxes', you'd write it for some real purpose with real entities. So you need a Model to start off with. I believe my answer already says the first class would contain a property for the text and an ICommand, and the second would be a collection, too.?
Kieren Johnstone
@Kieren Johnstone: You are right I got the wrong approach here. I see it now. But I agree that ViewModels should be created based on view not the Model.Anyway in my case I don't have a collection its a fixed number of repeated controls that should represet a fixed number of strings in my Model.
Kugel
Could you re-read my answer and my follow-up: I didn't propose anyone creates a ViewModel starting with the model. I was saying 'You write an application based on/to work with a model', as opposed to 'you write an application based on a view' [which is what your question suggests]. :)
Kieren Johnstone
Accepted. After some thought I discovered that my model had a design flaw and now this answer fits very well.
Kugel
+1  A: 

There's no need to create a separate ViewModel for a reusable control that has such simple behavior. Just by adding a few DependencyProperties and an event handler to the simple UserControl you can reuse the logic and only set the properties that are actually different on each instance. For the UserControl XAML you just need to hook up the TextBox to the DependencyProperty and the Button to a Click handler.

<DockPanel>
    <Button Content="Reset" Click="Button_Click"/>
    <TextBox Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=Text}"/>
</DockPanel>

The code for the UserControl just needs to define the properties that can be bound externally and the handler to reset the Text.

public partial class ResetTextBox : UserControl
{
    public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
        "Text",
        typeof(string),
        typeof(ResetTextBox),
        new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    public static readonly DependencyProperty ResetTextProperty = DependencyProperty.Register(
        "ResetText",
        typeof(string),
        typeof(ResetTextBox),
        new UIPropertyMetadata(String.Empty));

    public string ResetText
    {
        get { return (string)GetValue(ResetTextProperty); }
        set { SetValue(ResetTextProperty, value); }
    }

    public ResetTextBox()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Text = ResetText;
    }
}

Then to use the control you just need to bind to your ViewModel string properties and set the default text that should be applied on a reset which can either be hardcoded here, or bound to other VM properties.

<StackPanel>
    <local:ResetTextBox ResetText="One" Text="{Binding Name1}"/>
    <local:ResetTextBox ResetText="Two" Text="{Binding Name2}"/>
    <local:ResetTextBox ResetText="Three" Text="{Binding Name3}"/>
</StackPanel>
John Bowen
This is my appreach #2. In your case you have only two exposed properties. In my case I had 4 or 5 and that seems like a lot of work, just exposing properties.
Kugel
This is not what you described in #2. Your approach needed extra properties because you were relying on the ViewModel to do the work that the UserControl is doing here. If you use this method you don't have extra properties for all of the command functions because instead of acting through a passed through Command from your ViewModel all of that is handled internally by the UserControl.
John Bowen
You are right. I like your answer, because it's practical. But it is not testable. But I guess It's ok for small UserControls.
Kugel