簡體   English   中英

MVVM在視圖中動態添加字段

[英]MVVM Dynamically add fields into View

我正在使用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的用戶控件,上面沒有任何內容。 無論何時加載視圖,都應將新字段添加到視圖中,並且字段應綁定到屬性。

根據上面的代碼示例,Product類中有3個公共屬性,因此應將3個TextBoxes動態添加到視圖中。 當用戶在文本字段中輸入數據時,應更新相應的屬性。

這可能嗎? 任何專家都可以通過提供一些示例來幫助我實現這一目標嗎?

我建議對此進行不同的處理。 與其考慮動態地向視圖/模型添加屬性,不如考慮將有關這些屬性的信息添加到視圖模型的列表中。 然后,該列表將被綁定到帶有看起來像TextBox的模板的ItemsControl

因此,您的視圖模型將具有您要檢查的“事物”的屬性。 在此屬性的設置器中,使用反射來枚舉您感興趣的屬性,然后將某種FieldInfo類(您創建的)的實例添加到具有綁定的屬性列表中。

這具有使所有MVVM都兼容的優勢,並且無需使用您自己的代碼動態創建控件。


下面的示例使用我自己的MVVM庫(作為nuget程序包)而不是caliburn.micro,但是它應該足夠相似以遵循基本思想。 可以從此BitBucket存儲庫中下載示例的完整源代碼。

如您在隨附的屏幕快照中所見,搜索字段是在視圖上動態創建的,視圖中沒有任何代碼。 一切都在視圖模型上完成。 這也使您可以輕松訪問用戶輸入的數據。

視圖模型:

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

搜索小部件模型 搜索用戶模型

這是一個基本示例,說明如何使用反射在控件中為T每個公共屬性生成一個TextBox

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

顯然,另一個選擇是為每種T類型創建一個“靜態”視圖,並照常在XAML標記中定義TextBox元素。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM