7

I'm slowly learning WPF using this article and other resources.

I am focusing on the application logic - defining the model + viewModel, and creating commands that operate on these. I have not yet looked at the view and the .xaml format.

While I am working on the logic, I want to have a view that can render any viewModel I bind to it. The view should

  • Render any public string properties as text boxes, and bind the text box to the property
  • Render the name of the property as a label.
  • Render any public 'Command' property as a button, and bind the button to the command (perhaps only if the command takes no arguments?)

Is something like this possible while maintaing the MVVM design pattern? If so, how would I achieve it? Also, the article suggests to avoid using .xaml codebehind - can this view be implemented in pure xaml?

3
  • I think thats a great idea! You could have a working application without any but the most basic UI and start testing the viewmodel/model properly before the designer starts working. =) Commented Apr 13, 2012 at 11:32
  • you dont need the view to test your viewmodel. that what mvvm for ;) Commented Apr 13, 2012 at 11:35
  • You don't need the UI to unit test the viewmodel, but I think a basic UI'd be very helpful to see if all components work together properly. Commented Apr 13, 2012 at 11:37

4 Answers 4

6

I don't think it is possible in XAML only. If you want to generate your views in runtime then you have to just use reflection over your ViewModels and generate controls accordingly. If you want to generate views at compile time then you can generate xaml files from your ViewModels at build time with some template engine (like T4 or string template) or CodeDom. Or you can go further and have some metadata format (or even DSL) from which you will generate both models and views and so on. It is up to your app needs.

And also in MVVM code-behind is Ok for visual logic and binding to model/viewmodel that can't be done in XAML only.

Sign up to request clarification or add additional context in comments.

Comments

3

I'm not sure this is an appropriate use for a "pure MVVM" approach, certainly not everything is going to be achieved simply by binding. And I'd just throw away the idea of avoiding using code-behind for your "view" here, this is an inherently programmatic task. The one thing you should stick to is giving the ViewModel no knowledge of the view, so that when you replace it with the "real thing" there is no work to do.

But certainly seems reasonable thing to do; it almost sounds more like a debugging visualiser - you may be able to leverage an existing tool for this.

(If you did want to do this in mostly XAML with standard ItemsControls and templates you might write a converter to expose properties of your ViewModel by reflection in some form that you can bind to, a collection of wrapper objects with exposed metadata, but I think ensuring that the properties exposed are properly bindable would be more work than it's worth)

Comments

3

I'm halfway through implementing this now, I hope the following code will help anyone else trying to do this. It might be fun to turn into a more robust library.

AbstractView.xaml:

<UserControl x:Class="MyApplication.View.AbstractView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel Name="container">
    </StackPanel>
</UserControl>

AbstractView.xaml.cs:

public partial class AbstractView : UserControl
{
    public AbstractView()
    {
        InitializeComponent();

        DataContextChanged += Changed;
    }

    void Changed(object sender, DependencyPropertyChangedEventArgs e)
    {
        object ob = e.NewValue;
        var props = ob.GetType().GetProperties();

        List<UIElement> uies = new List<UIElement>();
        foreach (var prop in props)
        {
            if (prop.PropertyType == typeof(String))
                uies.Add(makeStringProperty(prop));
            else if (prop.PropertyType == typeof(int))
                uies.Add(makeIntProperty(prop));
            else if (prop.PropertyType == typeof(bool))
                uies.Add(makeBoolProperty(prop));
            else if (prop.PropertyType == typeof(ICommand))
                uies.Add(makeCommandProperty(prop));
            else
            {
            }
        }

        StackPanel st = new StackPanel();
        st.Orientation = Orientation.Horizontal;
        st.HorizontalAlignment = HorizontalAlignment.Center;
        st.Margin = new Thickness(0, 20, 0, 0);
        foreach (var uie in uies) {
            if (uie is Button)
                st.Children.Add(uie);
            else
                container.Children.Add(uie);
        }
        if (st.Children.Count > 0)
            container.Children.Add(st);

    }

    UIElement makeCommandProperty(PropertyInfo prop)
    {
        var btn = new Button();
        btn.Content = prop.Name;

        var bn = new Binding(prop.Name);
        btn.SetBinding(Button.CommandProperty, bn);
        return btn;
    }

    UIElement makeBoolProperty(PropertyInfo prop)
    {
        CheckBox bx = new CheckBox();
        bx.SetBinding(CheckBox.IsCheckedProperty, getBinding(prop));
        if (!prop.CanWrite)
            bx.IsEnabled = false;
        return makeUniformGrid(bx, prop);
    }

    UIElement makeStringProperty(PropertyInfo prop)
    {
        TextBox bx = new TextBox();
        bx.SetBinding(TextBox.TextProperty, getBinding(prop));
        if (!prop.CanWrite)
            bx.IsEnabled = false;

        return makeUniformGrid(bx, prop);
    }

    UIElement makeIntProperty(PropertyInfo prop)
    {
        TextBlock bl = new TextBlock();
        bl.SetBinding(TextBlock.TextProperty, getBinding(prop));

        return makeUniformGrid(bl, prop);
    }

    UIElement makeUniformGrid(UIElement ctrl, PropertyInfo prop)
    {
        Label lb = new Label();
        lb.Content = prop.Name;

        UniformGrid u = new UniformGrid();
        u.Rows = 1;
        u.Columns = 2;
        u.Children.Add(lb);
        u.Children.Add(ctrl);

        return u;
    }

    Binding getBinding(PropertyInfo prop)
    {
        var bn = new Binding(prop.Name);
        if (prop.CanRead && prop.CanWrite)
            bn.Mode = BindingMode.TwoWay;
        else if (prop.CanRead)
            bn.Mode = BindingMode.OneWay;
        else if (prop.CanWrite)
            bn.Mode = BindingMode.OneWayToSource;
        return bn;
    }

}

1 Comment

Can you provide a link to the final project (if it was open sourced)? I would be very interested to see how it looks and what you managed to do.
1

Pointer: Generate a dynamic DataTemplate as a string tied to the specific VM (Target). Parse it via XamlReader. Plonk it into your app resources in code.

Just an idea.. run with it.. Should be done by some type other than the View or the ViewModel.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.