0

I'm developing a WPF application using caliburn.micro MVVM framework.. In-order to develop a search screen, I need to dynamically load fields into the view, based on model properties.

Consider below view and view model:

  • SearchViewModel
  • SearchView

Let's assume T is a type of Product in below example.

public class SearchViewModel<T>
{
   public T Item{get;set;}
}

public class Product
{
   public int Id{get;set;}
   public string Name{get;set;}
   public string Description{get;set;}
}

I have a user control called SearchView.xaml with no contents on it. Whenever View is loaded new fields should be added to the view and field should be bound to the properties.

According to above code example, there are 3 public properties in the Product class, therefore 3 TextBoxes should be added to the view dynamically. When user enters data in the text field, corresponding property should be updated.

Is this possible? Can any experts help me to achieve this by providing some examples?

Rahul
  • 2,431
  • 3
  • 35
  • 77

2 Answers2

2

I would propose going about this differently. Instead of thinking about dynamically adding properties to a view / model, I would think about adding information about those properties to a list on the viewmodel. That list would then be bound to an ItemsControl with a template that looks like a TextBox.

So your view-model would have a property on it for the "thing" you want to examine. In the setter for this property, use reflection to enumerate the properties you are interested in, and add an instance of some kind of FieldInfo class (that you create) to the list of properties with the binding.

This has the benefit of keeping everything all MVVM compatible too, and there is no need to dynamically create controls with your own code.


The example below uses my own MVVM library (as a nuget package) rather than caliburn.micro, but it should be similar enough to follow the basic idea. The full source code of the example can be downloaded from this BitBucket repo.

As you can see in the included screenshots, the search fields are created dynamically on the view without any code in the view. Everything is done on the viewmodel. This also gives you easy access to the data that the user enters.

The view-model:

namespace DynamicViewExample
{
    class MainWindowVm : ViewModel
    {
        public MainWindowVm()
        {
            Fields = new ObservableCollection<SearchFieldInfo>();
            SearchableTypes = new ObservableCollection<Type>()
                              {
                                  typeof(Models.User),
                                  typeof(Models.Widget)
                              };

            SearchType = SearchableTypes.First();
        }

        public ObservableCollection<Type> SearchableTypes { get; }
        public ObservableCollection<SearchFieldInfo> Fields { get; }


        private Type _searchType;

        public Type SearchType
        {
            get { return _searchType; }
            set
            {
                _searchType = value;
                Fields.Clear();
                foreach (PropertyInfo prop in _searchType.GetProperties())
                {
                    var searchField = new SearchFieldInfo(prop.Name);
                    Fields.Add(searchField);
                }
            }
        }

        private ICommand _searchCommand;

        public ICommand SearchCommand
        {
            get { return _searchCommand ?? (_searchCommand = new SimpleCommand((obj) =>
            {
                WindowManager.ShowMessage(String.Join(", ", Fields.Select(f => $"{f.Name}: {f.Value}")));
            })); }
        }
    }
}

The SearchFieldInfo class:

namespace DynamicViewExample
{
    public class SearchFieldInfo
    {
        public SearchFieldInfo(string name)
        {
            Name = name;
        }

        public string Name { get; }

        public string Value { get; set; } = "";
    }
}

The view:

<Window
    x:Class="DynamicViewExample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:DynamicViewExample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="525"
    Height="350"
    d:DataContext="{d:DesignInstance local:MainWindowVm}"
    mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ComboBox
            Grid.Row="0"
            ItemsSource="{Binding Path=SearchableTypes}"
            SelectedItem="{Binding Path=SearchType}" />
        <ItemsControl Grid.Row="1" ItemsSource="{Binding Path=Fields}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Path=Name}" />
                        <TextBox Width="300" Text="{Binding Path=Value}" />
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        <Button Grid.Row="2" Command="{Binding Path=SearchCommand}">Search</Button>
    </Grid>
</Window>

The model classes:

class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string PhoneNumber { get; set; }
    public string Id { get; set; }
}

class Widget
{
    public string ModelNumber { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
}

searching a Widget model searching User model

Bradley Uffner
  • 16,641
  • 3
  • 39
  • 76
  • Intresting,Could you please explain this using some code? – Rahul May 24 '17 at 14:22
  • I'll need a bit of time to prepare something. – Bradley Uffner May 24 '17 at 14:25
  • Here is an example project I created for you to look at. I'm in the process of updating my answer with the most relevant parts. https://bitbucket.org/BradleyUffner/wpf-dynamic-search-fields-example/src – Bradley Uffner May 24 '17 at 14:50
  • This example only provides text fields, but `SearchFieldInfo` and the XAML templates could be extended with template selectors to provide other fields types, such as checkboxes for boolean properties, etc, fairly easily. – Bradley Uffner May 24 '17 at 14:58
1

Here is a basic example of how you could generate a TextBox per public property of the T in the control using reflection.

SearchView.xaml:

<Window x:Class="WpfApplication4.SearchView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication4"
        mc:Ignorable="d"
        Title="SearchView" Height="300" Width="300">
    <StackPanel x:Name="rootPanel">

    </StackPanel>
</Window>

SearchView.xaml.cs:

public partial class SearchView : UserControl
{
    public SearchView()
    {
        InitializeComponent();
        DataContextChanged += SearchView_DataContextChanged;
        DataContext = new SearchViewModel<Product>();
    }

    private void SearchView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue != null)
        {
            Type genericType = e.NewValue.GetType();
            //check the DataContext was set to a SearchViewModel<T>
            if (genericType.GetGenericTypeDefinition() == typeof(SearchViewModel<>))
            {
                //...and create a TextBox for each property of the type T
                Type type = genericType.GetGenericArguments()[0];
                var properties = type.GetProperties();
                foreach(var property in properties)
                {
                    TextBox textBox = new TextBox();
                    Binding binding = new Binding(property.Name);
                    if (!property.CanWrite)
                        binding.Mode = BindingMode.OneWay;
                    textBox.SetBinding(TextBox.TextProperty, binding);

                    rootPanel.Children.Add(textBox);
                }
            }
        }
    }
}

The other option will obviously be to create a "static" view for each type of T and define the TextBox elements in the XAML markup as usual.

mm8
  • 163,881
  • 10
  • 57
  • 88