工作 · 2022年7月12日 0

MVVM WPF 动态添加控件及绑定用WPF mvvm如何动态添加自定义控件问题

现在有这么一个需求,需要动态添加自定义控件到界面,自定义控件数量不固定,内容是通过服务获取的,以前是做winform的,winform简单,直接 new自定义控件添加上去就行,现在wpf mvvm模式,不知道怎么做了,能否在viewmodel中添加一个 observablecollection,界面绑定这个属性,界面自动生成这么多个自定义控件?如果可以,各位大佬能否提供下实现思路,有个demo什 么的最好了。

<Window x:Class="WpfApplication5.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <!--控件模板-->
        <DataTemplate x:Key="UserDataTemplate">
          
            <Border BorderThickness="2" BorderBrush="Black" Margin="2">
                <StackPanel>
                    <Image />
                    <Label HorizontalAlignment="Center" Content="{Binding Path=Name}" />
                </StackPanel>
            </Border>
        </DataTemplate>
    </Window.Resources>
    <Grid Name="mygrid">
        <!--容器-->
        <ItemsControl x:Name="testList" ItemTemplate="{StaticResource UserDataTemplate}" ItemsSource="{Binding Btns}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApplication5
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
       public MainWindowViewModel user { get; set; }
        public MainWindow()
        {
            InitializeComponent();
            user = new MainWindowViewModel();
            mygrid.DataContext = user;//指定ViewModel
        }

    }
}
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;

namespace WpfApplication5
{
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public MainWindowViewModel()
        {
            Btns = new ObservableCollection<Item>() { new Item(){Name = "测试1"}, new Item(){Name = "测试2"} };
        }


        private ObservableCollection<Item> btns;

        public ObservableCollection<Item> Btns
        {
            get { return btns; }
            set { btns = value; }
        }
    }  
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfApplication5
{
    public class Item : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("Name"));
                }
            }
        }
    }
}
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local ="clr-namespace:WpfApp1"
        Title="MainWindow" Height="450" Width="800">
  <Window.Resources>
    <DataTemplate DataType="{x:Type local:MyContract}"> <!-- 类型是MyContract的,用这个模板 -->
      <StackPanel Width="128" Height="150" Margin="20">
        <Image Source="{Binding ImageUrl}" />
        <TextBlock Text="{Binding Name}" />
      </StackPanel>
    </DataTemplate>
    <DataTemplate DataType="{x:Type local:MyCard}"><!-- 类型是MyCard的,用这个模板 -->
      <Grid Width="128" Height="150" Margin="20" Background="{Binding Color}">
        <TextBlock Text="{Binding Color}" FontSize="24" VerticalAlignment="Center" />
      </Grid>
    </DataTemplate>
  </Window.Resources>
  <Grid>
    <ItemsControl ItemsSource="{Binding Items}" > <!-- 不直接指定模板,而是用数据类型来决定 -->
      <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate><WrapPanel IsItemsHost="True" /></ItemsPanelTemplate>
      </ItemsControl.ItemsPanel>
    </ItemsControl>
  </Grid>
</Window>
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Items = new ObservableCollection<object>()
            {
                new MyContract(){ Name = "杀马特丶蛮牛", ImageUrl = "https://profile.csdnimg.cn/A/3/4/1_zhengbingfe"},
                new MyContract(){ Name = "正怒月神", ImageUrl = "https://profile.csdnimg.cn/F/F/E/1_hanjun0612"},
                new MyContract(){ Name = "OrdinaryCoder", ImageUrl = "https://profile.csdnimg.cn/6/0/9/1_weixin_40440974"},
                new MyCard() { Color = "PeachPuff"},
                new MyCard() { Color = "SeaShell"},
                new MyCard() { Color = "SteelBlue"},
            };
            this.DataContext = this;
        }
        public ObservableCollection<object> Items { get; }
    }
    public class MyContract // 可自行实现INotifyPropertyChanged接口
    {
        public string Name { get; set; }
        public string ImageUrl { get; set; }
    }
    public class MyCard
    {
        public string Color { get; set; }
    }
}

另外,数据模板的强处,在于它可以利用数据类型,来决定具体的展示模板。