ListBox Controls in Winform - c#

I would like to perform 2 different actions. I have a Form where CSV File Containing two Column(Data and Image) will be read and and displayed from a path. I've used the CSVReader but I'm not sure how to implement the picturebox and listbox display portion.
For the listbox, I created a list(record). all the properties are in this List, But when I go to display it, it shows just the name of the Class as a list for all of them. I would like the name to be the Value of Data(For Example with the image referenced it would be StackIndex=1of10
Here is my Code for Displaying the List:
public Viewer()
{
InitializeComponent();
_path = #"\\ikscp0207\Images\Debug\Debug.csv";
List<Elements> records; //each row of csv will represent one object in the elements class thats generated. Here we need a list
using (var reader = new StreamReader(_path)) //Standard Stream reader for File Reading Setup. ReadFile is Hard Coded in this Iteration.
using(var csv = new CsvReader(reader, System.Globalization.CultureInfo.InvariantCulture)) //Takes the reader and generates new csv reader,
//which is used by csvhelper to get all the records
//in list of elements and converting it into a list.
{
records = csv.GetRecords<Elements>().ToList();
}
ListControl.DataSource = records;
}
Here is what the listbox looks like
the next thing is that I would like to be able to navigate up and down the listbox and display the appropriate image column data in a textbox. This is a sample of the CSV File I have put in a button but have not been successful in doing so.
private void PushNext_Click(object sender, EventArgs e)
{
int next = Convert.ToInt32(ListControl.SelectedIndex) + 1;
this.ListControl.Focus();
this.ListControl.Items[next] = true;
I'm able to increment it, but I'm getting an exception where it shows "Items collection cannot be modified when the DataSource property is set.

Related

I want to export single row on button click from the gridview in c# winform

I am able to export complete gridview to pdf but I can't understand how to target the specific row and export it to pdf using itextsharp when button clicked
Here is my code below for exporting to pdf where I am able to export complete gridview
private void gvSamplereports_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == gvSamplereports.Columns["btnPDFsingle"].Index)
{
DateTime PrintTime = DateTime.Now;
if (gvSamplereports.Rows.Count > 0)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "PDF (*.pdf)|*.pdf";
sfd.FileName = "SampleDataReports_" + PrintTime.ToShortDateString() + ".pdf";
bool fileError = false;
if (sfd.ShowDialog() == DialogResult.OK)
{
if (File.Exists(sfd.FileName))
{
try
{
File.Delete(sfd.FileName);
}
catch (IOException ex)
{
fileError = true;
MessageBox.Show("It wasn't possible to write the data to the disk." + ex.Message);
}
}
if (!fileError)
{
try
{
PdfPTable pdfTable = new PdfPTable(gvSamplereports.Columns.Count);
pdfTable.DefaultCell.Padding = 3;
pdfTable.WidthPercentage = 100;
pdfTable.HorizontalAlignment = Element.ALIGN_CENTER;
//Below line is to add the header column name on each page of pdf
pdfTable.HeaderRows = 1;
foreach (DataGridViewColumn column in gvSamplereports.Columns)
{
Font fon = FontFactory.GetFont("ARIAL", 6);
fon.SetStyle(1);
PdfPCell cell = new PdfPCell(new Phrase(column.HeaderText, fon));
cell.HorizontalAlignment = Element.ALIGN_CENTER;
pdfTable.AddCell(cell);
}
foreach (DataGridViewRow row in gvSamplereports.Rows)
{
foreach (DataGridViewCell cell in row.Cells)
{
Font fon = FontFactory.GetFont("ARIAL", 6);
PdfPCell cell2 = new PdfPCell(new Phrase(cell.Value?.ToString(), fon));
cell2.HorizontalAlignment = Element.ALIGN_CENTER;
pdfTable.AddCell(cell2);
//pdfTable.AddCell(cell.Value.ToString());
}
}
using (FileStream stream = new FileStream(sfd.FileName, FileMode.Create))
{
Document pdfDoc = new Document(PageSize.A4, 30f, 30f, 100f, 50f);
PdfWriter writer = PdfWriter.GetInstance(pdfDoc, stream);
//PDFFooter is class created for adding header and footer in the pdf
writer.PageEvent = new PDFFooter();
pdfDoc.Open();
pdfDoc.Add(pdfTable);
pdfDoc.Close();
stream.Close();
}
MessageBox.Show("Data Exported Successfully !!!", "Info");
}
catch (Exception ex)
{
MessageBox.Show("Error :" + ex.Message);
}
}
}
}
else
{
MessageBox.Show("No Record To Export !!!", "Info");
}
}
}
I have added image for reference, Once I click the button I want to export that single row with header columns name in pdf using Itextsharp in c# winform, the data exported in pdf should look like Image below
Separate the data from how you display it
In modern programming there is a tendency to separate the data (=model) from the way it is communicated to the operator (=view). This has the advantage that you can reuse the model if you decide to display it differently, for instance, if you want to show the data as a Graph, instead of a table.
To match the model with the view, an adapter class is needed. This adapter class is usually called the Viewmodel. Together the three classes are abbreviated MVVM. Consider to read some background information about this.
When using Winforms and DataGridView, people tend to fiddle directly with the Rows and the Cells, instead of separating the data from the way it is displayed. Quite often this leads to a lot of problems. Apart from that you can't unit test the data without the form, you can't reuse the data in other forms, nor change the data without having to change the DataGridView.
Winforms supports MVVM using property DataGridView.DataSource.
How to easily and efficiently access the data of your DataGridView?
Alas, you forgot to tell us what's in your DataGridView, and from your code I can't extract what is shown. So for the example, let's suppose you show several properties of a collection of Products:
class Product
{
public int Id {get; set;}
public string ProductCode {get; set;}
public string Description {get; set;}
public ProductType ProductType {get; set;} // some enumeration: food / non-food / etc
public decimal Price {get; set;}
public int LocationId {get; set;} // foreign key to Location table
...
}
You probably don't want to show all properties.
So of course you have a procedure to fetch the products that you want to show initially:
IEnumerable<Product> FetchProductsToShow() {...}
Implementation is out of scope of the question.
Using Visual Studio Designer you have added a DataGridView, and one DataGridViewColumn per Product property that you want to show. You'll have to define which DataGridViewColumn will show the values of which property. This can be done using the designer. I usually do it in the constructor with the use of nameof.
public MyForm : Form
{
InitializeComponents();
// Define which column shows which Property:
coilumnProductId.DataPropertyName = nameof(Product.Id);
columnProductCode.DataPropertyName = nameof(Product.ProductCode);
columnProductPrice.DataPropertyName = nameof(Product.Price);
...
The advantage of using nameof, is that if later you decide to change the name of the properties, it is automatically changed here. Typing errors are detected by the compiler.
Now to show all Products, all you have to do is assign the ProductsToDisplay to the `dataGridView1.DataSource:
this.dataGridView1.DataSource = this.FetchProductsToShow().ToList();
And presto your data is shown.
However, if the operator edits the table, the data is not updated. If you want that, you'll have to put the products that must be shown into an object that implements IBindingList. Luckily there is already such a class, not surprisingly named BindingList<T>
Add the following property to your form:
public BindingList<Product> DisplayedProducts
{
get => (BindingList<Product>)this.dataGridView1.DataSource;
set => this.dataGridView1.DataSource = value;
}
Now all changes made by the operator are automatically updated in the BindingList: changes to cells, but also added and deleted rows.
private void ShowInitialProducts()
{
this.DisplayedProducts = new BindingList<Product>(this.FetchProductsToDisplay().ToList());
}
To access the edited table, for instance after the operator pressed the OK button:
public void OnButtonOk_Clicked(object sender, ...)
{
BindingList<Product> editedProducts = this.DisplayedProducts;
// find out which products are changed, and process them:
this.ProcessEditedProducts(editedProducts);
}
Back to your question
but I can't understand how to target the specific row
BindingList<T> does not implement IList<T>. The designers didn't find it useful to access this.DisplayedProducts[4] directly. After all: if the operator can rearrange the rows, you don't know what's in the row with index [4].
However, you might want to access the Products as a sequence. Therefore ICollection<T> is implemented.
If you want to access the current row, or the selected rows, consider to add the following properties to your form:
public Product CurrentProduct => this.dataGridView1.CurrentRow?.DataBoundItem as Product;
This will return the current Product, or null if nothing is selected
public IEnumerable<Product> SelectedProducts = this.dataGridView1.SelectedRows
.Cast<DataGridViewRow>()
.Select(row => row.DataBoundItem)
.Cast<Product>();
So to access the selected Products after the operator pressed the Ok button:
public void OnButtonOk_Clicked(object sender, ...)
{
IEnumerable<Product> selectedProducts = this.SelectedProducts;
// process the selected products:
this.ProcessProducts(selectedProducts);
}
There's room for improvement
If I look at your code, it seems to me that if the operator clicks on a cell in the column with name btnPDFsingle (why not use a name that explains what the columns displays?), then you do several things:
you ask the operator to provide a file name,
if the file exists, you delete it (and solve the problem if it can't be deleted)
then you create a PdfPTable and fill it with the contents of the DataGridView
Finally you write the PdfPTable to a file.
And you decide to do that all in one procedure. This makes it difficult to unit test it. You can't reuse any part of this code, and if you change part of this, it is difficult to detect which parts of your code also has to change.
private void gvSamplereports_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == gvSamplereports.Columns["btnPDFsingle"].Index)
{
this.SaveProducts();
}
else
... // report problem to operator
}
private void SaveProducts()
{
string fileName = this.AskOperatorForFileName();
if (String.IsNullOrEmpty(fileName))
{
... // report operator that no filename is given
return;
}
// fetch the products that must be in the Pdf
IEnumerable<Product> productsToSave = this.FetchProductsToSave();
this.SaveProducts(fileName, productsToSave);
}
ICollection<Product> FetchProductsToSave()
{
// Do you want to save all Products or Only the selected ones?
return this.DisplayedProducts;
}
Note: if you decide to save something different, only the selected products, or maybe only the first 10 Products, or only the non-food products, all you have to do is change this method. The other methods don't know, and don't have to know which Products are saved.
By the way, did you notice, that until now I nowhere mentioned that the Products are saved as a PDF? If later you decide to save them as XML, CSV, or plain text, none of these procedures have to change.
private void SaveProducts(string fileName, IEnumerable<Product> productsToSave)
{
PdfPTable tableToSave = this.CreatePdfPTable(productsToSave);
this.SavePdfPTable (fileName, tableToSave);
}
private PdfPTable CreatePdfPTable(IEnumerable<Product> products)
{
...
foreach (Product product in products)
{
...
}
}
Did you see, that to create the PdfPTable, I don't have to access the DataGridViewRows or Cells anymore? If you decide to change the layout of the PdfPTable, only this procedure is changed. No method outside knows anything about the inner format of the table. Easy to unit test, easy to reuse, easy to change.
private void SavePdfPTable (string fileName, PdfPTable pdfPTable)
{
// if the file already exists, if will be overwritten (FileMode.Create)
// so no need to delete it first
using (FileStream stream = new FileStream(sfd.FileName, FileMode.Create))
{
... etc, write the PdfTable in the stream.
}
}
Did you see, that because of all the small procedures, each procedure has only one specific task. It is way easier to unit test this task, to replace this task with a similar task if you want a small change (save as XML for instance). You can reuse each of these procedure in different forms. Because you have proper unit tests, you don't have to be afraid that these procedures have unexpected behaviour.
Conclusion:
Don't make your procedures too big. Every Procedure should have one specific clear task. This is part of what is called separation of concerns. Consider to read some background information about this.

how to append data to the last row, from the list to asp.net grid view? so that my uploaded file will be shown latest

My requirement is to upload a file everytime at the bottom row of the gridview and the datalist has the new attached file. Now it is showing the uploaded files randomly in the rows of the UploadedReportGrid gridview. Please help
private void UpdateReportList()
{
ICrashReportService clientUpload = new CrashReportServiceClient();
RequestIdentifier requestIdentifier = new RequestIdentifier { RequestId = Id };
try
{
CrashReportCollection data = clientUpload.GetCrashReportsByRequestId(requestIdentifier);
List<CrashData> datalist = ConvertToCrashData(data);
UploadedReportGrid.DataSource = datalist;
UploadedReportGrid.DataBind();
SetFirstItemToButtonForReport(datalist);
}
catch (FaultException fx)
{
UploadStatusLabel.ForeColor = Color.Red;
UploadStatusLabel.Text = "Error: " + fx.Message;
}
}
I have a list(datalist) of CrashData class which has elements(properties) like file,reportid,version,categoryid,occureddate, every time I upload a file its details are stored in the datalist which is the datasource for my grid view(UploadedReportGrid), but in the grid the latest uploaded file is not shown in the last row. the grid rows which has crash report files is not shown in the uploaded order
In your situation, all you need to do is sort the data list on occurreddate before setting data source of gridview.
Use the code snippet given below, where you are setting data source of gridview. Make sure you use x.OcurredDate with proper case since class member names are case sensitive. The LINQ extension method OrderBy will order by ascending order of the property OccurredDate.
UploadedReportGrid.DataSource = datalist.OrderBy(x => x.OcurredDate).ToList();

Variable IDs in C#

When programming a Windows Forms application I found myself having to create a variable amount of text fields according to the number of inputs.
I decided to name these name1, name2, name3, ..., nameN.
Now I want to be able to save the user's input to a text file. To do this I need to get the text from the text boxes into separate variables to be able to write this to the text file.
This would result in a for loop like this:
for(i=0; i < totalnames; i++)
{
string varname= "name" + i;
}
But this way I cannot get the value from the text boxes. How can I get the separate values from the text boxes to write them to the text file?
Thank you,
When you create the form controls, keep references to them in a list:
// probably at the class level...
List<TextBox> myTextBoxes = new List<TextBox>();
// then when you create them...
myTextBoxes.Add(name1);
// etc.
Then later, when you need to reference them, use the list:
foreach (var textBox in myTextBoxes)
{
// get the value from the text box and use it in your output
}
You can create a List of string List and add your name in it. Then use StreamWriter to add the name in your file:
List<string> myListOfNames = new List<string>();
int totalnames = 10;
for (int i = 0; i < totalnames; i++)
{
myListOfNames.Add("name" + i);
}
using (StreamWriter writer = new StreamWriter("C:\\MyTextFile.txt", true))
{
foreach (string name in myListOfNames)
{
writer.WriteLine(name);
}
}
Here is my two pennies worth, because the OP originally said Windows Form Application - I would have a Save button, which when fired, the code behind will grab all the Textboxes and save to the file. You can add your own filtering for textboxes yourself if you want.
Firstly here is the code behind for the button event:
private void saveToFile_Click(object sender, EventArgs e)
{
using (StreamWriter writer = new StreamWriter("C:\\k\\saveToFile.txt", true))
{
if (this.Controls.Count > 0)
{
var textBoxes = this.Controls.OfType<TextBox>();
foreach (TextBox textbox in textBoxes)
{
writer.WriteLine(textbox.Name + "=" + textbox.Text);
}
}
}
}
A simple forn to prove the point, each TextBox has a name of name1 etc.
Also here is an example output of the file:
Improvements
Filtering on the Textboxes - you may only want to do this for some textboxes for a particular name.
Loading the file. I have added the textbox name to the file, so theorically you could load the data back into the textboxes.

C# Save all items in a ListBox to text file

Recently I've been quite enjoying C# and I'm just testing with it but there seems to be one part I don't get.
Basically I want it so that when I click the SAVE button must save all the items in the listbox to a text file. At the moment all it comes up with in the file is System.Windows.Forms.ListBox+ObjectCollection.
Here's what I've got so far. With the SaveFile.WriteLine(listBox1.Items); part I've tried putting many different methods in and I can't seem to figure it out. Also take in mind that in the end product of my program I would like it to read back to that to that text file and output what's in the text file to the listbox, if this isn't possible then my bad, I am new to C# after all ;)
private void btn_Save_Click(object sender, EventArgs e)
{
const string sPath = "save.txt";
System.IO.StreamWriter SaveFile = new System.IO.StreamWriter(sPath);
SaveFile.WriteLine(listBox1.Items);
SaveFile.ToString();
SaveFile.Close();
MessageBox.Show("Programs saved!");
}
From your code
SaveFile.WriteLine(listBox1.Items);
your program actually does this:
SaveFile.WriteLine(listBox1.Items.ToString());
The .ToString() method of the Items collection returns the type name of the collection (System.Windows.Forms.ListBox+ObjectCollection) as this is the default .ToString() behavior if the method is not overridden.
In order to save the data in a meaningful way, you need to loop trough each item and write it the way you need. Here is an example code, I am assuming your items have the appropriate .ToString() implementation:
System.IO.StreamWriter SaveFile = new System.IO.StreamWriter(sPath);
foreach(var item in listBox1.Items)
{
SaveFile.WriteLine(item.ToString());
}
Items is a collection, you should iterate through all your items to save them
private void btn_Save_Click(object sender, EventArgs e)
{
const string sPath = "save.txt";
System.IO.StreamWriter SaveFile = new System.IO.StreamWriter(sPath);
foreach (var item in listBox1.Items)
{
SaveFile.WriteLine(item);
}
SaveFile.Close();
MessageBox.Show("Programs saved!");
}
There is one line solution to the problem.
System.IO.File.WriteAllLines(path, Listbox.Items.Cast<string>().ToArray());
put your file path+name and Listbox name in above code.
Example:
in Example below path and name of the file is D:\sku3.txt and list box name is lb
System.IO.File.WriteAllLines(#"D:\sku3.txt", lb.Items.Cast<string>().ToArray());

Keeping track of 40+ control values

I am in need of some guidance for the following design.
I have a tab control that contains various group boxes. Within the group box, there are specific controls that relates to that group box. For example:
Now whenever a change is made to any control in the group box, the value for the control needs to be tracked because at the end of the application run cycle, the control data will need to be saved to a file that contains that value. An example file is:
HOT_STANDBY_FEATURE_ENABLE [val from control here]
HEART_BEAT_DIGITAL_OUTPUT [val from control here]
....
A design that I have in mind has another that has just properties that the group box form sets whenever a ValueChanged event occurs on a control.
Example code:
class ConfigurationValues
{
public int HotStandbyValue { get; set; }
public int HeartBeatDigitalOutputValue { get; set; }
//...add all other controls here
}
The downside that I see to this is that there are 40 controls on that tab page, so I'd have to manually type each property. When the file needs to be generated at the end of the application run cycle, I have a method that gets the value of the control need.
Example:
private void GenerateFile()
{
string[] file =
"HOT_STANDBY_FEATURE_ENABLE " + ConfigurationTabSettings.HotStandbyValue;
}
Another design consideration I need to make is that whenever a user clicks "Open Configuration File", the values from the file from disk need to be loaded into the properties so the form can take that data on startup and populate the controls within the group boxes with their respective values.
Any suggestions on this design would be greatly appreciated. I know this is not the most efficent way to do this and am not the most experienced programmer, so any Google keywords I can search for would be great also.
You could xml serialise and xml deserialise your ConfigurationValues class rather than writing manual "generate file" and "read file" methods
http://support.microsoft.com/kb/815813
You'll need to bind the controls Text or Value properties to the properties in your ConfigurationValues class e.g.
ConfigurationValues cv = Repository.ReadConfigValues();
numPulseFilterDelay.DataBindings.Add("Value", cv, "PulseFilterDelay");
// Add the rest of your control bindings here
on the btnSave_Click() of your Form, end the current edit on the form and save the config values
void btnSave_Click(object sender, EventArgs e)
{
BindingContext[cv].EndCurrentEdit(); // Commits all values to the underlying data source
Repository.SaveConfigValues(cv);
}
In your repository class you'll need methods to Load() and Save() the data. You can put XmlSerialization code in here, or write your own format (depending on your requirements)
public class Repository
{
public static ConfigurationValues LoadConfigValues()
{
var cv = new ConfigurationValues();
string[] lines = File.ReadAllLines("values.cfg");
foreach (string cfg in lines)
{
string[] nameValue = cfg.Split(new char[] { ' ' } ); // To get label/value
if (nameValue[0] == "HOT_STANDBY_FEATURE_ENABLE")
cv.HotStandbyFeatureEnable = nameValue[1];
else if (nameValue[0] == "SOME_OTHER_PROPERTY")
cv.SomeOtherProperty = nameValue[2];
// Continue for all properties
}
return cv;
}
public static void SaveConfigValues(ConfigurationValues cv)
{
var builder = new StringBuilder();
builder.AppendFormat("HOST_STANDBY_FEATURE_ENABLE {0}\r\n", cv.HostStandbyFeatureEnable);
// Add the rest of your properties
File.WriteAllText("values.cfg", builder.ToString());
}
}

Categories

Resources