Mik AA
Mik AA

Reputation: 37

How To Add Column field from database to String Builder c# and implementing Two Column layout

I'm trying to make a layout design on string builder for a receipt but it's not doing it also i tried to place column fields taken from database table to append on the string builder but it's not also printing it. can any one please show the proper way of this? how it is done?

StringBuilder sb = new StringBuilder();
    
sb.Append("<b>--- DINE IN --- </b>\n\n");
        
// Revert to align left and normal weight
sb.Append(ESC + "a" + (char)0);
sb.Append(ESC + "E" + (char)0);
        
//this are from the database table columns
printJobQuery.CheckNumber.ToString();
printJobQuery.CheckStartDate.ToString();
printJobQuery.CheckStartDate.ToString(); // convert to time
        
foreach (var OrderLst in printJobLineQuery)
{
    OrderLst.Quantity.ToString();
    OrderLst.ItemName.ToString();
    OrderLst.ActualPrice.ToString();
}
        
printJobQuery.TableDescription.ToString();
printJobQuery.CustomerNumber.ToString();
printJobQuery.EmpId.ToString();
// end here database column
        
sb.Append("Check: @printJobQuery.CheckStartDate.ToString()");
        
// Format some text
sb.Append("This is it's header text of order receipt\n");
sb.AppendFormat("This is the Body Content of Header Receipt\n\n", host);
        
sb.Append("Can Add More Content Here,\n\n");
        
//Footer
sb.Append("Footer Part of the Receipt can " +
          "\nAdd Time and Date of Order \n\n");
        
// Feed and cut paper
sb.Append(GS + "V\x41\0");
        
return Encoding.Default.GetBytes(sb.ToString());

layout should be like

1 Milk :: 25.00 1 Sandwich :: 30.00

Upvotes: 1

Views: 867

Answers (1)

sbridewell
sbridewell

Reputation: 1140

In this answer I'm assuming that the paper you want to print the receipt on is of a fixed width, and so is limited to a maximum number of characters per line, in order to prevent your 2 columns of item information wrapping in an ugly way. I'm also assuming that your printer is using a monospace font, otherwise all the calculations of how many characters can fit on a line go out of the window.

This class should help you to get started in formatting some data from a list so that the output shows the contents of the list in 2 columns.

namespace StackOverflow69117445TwoColumns
{
    using System.Text;

    public class Receipt
    {
        private readonly PrintJobQuery query;
        private readonly int columnWidth;

        public Receipt(PrintJobQuery query, int columnWidth)
        {
            this.query = query;
            this.columnWidth = columnWidth;
        }

        public string FormatForPrinting()
        {
            var sb = new StringBuilder();
            sb.AppendLine(query.CheckNumber.ToString());
            sb.AppendLine(query.CheckStartDate.ToString());

            for (var i = 0; i < query.OrderLst.Count; i += 2)
            {
                var item1 = this.FormatItem(query.OrderLst[i]);
                if (i + 1 < query.OrderLst.Count)
                {
                    // then we've got 2 items to display on this line
                    var item2 = this.FormatItem(query.OrderLst[i + 1]);
                    sb.AppendLine($"{item1}   {item2}");
                }
                else
                {
                    // then we've got only 1 item to display so pad the
                    // remainder of the line with spaces
                    sb.Append(item1);
                    sb.AppendLine(new string(' ', this.columnWidth + 3));
                }
            }

            sb.AppendLine(query.TableDescription);
            sb.AppendLine(query.CustomerNumber.ToString());
            sb.AppendLine(query.EmpId.ToString());

            return sb.ToString();
        }

        private string FormatItem(OrderItem item)
        {
            var quantity = item.Quantity.ToString();
            var name = item.ItemName;
            var price = item.ActualPrice.ToString("#0.00");

            // If the format is <quantity> <item name> :: <price>
            // then the total string length without any truncation will be
            // the total length of the 3 data items plus 5 (3 spaces and 2
            // colons)
            var stringLengthWithoutTruncation = quantity.Length + name.Length + price.Length + 5;

            // Either truncate parts of the line or pad with spaces to make it the right length
            // for the column width.
            var overflow = stringLengthWithoutTruncation - this.columnWidth;
            var desiredNameLength = name.Length - overflow;
            if (overflow > 0)
            {
                // The line is wider than the column, so we need to truncate the name
                name = name.Substring(0, desiredNameLength) + " ::";
            }

            if (overflow < 0)
            {
                // The string is narrower than the column, so pad the item name with spaces
                name = name.PadRight(desiredNameLength) + " ::";
            }

            return $"{quantity} {name} {price}";
        }
    }
}

