简体   繁体   中英

Adding dynamically UserControls in WPF/MVVM

Is it possible to add and bind user controls dynamically? Maybe I'll show sample code to show what I exactly mean.

MainWindow:

 <UniformGrid
    Rows="11"
    Columns="11"
    DataContext="{StaticResource vm}">

    <local:DynamicUserControl
        ButClickControl="{Binding Path=UserControlObjects[0].ButClickCommand}"
        SomeDataInUserControl="{Binding Path=UserControlObjects[0].SomeData, Mode=OneWay}" />

    <local:DynamicUserControl
        ButClickControl="{Binding Path=UserControlObjects[1].ButClickCommand}"
        SomeDataInUserControl="{Binding Path=UserControlObjects[1].SomeData, Mode=OneWay}" />

    <local:DynamicUserControl
        ButClickControl="{Binding Path=UserControlObjects[2].ButClickCommand}"
        SomeDataInUserControl="{Binding Path=UserControlObjects[2].SomeData, Mode=OneWay}" />

    .....


</UniformGrid>

In ViewModel there is an array of UserControlObjects . But in this array I will have over 100 elements, so it is not the best option to write all elements one by one. Is there any way to add DynamicUserControls not in XAML but somewhere in code in loop with keeping the MVVM pattern and binding?

Use an ItemsControl with the UniformGrid as ItemsPanel and the DynamicUserControl in the ItemTemplate:

<ItemsControl DataContext="{StaticResource vm}"
              ItemsSource="{Binding UserControlObjects}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid Rows="11" Columns="11"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <local:DynamicUserControl
                ButClickControl="{Binding ButClickCommand}"
                SomeDataInUserControl="{Binding SomeData}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

In my opinion, you would want to keep any controls out of your view model. You could however keep the individual view models that back the controls in a list within the main view model. For example, create the view model that will provide the data for the “dynamic” controls.

   class SubViewModel
   {
      public string Name { get; private set; } = string.Empty;

      public SubViewModel(string aName)
      {
         Name = aName;
      }
   }

And in the main view model you can do whatever you would do to dynamically create instances. In this case, I am just creating then in a for loop.

   class MainWindowViewModel
   {
      public ObservableCollection<SubViewModel> SubViewModels
      {
         get
         {
            return mSubViewModels;
         }
      } private ObservableCollection<SubViewModel> mSubViewModels = new ObservableCollection<SubViewModel>();

      public MainWindowViewModel()
      {
         for(int i = 0; i < 30; i++)
         {
            SubViewModels.Add(new SubViewModel($"Control: {i}"));
         }
      }
   }

Then in the view, you can utilize an ItemsControl with an UniformGrid based ItemsPanelTemplate, and then whatever you want for the data template, whether you define it there explicitly, or make a user control (like your local:DynamicUserControl) to clean things up. In this sample, the data template it explicitly defined.

<Window x:Class="ListOfViewsSample.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ListOfViewsSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
   <Window.DataContext>
      <local:MainWindowViewModel/>
   </Window.DataContext>
   <Grid>
      <ItemsControl ItemsSource="{Binding SubViewModels}">
         <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
               <UniformGrid/>
            </ItemsPanelTemplate>
         </ItemsControl.ItemsPanel>
         <ItemsControl.ItemTemplate>
            <DataTemplate>
               <Grid Background="LightGray" Margin="10">
                  <Label Content="{Binding Name}" Margin="5" HorizontalAlignment="Center" VerticalAlignment="Center"/>
               </Grid>
            </DataTemplate>
         </ItemsControl.ItemTemplate>
      </ItemsControl>
   </Grid>
</Window>

which results in the following:

在此处输入图像描述

If don't want the multiple dynamic views to be the same, you can look into data template selectors to display something different based on the specified view model, but based in your question I think you were looking for a list of the same control/data. Hope this helps!

The usual way of doing this is:

  1. Create an ItemsControl for the dynamic items you want to create
  2. Override the ItemsPanel to whatever you need (UniformGrid in your case)
  3. Bind it to a list of view models, with one view model per control
  4. Define DataTemplates to map each view model type to its corresponding view type

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM