MVVM动态地将字段添加到View

时间:2017-05-24 12:15:51

标签: c# wpf mvvm caliburn.micro

我正在使用caliburn.micro MVVM框架开发WPF应用程序。 为了开发搜索屏幕,我需要根据模型属性动态地将字段加载到视图中。

考虑以下视图和视图模型:

  • SearchViewModel
  • 搜索查看

我们假设T是以下示例中的一种产品。

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;}
}

我有一个名为SearchView.xaml的用户控件,其上没有内容。 每当加载View时,应将新字段添加到视图中,并且字段应绑定到属性。

根据上面的代码示例,Product类中有3个公共属性,因此应该动态地将3个TextBox添加到视图中。当用户在文本字段中输入数据时,应更新相应的属性。

这可能吗? 任何专家都可以通过提供一些例子帮助我实现这一目标吗?

2 个答案:

答案 0 :(得分:2)

我建议以不同的方式解决这个问题。我没有考虑动态地向视图/模型添加属性,而是考虑将关于这些属性的信息添加到viewmodel上的列表中。然后,该列表将绑定到ItemsControl,其模板看起来像TextBox

所以你的视图模型会为它提供一个属性&#34;&#34;你想检查一下。在此属性的setter中,使用reflection来枚举您感兴趣的属性,并将某种FieldInfo类(您创建的)的实例添加到带有绑定的属性列表中。

这样可以保持所有MVVM兼容,并且不需要使用自己的代码动态创建控件。

下面的示例使用我自己的MVVM库(作为nuget包)而不是caliburn.micro,但它应该足够类似于遵循基本思想。可以从this BitBucket repo下载该示例的完整源代码。

正如您在附带的屏幕截图中所看到的,搜索字段是在视图上动态创建的,视图中没有任何代码。一切都在viewmodel上完成。这也使您可以轻松访问用户输入的数据。

视图模型:

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}")));
            })); }
        }
    }
}

SearchFieldInfo类:

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

        public string Name { get; }

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

观点:

<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>

模型类:

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

答案 1 :(得分:1)

以下是如何使用反射在控件中为TextBox的每个公共属性生成T的基本示例。

<强> 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);
                }
            }
        }
    }
}

另一个选择显然是创建一个&#34;静态&#34;查看每种类型的T并像往常一样定义XAML标记中的TextBox元素。