Home > Back-end >  C# WPF - Creating JSON while looping with while and for loops
C# WPF - Creating JSON while looping with while and for loops

Time:01-25

I'm stuck on creating the right order in my while and for loop for the right JSON outcome.

In my XAML I got 3 Canvasses (FirstInsuranceGroup, SecondInsuranceGroup, ThirdInsuranceGroup) inside each canvas I got a combobox where you can choose a insurance ( #1, #2, etc)

below each combobox there are a few textboxes for setting the insurance policy and price.

        <Canvas x:Name="FirstInsuranceGroup" Margin="10,95,0,0" Background="#FFFBFBFB" HorizontalAlignment="Left" Width="507" Height="486" VerticalAlignment="Top">
        <ComboBox x:Name="Verzekering_1" Canvas.Left="110" Canvas.Top="9" HorizontalAlignment="Left" VerticalAlignment="Center" Width="355" FontFamily="Arial" FontSize="16">
            <ComboBoxItem Content="#1"/>
            <ComboBoxItem Content="#2"/>
            <ComboBoxItem Content="#3"/>
            <ComboBoxItem Content="#4"/>
            <ComboBoxItem Content="#5"/>
        </ComboBox>
        <Label Content="Verzekering" Width="100" HorizontalAlignment="Center" Canvas.Left="10" Canvas.Top="5" VerticalAlignment="Center" FontFamily="Arial" FontSize="16"/>
        <Button x:Name="EersteExtraPolisButton" Content="Extra Polis Toevoegen" HorizontalAlignment="Left" VerticalAlignment="Center" Click="EersteVerzkeringAddPolis" Canvas.Left="332" Canvas.Top="456" Width="165" FontFamily="Arial" FontSize="16"/>
        <StackPanel Orientation="Horizontal" Canvas.Left="2" Canvas.Top="36">
            <StackPanel x:Name="EersteVerzekeringGroup" Orientation="Vertical" Grid.Column="0" Width="497">
                <StackPanel x:Name="StackPanel_PolisNr1_1" Orientation="Horizontal" Width="486">
                    <Label Margin="3" Height="31" VerticalAlignment="Top" Content="Polis:" FontFamily="Arial" FontSize="16"/>
                    <TextBox Height="26" x:Name="PolisNr1_1" Width="195" Margin="10,5" FontFamily="Arial" FontSize="16"/>
                    <Label Margin="3" Height="31" VerticalAlignment="Top" Content="Bedrag:" FontFamily="Arial" FontSize="16"/>
                    <TextBox Height="26" x:Name="Bedrag1_1" Width="103" Margin="10,5" FontFamily="Arial" FontSize="16"/>
                </StackPanel>
                <StackPanel x:Name="StackPanel_PolisNr1_2" Orientation="Horizontal" Width="486">
                    <Label Margin="3" Height="31" VerticalAlignment="Top" Content="Polis:" FontFamily="Arial" FontSize="16"/>
                    <TextBox Height="26" x:Name="PolisNr1_2" Width="195" Margin="10,5" FontFamily="Arial" FontSize="16"/>
                    <Label Margin="3" Height="31" VerticalAlignment="Top" Content="Bedrag:" FontFamily="Arial" FontSize="16"/>
                    <TextBox Height="26" x:Name="Bedrag1_2" Width="103" Margin="10,5" FontFamily="Arial" FontSize="16"/>
                </StackPanel>
                <StackPanel x:Name="StackPanel_PolisNr1_3" Orientation="Horizontal" Width="486">
                    <Label Margin="3" Height="31" VerticalAlignment="Top" Content="Polis:" FontFamily="Arial" FontSize="16"/>
                    <TextBox Height="26" x:Name="PolisNr1_3" Width="195" Margin="10,5" FontFamily="Arial" FontSize="16"/>
                    <Label Margin="3" Height="31" VerticalAlignment="Top" Content="Bedrag:" FontFamily="Arial" FontSize="16"/>
                    <TextBox Height="26" x:Name="Bedrag1_3" Width="103" Margin="10,5" FontFamily="Arial" FontSize="16"/>
                </StackPanel>
                <StackPanel x:Name="StackPanel_PolisNr1_4" Orientation="Horizontal" Width="486">
                    <Label Margin="3" Height="31" VerticalAlignment="Top" Content="Polis:" FontFamily="Arial" FontSize="16"/>
                    <TextBox Height="26" x:Name="PolisNr1_4" Width="195" Margin="10,5" FontFamily="Arial" FontSize="16"/>
                    <Label Margin="3" Height="31" VerticalAlignment="Top" Content="Bedrag:" FontFamily="Arial" FontSize="16"/>
                    <TextBox Height="26" x:Name="Bedrag1_4" Width="103" Margin="10,5" FontFamily="Arial" FontSize="16"/>
                </StackPanel>
                <StackPanel x:Name="StackPanel_PolisNr1_5" Orientation="Horizontal" Width="486">
                    <Label Margin="3" Height="31" VerticalAlignment="Top" Content="Polis:" FontFamily="Arial" FontSize="16"/>
                    <TextBox Height="26" x:Name="PolisNr1_5" Width="195" Margin="10,5" FontFamily="Arial" FontSize="16"/>
                    <Label Margin="3" Height="31" VerticalAlignment="Top" Content="Bedrag:" FontFamily="Arial" FontSize="16"/>
                    <TextBox Height="26" x:Name="Bedrag1_5" Width="103" Margin="10,5" FontFamily="Arial" FontSize="16"/>
                </StackPanel>
            </StackPanel>
        </StackPanel>
    </Canvas>

Now in the code i'm trying to get the values of each insurance followd by the insurance policy and price and putting it inside a json string

        public class PolisVerzekering
    {
        public string VerzekeringName { get; set; }

        public List<Polis> PolisInfoList { get; set; }
    }
    public class Polis
    {
        public string PolisNr { get; set; }
        public string PolisBedrag { get; set; }
    }

    private void SaveVerzekeringPolis(object sender, RoutedEventArgs e)
    {
        int v = 0;
        var PolisLijst = new List<Polis>();
        List<PolisVerzekering> verzekeringList = new List<PolisVerzekering>();

        while(v < 5)
        {

            v  ;
            var dynVerzekering = FindChildElement.FindChild<ComboBox>(Application.Current.MainWindow, "Verzekering_"   v);

            if (dynVerzekering != null && dynVerzekering.Text != "")
            {
                MessageBox.Show(dynVerzekering.Name);
                var verzekeringChoice = new PolisVerzekering { VerzekeringName = dynVerzekering.Text, PolisInfoList = PolisLijst };
                verzekeringList.Add(verzekeringChoice);
            }

            for (int p = 1; p < 10; p  )
            {
                var DynPolisNr = FindChildElement.FindChild<TextBox>(Application.Current.MainWindow, "PolisNr"   v   "_"   p);
                var DynPolisBedrag = FindChildElement.FindChild<TextBox>(Application.Current.MainWindow, "Bedrag"   v   "_"   p);
                 
                if ((DynPolisNr != null && DynPolisBedrag != null) && (DynPolisNr.Text != "" && DynPolisBedrag.Text != ""))
                {
                    var dynPolis = new Polis { PolisNr = DynPolisNr.Text, PolisBedrag = DynPolisBedrag.Text };
                    PolisLijst.Add(dynPolis);
                }
            }
        }
        var jsonString = JsonConvert.SerializeObject(verzekeringList);

        File.WriteAllText(@"C:\Users\Patrick\Documents\verzekering.json", JsonConvert.SerializeObject(verzekeringList));
    }

The result i'm expecting would be this;

[
{
    "VerzekeringName": "#1",
    "PolisInfoList": [
        {
            "PolisBedrag": "123",
            "PolisNr": "123"
        }
    ]
},
{
    "VerzekeringName": "#2",
    "PolisInfoList": [
        {
            "PolisBedrag": "321",
            "PolisNr": "321"
        }
    ]
}
]

But instead i'm getting this;

[
{
    "PolisInfoList": [
        {
            "PolisBedrag": "123",
            "PolisNr": "123"
        },
        {
            "PolisBedrag": "321",
            "PolisNr": "321"
        }
    ],
    "VerzekeringName": "#1"
},
{
    "PolisInfoList": [
        {
            "PolisBedrag": "123",
            "PolisNr": "123"
        },
        {
            "PolisBedrag": "321",
            "PolisNr": "321"
        }
    ],
    "VerzekeringName": "#2"
}
]

Visual input: Input

Any help would greatly be appriciated, i'm really stuck..

Kind regards, Patrick

CodePudding user response:

You obviously add every TextBox to each list.
You always loop 10 times (inner loop) to find all 10 TextBoxes. But you have to iterate 5 times per column of your form (first iteration TextBox elements 1-5, second iteration 6-10). Your solution is not dynamic. That's why you struggle. Writing code this way code doesn't feel natural to anybody.
Your implementation simply doesn't scale. For example if you add a TextBox pair you have to touch each loop to increment the loop counter. Adding a TextBox pair this way at runtime is not possible.

You have to use data binding (Data binding overview (WPF .NET)).
Data binding will simplify your code. It eliminates the explicit access to the UI elements and therefore eliminates the nested loops. Data binding requires you to design your data structures properly, which in turn simplifies serialization.

Also use the asynchronous File.WriteAllTextAsync method to improve the performance (see example below).
.NET already provides a JSON library. In your case there is no need to use a 3rd party library. The .NET library also supports asynchronous serialization which your chosen library doesn't.
How to serialize and deserialize (marshal and unmarshal) JSON in .NET

The example below is held simple, and replicates your view quite raw. It also shows how to dynamically add new rows of TextBox pairs using an ICommand. You can extend this example to improve your input form.

InsuranceType.cs
This class holds the values for the ComboBox.
How to: Implement INotifyPropertyChanged

// TODO::Implement INotifyPropertyChanged
class InsuranceType : INotifyPropertyChanged
{
  public string Name { get; }

  public Insurance(string name) => this.Name = name;
}

Entry.cs
This class represents each pair of TextBox elements.
How to: Implement INotifyPropertyChanged

// TODO::Implement INotifyPropertyChanged
class Entry : INotifyPropertyChanged
{
  // TODO::Raise PropertyChanged
  public int PolisNr { get; set; }

  // TODO::Raise PropertyChanged
  public decimal PolisBedrag { get; set; }
}

Insurance.cs
This class makes a single form containing a ComboBox and a set of TextBox pairs. It's the main data model.
How to: Implement INotifyPropertyChanged.
This example uses a RelayCommand to execute a Button action.
You can find a very simple implementation of the RelayCommand at Microsoft Docs: Relaying Command Logic. Copy&Paste it to your project.

// TODO::Implement INotifyPropertyChanged
class Insurance : INotifyPropertyChanged
{
  public InsuranceGroup()
  {
    this.Insurances = new ObservableBatchCollection<Insurance>
    {
      new InsuranceType("#1"),
      new InsuranceType("#2"),
    };

    this.Entries = new ObservableCollection<Entry>();
    for (int i = 0; i < 5; i  )
    {
      this.Entries.Add(new Entry());
    }
      
    this.AddNewEntryCommand = new RelayCommand(commandParameter => this.Entries.Add(new Entry()));
  }

  public ObservableBatchCollection<InsuranceType> InsuranceTypes { get; }
  public ObservableCollection<Entry> Entries { get; }
  public RelayCommand AddNewEntryCommand { get; }
}

InsuranceCollection.cs
This class contains a set of Insurance forms.
A horizontal ListBox can be used to show them in horizontal columns.

class InsuranceCollection : ObservableCollection<Insurance>
{ }

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public InsuranceCollection Insurances  { get; }

  public MainWindow()
  {
    InitializeComponent();

    this.DataContext = this;
    this.Insurances  = new InsuranceGroupCollection
    {
      new Insurance(),
      new Insurance(),
    };
  }

  // Serialization has become that simple.
  // Using the async 'File.WriteAllTextAsync' method also improves the performance
  // and prevents your UI from freezing during the serialization
  private async void SaveVerzekeringPolis(object sender, RoutedEventArgs e)
  {
    // Recommended: use the async .NET JSON serializer. 
    // Because the serializer can write directly to a file, 
    // the 'File.WriteAllTextAsync' is no longer neeeded
    await using FileStream destinationFileStream = File.Create(@"C:\Users\Patrick\Documents\verzekering.json");
    await JsonSerializer.SerializeAsync(destinationFileStream, this.Insurances);

    // Alternative and not recommended use of your current 3rd party library
    var jsonString = JsonConvert.SerializeObject(this.Insurances);
    await File.WriteAllTextAsync(@"C:\Users\Patrick\Documents\verzekering.json", JsonConvert.SerializeObject(verzekeringList));
  }
}