So you create an instance of Receipt, supplying the PrintJobQuery instance containing the data you want to format into a receipt and the length of each of the 2 columns in characters. The FormatForPrinting method will then return a string containing a formatted version of that data which you can send to the printer.

If the quantity, name and price of any of the items, when formatted, is too long to fit into the desired column width, then the name is truncated to make it fit. If the quantity, name and price of any of the items, when formatted, is shorter than the desired column width, then the name is right-padded with spaces.

Please don't ask me how to send that string to the printer, I have no experience of programatically interacting with peripheral devices such as printers so I'm just concentrating on how to implement the 2 column layout.

A couple of things to note about how this implementation differs from your initial code...

  • Everything which needs to be printed is passed to one of the StringBuilder's Append... methods - anything which you don't pass to the StringBuilder won't be included in the output
  • StringBuilder has two commonly used methods - Append and AppendLine. Append just adds characters to the end of the string, whereas AppendLine appends the characters followed by whatever sequence of characters represents a line break in the user's operating system. Given that you're sending the output to a printer, whether or not the use of AppendLine is appropriate will depend very much on the make and model of your printer and what it considers to be a line break, and again, I have no experience in that area.

I've had to make some assumptions about the model classes you're using to hold the data, here are the classes I've used, but it should be simple enough to adapt the Receipt class to match your actual model classes.

First, PrintJobQuery, which holds all the data to go on the receipt:

namespace StackOverflow69117445TwoColumns
{
    using System;
    using System.Collections.Generic;

    public class PrintJobQuery
    {
        public int CheckNumber { get; set; }
        public DateTime CheckStartDate { get; set; }
        public string TableDescription { get; set; }
        public int CustomerNumber { get; set; }
        public int EmpId { get; set; }
        public List<OrderItem> OrderLst { get; } = new List<OrderItem>();
    }
}

And OrderItem, which holds the details of a single item to be displayed in the 2 columns of items detailed on the receipt:

namespace StackOverflow69117445TwoColumns
{
    public class OrderItem
    {
        public int Quantity { get; set; }
        public string ItemName { get; set; }
        public decimal ActualPrice { get; set; }
    }
}

I've tested this too - here are my unit tests:

namespace StackOverflow69117445TwoColumns
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using Xunit;

    public class UnitTest1
    {
        [Theory]
        [InlineData(50, 103)]
        [InlineData(40, 83)]
        [InlineData(30, 63)]
        //[InlineData(5, 63)]
        public void FourItemsTest(int columnWidth, int receiptWidth)
        {
            // Arrange
            var query = new PrintJobQuery
            {
                CheckNumber = 123456,
                CheckStartDate = DateTime.Now,
                CustomerNumber = 123,
                EmpId = 3,
                TableDescription = "Rectangular with a nice tablecloth",
            };
            query.OrderLst.AddRange(TestData.FourItems);
            var receipt = new Receipt(query, columnWidth);

            // Act
            var output = receipt.FormatForPrinting();

            // Assert
            Debug.WriteLine($"FourItemsTest with columnWidth {columnWidth} and expected receiptWidth {receiptWidth}");
            Debug.WriteLine(output);
            var lines = output.Split(Environment.NewLine);
            Assert.Equal(receiptWidth, lines[2].Length);
            Assert.Equal(receiptWidth, lines[3].Length);
        }

        [Theory]
        [InlineData(50, 103)]
        [InlineData(40, 83)]
        [InlineData(30, 63)]
        public void FiveItemsTest(int columnWidth, int receiptWidth)
        {
            // Arrange
            var query = new PrintJobQuery
            {
                CheckNumber = 123456,
                CheckStartDate = DateTime.Now,
                CustomerNumber = 123,
                EmpId = 3,
                TableDescription = "Rectangular with a nice tablecloth",
            };
            query.OrderLst.AddRange(TestData.FiveItems);
            var receipt = new Receipt(query, columnWidth);

            // Act
            var output = receipt.FormatForPrinting();

            // Assert
            Debug.WriteLine($"FiveItemsTest with columnWidth {columnWidth} and expected receiptWidth {receiptWidth}");
            Debug.WriteLine(output);
            var lines = output.Split(Environment.NewLine);
            Assert.Equal(receiptWidth, lines[2].Length);
            Assert.Equal(receiptWidth, lines[3].Length);
            Assert.Equal(receiptWidth, lines[4].Length);
        }
    }
}

If you're not familiar with xunit or its [Theory] attribute, read about it here.

And here's the class which supplies the test data:

