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
string
properties as text boxes, and bind the text box to the propertyIs 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?
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.
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 ItemsControl
s 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)
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;
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With