0

I am learning MVVM. Until now I have a window (without MVVM) in which I call in the code behind constructor the method CreateLetterButtons() which creates buttons for each letter in the alphabet in a StackPanel. I do this because it’s easier than creating all these buttons in XAML – especially if I want to change something about these buttons.

public SelectionWindow() {
    InitializeComponent();
    CreateLetterButtons();
    }

private void CreateLetterButtons() {
    for (int i = 65; i <= 90; i++)  // A - Z
        { AddLetterButton(i); }
    }

private void AddLetterButton(int i) {
    Button button = new Button();
    button.Content = (char)i;
    button.MinWidth = 20;
    button.PreviewMouseDown += LetterButtonMouseDown;
    LetterStackPanelAuto.Children.Add(button);
    }

The buttons are used in this window to input text (just a few letters) instead of a keyboard.

Now my question: Should I keep this code in the code behind the window? I guess this would be correct according to MVVM because these buttons are just part of the view and are used to input text into the view. They have no connection to any underlying data (model) so I guess this code should not be in the view model, correct?

Then there is one more little issue: Now my method CreateLetterButtons() runs only when my application runs and the window is loaded so I don’t see these buttons in design view. But if I would call the method CreateLetterButtons() in the constructor of the ViewModel then the letters would be created when the ViewModel constructor is executed which happens even in design mode.

I like the idea of MVVM but I wonder if I should treat it like a law or more like a suggestion which I may ignore from time to time. I work alone and I don’t have to consider what would make sense in a team.

Tarabass
  • 3,132
  • 2
  • 17
  • 35
Edgar
  • 2,527
  • 2
  • 19
  • 40

2 Answers2

3

Do not create UI elements in code behind.

In a real MVVM approach, you would use an ItemsControl to display a collection of Buttons. The ItemsSource property of the ItemsControl is then bound to a collection of characters in a view model class. In the ItemTemplate, the Content property of the Button is bound to the individual character.

<ItemsControl ItemsSource="{Binding Letters}">
    <ItemsControl.DataContext>
        <local:LettersViewModel/>
    </ItemsControl.DataContext>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Content="{Binding}" MinWidth="20" Click="LetterButtonClick"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

The code behind would just initialize the view model and provide the Button Click handler:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void LetterButtonClick(object sender, RoutedEventArgs e)
    {
        var button = (Button)sender;
        Trace.WriteLine("Clicked " + button.Content);
    }
}

public class LettersViewModel
{
    public LettersViewModel()
    {
        Letters = Enumerable.Range('A', 'Z' - 'A' + 1).Select(l => (char)l);
    }

    public IEnumerable<char> Letters { get; private set; }
}

A probably much simpler approach without an explicit VM class would be the following. It works because a string is also an IEnumerable<char>.

<ItemsControl ItemsSource="ABCDEFGHIJKLMNOPQRSTUVWXYZ">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button MinWidth="20" Content="{Binding}" Click="LetterButtonClick"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • Does this qualify as mvvm? The letter data is still defined in code behind and although bound, it is still defined by the view code - you may as well create all the buttons in xaml. – kidshaw Oct 10 '15 at 07:33
  • Thanks. What I don’t understand is: “Do not create UI elements in code behind.” It seems many MVVM users think that code behind is always evil. Why? I understand that the code to connect the view to the model should be in the view model. But why is it bad to have any code in the code behind even if it has nothing to do with the model? I.e. I understand your XAML when I read it but I think a couple of lines of code in C# in the code behind are easier (for me) and they do the same thing. So why is the XAML solution better? I don’t question your expertise, I just don’t understand the reasoning. – Edgar Oct 10 '15 at 08:26
  • [See here](http://stackoverflow.com/a/1002626/1136211). There's also a lot more on the web about "why xaml instead of code behind". – Clemens Oct 10 '15 at 09:10
  • I tried your XAML and I get the error message: "The TypeConverter for "IEnumerable" does not support converting from a string." in the line with ItemsSource="ABCDEFGHIJKLMNOPQRSTUVWXYZ" – Edgar Oct 11 '15 at 04:02
  • Beware of the "why XAML instead of code-behind" philosophy. XAML is very powerful and can do some amazing things but it can't be unit tested. When it comes to view logic you'll be much better off using neither and doing as much as you can in the view model. (One simple example: you canm' – Mark Feldman Oct 11 '15 at 05:05
  • @Edgar That is just the XAML designer complaining. The application still works. – Clemens Oct 11 '15 at 06:36
  • @Clements: Yes, I confirm it works – even when it shows the error message in the designer. Thanks – Edgar Oct 11 '15 at 12:12
0

Your correct in my opinion, that these are view specific and so out of the view model. A bindable collection of button data could facilitate it but it isn't necessary as you describe your app.

As for design time support, call your createletterbuttons method in your constructor in the code behind. That may not be preferred behaviour so you can limit this to only run when in design time. I usu mvvmlight library from nugget which has a static IsInDesignMode property you can IF against.

Hope that helps.

kidshaw
  • 3,423
  • 2
  • 16
  • 28
  • Creating `Button`s in code behind is not MVVM-compliant, which is a part the OP's question. @Clemens offers a real MVVM approach that is based on binding and templating. – xum59 Oct 10 '15 at 08:48
  • Agree and point to the fact, however mvvm doesn't require this. Mvvm is concerned with the input value not the method of input, otherwise the keyboard events that allow text entry to a text box should also be managed by the view model. I agree this is different as the input comes from another control rather than a system event but the same is still applicable. Also, a real MVVM solution would require the buttons to be bound to commands and those commands update the viewmodel entry property directly which is then reflected in the UI. – kidshaw Oct 10 '15 at 10:19
  • @xum59, why are buttons made in code not MVVM compliant? I read a lot of articles about MVVM but I don’t remember reading that anywhere. As far as I understand it is the View Model the connecting part between the View and the Model. I think in this case the buttons are only about the View with no connection to the Model. I like to learn and if there is a real good reason than I accept that but until now I didn’t read a good reason why this button creating code is wrong in the code behind. – Edgar Oct 10 '15 at 11:50
  • @edgar - I do agree with you. Patterns are guides and there are no doubt purists who'll disagree, but I find code behind for the sake of UI acceptable. If it's possible to do in pure xaml then absolutely do that first. – kidshaw Oct 10 '15 at 11:57
  • @Edgar: exact, there's no rule of thumb for not creating controls from code-behind, and, as _kidshaw_ stated above, patterns are guides, not rules. There are plenty of good reasons to use code-behind but IMO, in your specific case, there are better ways to add the buttons. _Clemens_ provides a convenient and very WPF way to do it. – xum59 Oct 10 '15 at 12:14