Dynamically create simple views from ViewModels in WPF?

I am slowly learning WPF using this article and other resources.

I focus on the application logic - defining a model + viewModel and creating teams that work with them. I haven't looked at the .xaml format and view .xaml .

While I'm working on logic, I want to have a view that any viewModel that I associate with it can display. Submission should

  • Provide any public string properties as text fields and bind the text field to the property
  • Display property name as label.
  • Provide any public “Command” property as a button and bind the button to the command (only possible if the command takes no arguments?)

Is something like this possible when creating an MVVM template? If so, how do I achieve this? In addition, the article suggests avoiding the use of .xaml codebehind - can this view be implemented in pure xaml?

+7
source share
4 answers

I do not think this is possible only in XAML. If you want to generate your views at runtime, you just need to use reflection over ViewModels and create controls accordingly. If you want to generate views at compile time, you can generate xaml files from ViewModels at build time using the template engine (like T4 or a 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 depends on the needs of your application.

And also in MVVM code, Ok code for visual logic and binding to the / viewmodel model, which cannot be executed only in XAML.

+6
source

I'm not sure if this is a suitable use for a “pure MVVM” approach, of course, not everything will be achieved simply by binding. And I would simply abandon the idea of ​​avoiding the use of code for your "presentation" here, it is inherently a software task. The only thing you have to adhere to is not to give the ViewModel any idea of ​​the view, so when you replace it with the “real thing”, it won’t work.

But of course it seems reasonable; it is almost like a debugging visualizer - you can use an existing tool to do this.

(If you want to do this basically XAML with a standard ItemsControl and templates, you can write a converter to display the properties of your ViewModel by reflecting in one form or another with which you can bind a set of wrapper objects with open metadata, but I think that ensuring that properties that are exposed are properly tied are more useful than it costs)

+3
source

I'm halfway to implementing this now, hope the following code helps someone else try to do it. It would be great 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; } } 
+3
source

Pointer. Create a dynamic DataTemplate as a string bound to a specific virtual machine (Target). Disassemble it using XamlReader. Plonk is in your application resources in code.

Just an idea .. run with it .. It must be done by some other type besides View or ViewModel.

+1
source

All Articles