namespace StackOverflow69117445TwoColumns
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Text;

    public class TestData
    {
        public static List<OrderItem> FourItems
        {
            get
            {
                var list = new List<OrderItem>();
                list.Add(new OrderItem { Quantity = 1, ItemName = "Penny chew", ActualPrice = 0.01M });
                list.Add(new OrderItem { Quantity = 1, ItemName = "Something expensive", ActualPrice = 350000.00M });
                list.Add(new OrderItem { Quantity = 2, ItemName = "Something with a really really really long name", ActualPrice = 25.00M });
                list.Add(new OrderItem { Quantity = 300, ItemName = "Lots of something", ActualPrice = 500.00M });
                return list;
            }
        }

        public static List<OrderItem> FiveItems
        {
            get
            {
                var list = new List<OrderItem>();
                list.Add(new OrderItem { Quantity = 1, ItemName = "Penny chew", ActualPrice = 0.01M });
                list.Add(new OrderItem { Quantity = 1, ItemName = "Something expensive", ActualPrice = 350000.00M });
                list.Add(new OrderItem { Quantity = 2, ItemName = "Something with a really really really long name", ActualPrice = 25.00M });
                list.Add(new OrderItem { Quantity = 300, ItemName = "Lots of something", ActualPrice = 500.00M });
                list.Add(new OrderItem { Quantity = 2, ItemName = "Wine", ActualPrice = 5.00M });
                return list;
            }
        }
    }
}

This is what the tests write to the debug console, so that you can see what the formatted output looks like:

FourItemsTest with columnWidth 50 and expected receiptWidth 103
123456
15/09/2021 20:18:29
1 Penny chew                               :: 0.01   1 Something expensive                 :: 350000.00
2 Something with a really really really l :: 25.00   300 Lots of something                    :: 500.00
Rectangular with a nice tablecloth
123
3

FourItemsTest with columnWidth 30 and expected receiptWidth 63
123456
15/09/2021 20:18:29
1 Penny chew           :: 0.01   1 Something expen :: 350000.00
2 Something with a re :: 25.00   300 Lots of somethin :: 500.00
Rectangular with a nice tablecloth
123
3

FourItemsTest with columnWidth 40 and expected receiptWidth 83
123456
15/09/2021 20:18:29
1 Penny chew                     :: 0.01   1 Something expensive       :: 350000.00
2 Something with a really reall :: 25.00   300 Lots of something          :: 500.00
Rectangular with a nice tablecloth
123
3

FiveItemsTest with columnWidth 50 and expected receiptWidth 103
123456
15/09/2021 20:18:29
1 Penny chew                               :: 0.01   1 Something expensive                 :: 350000.00
2 Something with a really really really l :: 25.00   300 Lots of something                    :: 500.00
2 Wine                                     :: 5.00                                                     
Rectangular with a nice tablecloth
123
3

FiveItemsTest with columnWidth 40 and expected receiptWidth 83
123456
15/09/2021 20:18:29
1 Penny chew                     :: 0.01   1 Something expensive       :: 350000.00
2 Something with a really reall :: 25.00   300 Lots of something          :: 500.00
2 Wine                           :: 5.00                                           
Rectangular with a nice tablecloth
123
3

FiveItemsTest with columnWidth 30 and expected receiptWidth 63
123456
15/09/2021 20:18:29
1 Penny chew           :: 0.01   1 Something expen :: 350000.00
2 Something with a re :: 25.00   300 Lots of somethin :: 500.00
2 Wine                 :: 5.00                                 
Rectangular with a nice tablecloth
123
3

It should be noted that this code is not production-ready, it's just an example which you can use as guidance to implement your own code.

In particular, there is one error condition which I haven't handled - what if the string representations of the quantity and price of an item, plus the spacing characters, is longer than the desired column width? In this scenario, my code will attempt to truncate the name of the item to a string with a negative length, which results in an unhandled exception

  Message: 
System.ArgumentOutOfRangeException : Length cannot be less than zero. (Parameter 'length')

  Stack Trace: 
String.Substring(Int32 startIndex, Int32 length)
Receipt.FormatItem(OrderItem item) line 66
Receipt.FormatForPrinting() line 24
UnitTest1.FourItemsTest(Int32 columnWidth, Int32 receiptWidth) line 30

To see this in action, uncomment the [InlineData(5, 63)] attribute on the FourItemsTest method.

I'm not going to try to second-guess how best to handle this situation in your case. You just need to make sure that your desired column width is wide enough to hold the information it needs to contain.

Hope this is useful :-)

Upvotes: 1

Related Questions