iknow
iknow

Reputation: 9852

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?

Upvotes: 2

Views: 2258

Answers (3)

Rob Goodwin
Rob Goodwin

Reputation: 2767

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:

enter image description here

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!

Upvotes: 3

Clemens
Clemens

Reputation: 128013

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>

Upvotes: 4

Mark Feldman
Mark Feldman

Reputation: 16119

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

Upvotes: 2

Related Questions