Creating dynamic data entry form from the list of fields chosen by User at run time

Viewed 72

I have a WPF application where I have view which lets User choose list of fields at run-time and I am storing in a Text file and I am trying to create a Data entry form based on the list of fields that user created Run-time.

I have developed an solution using code behind but I am trying to implement this using MVVM.

Approach 1: I can create the textBlock and Textbox in the code-behind and bind it to the properties in the Viewmodel. The viewmodel will have all the possible field property.

<TabControl Margin="335,10,10,71" TabStripPlacement="Bottom">
    <TabControl.Resources>
        <Style TargetType="{x:Type TabItem}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TabItem}">
                        <Grid>
                            <Border
                                Name="Border"
                                Margin="0,0,0,0"
                                Background="Transparent"
                                BorderBrush="Black"
                                BorderThickness="1,1,1,1"
                                CornerRadius="5">
                                <ContentPresenter
                                    x:Name="ContentSite"
                                    Margin="12,2,12,2"
                                    HorizontalAlignment="Center"
                                    VerticalAlignment="Center"
                                    ContentSource="Header"
                                    RecognizesAccessKey="True">
                                    <ContentPresenter.LayoutTransform>
                                        <RotateTransform Angle="0" />
                                    </ContentPresenter.LayoutTransform>
                                </ContentPresenter>
                            </Border>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsSelected" Value="True">
                                <Setter Property="Height" Value="40" />
                                <Setter Property="FontSize" Value="20" />
                                <Setter Property="FontWeight" Value="Bold" />
                                <Setter Property="Panel.ZIndex" Value="200" />
                                <Setter TargetName="Border" Property="Background" Value="Black" />
                                <Setter TargetName="Border" Property="BorderBrush" Value="White" />
                                <Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
                                <Setter Property="Foreground" Value="White" />
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter Property="FontSize" Value="20" />
                                <Setter Property="FontWeight" Value="Bold" />
                                <Setter TargetName="Border" Property="Background" Value="Black" />
                                <Setter TargetName="Border" Property="BorderBrush" Value="White" />
                                <Setter Property="Foreground" Value="White" />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </TabControl.Resources>
    <TabItem Header="Product Data 1">
        <Grid x:Name="Grid1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition x:Name="Column_1" Width="*" />
                <ColumnDefinition x:Name="Column_2" Width="*" />
            </Grid.ColumnDefinitions>
            <Border
                Margin="0,0,0,10"
                Background="LightSkyBlue"
                BorderBrush="Gray"
                BorderThickness="2,2,2,2">
                <StackPanel
                    x:Name="MainStack"
                    Margin="20,0,0,0"
                    x:FieldModifier="public"
                    Grid.IsSharedSizeScope="True"
                    Orientation="Vertical" />
            </Border>
            <Border
                Grid.Column="1"
                Margin="0,0,0,10"
                Background="LightSkyBlue"
                BorderBrush="Gray"
                BorderThickness="2,2,2,2">
                <StackPanel
                    x:Name="SecondStack"
                    Grid.Column="1"
                    Margin="20,0,0,0"
                    x:FieldModifier="public"
                    Grid.IsSharedSizeScope="True"
                    Orientation="Vertical" />
            </Border>
            <!--<StackPanel x:Name="ThirdStack" x:FieldModifier="public"  Grid.Column="2" Orientation="Vertical" Grid.IsSharedSizeScope="True" Width="auto" Height="476"  VerticalAlignment="Center" HorizontalAlignment="Center" />
            <Border BorderBrush="Black" BorderThickness="1,1,1,1" Grid.Column="2" Margin="0,0,0,10"/>-->
        </Grid>
    </TabItem>
</TabControl>

Code-Behind=

string[] Data = File.ReadAllLines("Field.txt");
foreach (var part in Data)
{
    Grid mytxtBStack = new Grid();
    MainStack.Children.Add(mytxtBStack);
    ColumnDefinition column_1 = new ColumnDefinition();
    column_1.SharedSizeGroup = "FirstColumn";

    ColumnDefinition column_2 = new ColumnDefinition();
    column_2.SharedSizeGroup = "SecondColumn";

    mytxtBStack.ColumnDefinitions.Add(column_1);
    mytxtBStack.ColumnDefinitions.Add(column_2);

    txtBlock = new TextBlock();
    txtBlock.Name = "MyBlock" + i;
    txtBlock.Text = part;
    txtBlock.FontSize = 14;
    txtBlock.FontWeight = FontWeights.DemiBold;
    txtBlock.Foreground = Brushes.Black;
    txtBlock.Margin = new Thickness(0, 20, 0, 0);
    mytxtBStack.Children.Add(txtBlock);

    txtBox = new TextBox();
    txtBox.Name = "MyText" + i.ToString();
    txtBox.FontSize = 12;
    txtBox.Height = 25;
    txtBox.Width = 250;
    txtBox.BorderThickness = new Thickness(1, 1, 1, 1);
    txtBox.Margin = new Thickness(130, 20, 0, 0);
    txtBox.Foreground = Brushes.Black;
    txtBox.BorderBrush = Brushes.Black;
    txtBox.Background = Brushes.White;
    txtList.Add(txtBox);
    mytxtBStack.Children.Add(txtBox);
}

Approach 2: I can create a dynamic class using the Expandoobject based on the Textfile and create a itemscontrols to bind with view and mentioned all the possibile field properties in the Viewmodel. I am thinking about implementing this part with inside the TabControl,

<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=Fields">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding}" />
                <TextBox Width="300" Text="{Binding}" />
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Can anyone please suggest me a way to do this with MVVM wpf?

1 Answers

I implemented this using the CommunityToolkit.Mvvm NuGet package.

MainWindowViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;

namespace WpfApp1;

public class Field
{
    public string Block { get; set; } = string.Empty;
    public string Text { get; set; } = string.Empty;
}

public partial class MainWindowViewModel : ObservableObject
{
    [ObservableProperty]
    // CommunityToolkit's source generator will create a "Field" property.
    private ObservableCollection<Field> fields = new();

    [RelayCommand]
    // CommunityToolkit's source generator will create a "LoadFieldsCommand" command.
    private void LoadFields()
    {
        for (int i = 0; i < 10; i++)
        {
            Fields.Add(new Field() { Block = $"Block#{i + 1}", Text = $"Text{i + 1}" });
        }
    }
}

MainWindow.xaml.cs

using System.Windows;

namespace WpfApp1;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    public MainWindowViewModel ViewModel { get; } = new();
}

MainWindow.xaml

<Window
    x:Class="WpfApp1.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:WpfApp1"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    x:Name="ThisWindow"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Button
            Grid.Row="0"
            Command="{Binding ElementName=ThisWindow, Path=ViewModel.LoadFieldsCommand}"
            Content="Load fields" />

        <ItemsControl Grid.Row="1" ItemsSource="{Binding ElementName=ThisWindow, Path=ViewModel.Fields}">
            <ItemsControl.ItemTemplate>
                <DataTemplate DataType="local:Field">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Block}" />
                        <TextBox Text="{Binding Text}" />
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>
Related