MainWindow.xaml
A highly dynamic view that is realized using ItemsControl derived types and templating (Data Templating Overview).

<Window>
  <!-- Display a set of forms horizontally -->
  <ListBox ItemsSource="{Binding Insurances}">
    <ListBox.ItemsPanel>
      <ItemsPanelTemplate>
        <VirtualizingStackPanel Orientation="Horizontal" />
      </ItemsPanelTemplate>
    </ListBox.ItemsPanel>

    <ListBox.ItemTemplate>
      <DataTemplate DataType="{x:Type local:Insurance}">
        <StackPanel>
          <ComboBox ItemsSource="{Binding InsuranceTypes}"
                    DisplayMemberPath="Name" />
              
          <!-- TextBox pairs. Each item makes a line of two TextBox elements -->
          <ListBox ItemsSource="{Binding Entries}">
            <ListBox.ItemTemplate>
              <DataTemplate DataType="{x:Type local:Entry}">
                <StackPanel Orientation="Horizontal">
                  <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Polis:" />
                    <TextBox Text="{Binding PolisNr}" />
                  </StackPanel>
                  <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Bedrag:" />
                    <TextBox Text="{Binding PolisBedrag}" />
                  </StackPanel>
                </StackPanel>
              </DataTemplate>
            </ListBox.ItemTemplate>
          </ListBox>

          <!-- Add a new TextBox pair -->
          <Button Content="Add Entry"
                  Command="{Binding AddNewEntryCommand}" />

          <Button Content="Extra Polis Toevoegen" />
        </StackPanel>
      </DataTemplate>
    </ListBox.ItemTemplate>
  </ListBox>
</Window>
  • Related