My programm loading data from table to List. Person - class, contains firstName, lastName, etc.
how it looks
So, in the first ListView (PersonList) it shows a list of Person's first names. What I want – is to somehow add informations from certain fields of Person class to second and third ListViews. So, how to make it? Create a buffer-class? Or just again scan the table to input values? Or to vies it in another form?
some reading-from-table code
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "Excel files| *.xlsx";
ofd.DefaultExt = ".xlsx";
Nullable<bool> dialogOK = Convert.ToBoolean(ofd.ShowDialog());
if (dialogOK == true)
{
_filePath = ofd.FileName;
}
List<Person> personlist = new List<Person>();
personlist = ExcelHelper.GetPersonData(_filePath);
foreach (Person person in personlist)
{
PersonList.Items.Add(person.Fio);
}
Tried to call from class, that contains personList, but error said, that Form1 class is void. Prefer not to change it. Tried to migrate data from 1-st ListView to 2-nd, but unsuccesful. (Just did it directly, like ListViewSecond.Items.Add(ListViewFirst.[1].firstName). yes, I know, it's pretty screwed up).
Related
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.
I'm trying to make a simple simple WPF application for Windows, the idea is to have a tabletop rpg's character sheet on a pc instead than on paper, so the layout is just plenty Textboxes and checkboxes for storing the stats and two richtext panels for notes and inventory.
I'm struggling to find an efficient way to save all the data when closing the app (or at the press of a button) and then loading it when it's opened again, all the solutions I've found so far seem overly complicated for a little project like this.
At this point i'm stuck with loading every field with the respective setting I've created, but it doesn't seem really efficient given the number of fields and I think this could will also give me problems with the rich-text boxes. Nonetheless I also have no clue on how to save the setting before closing the app.
Is there a way to just dump all the fields content in a file (xml maybe?) and load them or any kind of a more straightforward solution? I'm afraid I'm missing something and I'm on the wrong path overcomplicating an issue that could be handled very easily, so any advice is really appreciated.
I'm trying to get this thing done quickly, it's just something i'm making for my group that will be used by 4 people at most so I'm not really worried about best practices
You could create a Dataset, arranging all the needed data in it (like 1 column for every textbox you got on your rpg-sheet) You then store the given data in 1 single row of the Dataset and write it to an xml-file, which can be loaded when you start your application the next time: Code-Snippets:
Creating a dataset:
_dataSet = new DataSet();
//possible dataset-settings can be implemented here
_dataSet.Tables.Add(table) //adding a created table
for (int i = 0; i < koordinaten.Count; i++) //filling the dataset's table with your data
{
DataRow row = table.NewRow();
row["charclass"] = textBoxCharClass.Text;
row["HP"] = textBoxCharHP.Text;
//etc.
table.Rows.Add(row);}
Saving your dataset as xml-file:
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "XML-File|*.xml";
sfd.Title = "Save Char-Information";
sfd.ShowDialog();
if (sfd.FileName != "")
{
System.IO.FileStream fs = (System.IO.FileStream) sfd.OpenFile();
switch (sfd.FilterIndex)
{
case 1: dataset.WriteXml(fs, XmlWriteMode.WriteSchema);
}
fs.Close();
}
Opening the xml-file:
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "XML-File|*.xml";
ofd.Title = "Open Char-Information";
ofd.ShowDialog();
if (ofd.FileName != "")
{
System.IO.FileStream fs = (System.IO.FileStream) ofd.OpenFile();
switch (ofd.FilterIndex)
{
case 1: dataset.ReadXml(fs.Name, XmlReadMode.Auto);
}
fs.Close();
Here's the DataSet and the DataTable MSDN-documentation, in case you need further information :)
Unless this is the simplest of apps I'd definitely have a data "model" that represents a character you can save, load etc (build in xaml serialization being simple and easy), and bind that to the UI. Ideally, you'd have a View, which deals with the display, a ViewModel which has all the properties and methods supporting the view and a Model that represents the data iteself. It might be overkill for you (unless you're already familiar with it), but if not, it's the MVVM pattern and is industry standard for WPF. Simply having an object to bind to is a "shortcut" version.
public class Character : INotifyPropertyChanged
{
private int age = 20;
public int Age
{
get { return age; }
set
{
if (age != value)
{
age = value;
NotifyPropertyChanged("Age");
}
}
}
private string name = "Default Name";
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
NotifyPropertyChanged("Name");
}
}
}
private ObservableCollection<Spells> spells = new ObservableCollection<Spells>();
public ObservableCollection<Spells> Spells
{
get { return spells; }
set
{
if (spells != value)
{
spells = value;
NotifyPropertyChanged("Spells");
}
}
}
...
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Would it take a while? Yes. Would it mean you have a much more usable, and expandable? Yes. If you are doing this exercise simply because you like typing more than scribbling on note-paper, okay - maybe just having on-screen text boxes and nothing behind it is acceptable.
If you are doing it to have something to eventually add features to other than displaying things on screen (say, 'searching' or filtering spells or abilities, applying specific effects like "fatigued" that adjust stats appropriately etc) or especially if you are trying to learn more about programming/software dev then definitely put the extra effort in.
If you really are only interested in making a skin that you can write in and just want this one feature of saving/loading there's a way I think might work.
If you name all your UI compoennts "NameTextBox" etc etc you can use reflection to loop through them and store and retrieve information depending on the type.
For example:
<StackPanel Margin="20">
<TextBox x:Name="AgeTextBox"/>
<TextBox x:Name="NameTextBox"/>
<TextBox x:Name="Notes"/>
<Button Content="Save" Click="Button_Click"/>
</StackPanel>
I can search for all textboxes and do what I want with their contents, in a generic way. This just outputs to the console, but you could save to a text file, csv or xml:
var fields = typeof(MainWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
foreach (FieldInfo FI in fields)
{
if (FI.FieldType == typeof(TextBox))
{
TextBox value = FI.GetValue(this) as TextBox;
Console.WriteLine(FI.Name + ":" + value.Text);
}
}
Produces:
AgeTextBox:23
NameTextBox:Bob Bobson
Notes:Is a very nice Character
And similar could be done in reverse.
If the data being stored isn't of a sensitive nature, have you thought of using a JSON / XML file to save your output?
Using a JSON file you could store variable values (IE: checkbox state) as well as text for any required text boxes (if required this can also be serialized which would help protect against unwanted tampering externally, if that became a problem).
Then read the data back in from the file on application startup.
I'd recommend you look # JSON.net
and more specifically for serializing to file,
Serialize Json to file
Let's say I have a class, Person. I also have a button in a WPF window. I want to be able to click that button create a new Person instance (and then add it to a List).
What I don't get is how I do that. My understanding is that I need to specify an instance name (like Person person1 = new Person() - I need to specify that "personsomething" for each instance.
What if I wanted to have a school system with hundreds of people? I can't figure out a way to just click a button and create a new instance for each click (perhaps with some names and stuff, but that's besides the point).
I've got this
private void btnCreatePerson_Click(object sender, RoutedEventArgs e)
{
Person person = new Person(txtFirstName.Text, txtLastName.Text);
personList.Add(person);
ClearTextBoxes();
}
but that only works for a single instance of the class.
I do have to specify a name for each instance, right...?
I do have to specify a name for each instance, right...?
No. By adding the created instance to a list, you're creating one instance per person.
You can read them back from the list by iterating (foreach (var person in personList) { ... }) or querying them (var person = personList.FirstOrDefault(p => p.FirstName = "Joe")).
I have a series of lists in a static class (used as a global class)
public static class globalClass
{
public static List<classA> aList = new List<classA>();
public static List<classB> bList = new List<classB>();
public static List<classC> cList = new List<classC>();
}
I want to generate a xaml button for each list, and was told reflection was a bad idea. This is how I handled it using reflection.
//get FieldInfo for globalClass
TypeInfo typeInfo = IntrospectionExtensions.GetTypeInfo(typeof(globalClass));
IEnumerable<FieldInfo> FieldInfoList = typeInfo.DeclaredFields;
foreach (FieldInfo f in FieldInfoList)
{
//Only look at lists
if(f.FieldType.ToString().StartsWith("System.Collections.Generic.List`1")){
StackPanel s = new StackPanel();
s.Orientation = Orientation.Horizontal;
TextBlock textBlock = new TextBlock();
textBlock.FontSize = 45;
textBlock.Text = f.Name.ToString();
Button addButton = new Button();
addButton.Click += delegate(object sender, RoutedEventArgs e)
{
Frame.Navigate(typeof(addObjectToLibraryPage), f);
};
addButton.Margin = new Thickness(10);
addButton.Name = "addButton";
addButton.Content = "add";
Button deleteButton = new Button();
deleteButton.Click += delegate(object sender, RoutedEventArgs e)
{
Frame.Navigate(typeof(deleteObjectFromLibraryPage), f);
};
deleteButton.Margin = new Thickness(10);
deleteButton.Name = "deleteButton";
deleteButton.Content = "delete";
s.Children.Add(addButton);
s.Children.Add(deleteButton);
//add new textBlock and stackpanel to existing xaml
stackPanel.Items.Add(textBlock);
stackPanel.Items.Add(s);
}
}
Is there any cleaner way to do this? Hopefully I would like to be able to pass the actual list instead of a FieldInfo.
I don't want to have to handle each list individually because I may end up with 20+ lists and am using them all in a very similar way.
An example of what I am trying to do:
Suppose I have a grocery/nutrition App, and I want users to be able to record what they eat/need from the store. They can select from a list of Fruit, Vegetables, Meat, Dairy, Sweets, Canned Goods, Etc..
But, I want them to be able to (as an advanced option) be able to edit the list of possible fruits, or any other food category. And I don't want to just have a list of "food" because meat will record things like minimum cooking temperature or something like that.
So, under advanced options, I would want two buttons for each category (add to fruit, delete from fruit). And theoretically add an Import/Export page so I can share my list of fruits with other people or something.
It doesn't seem like the answers pointing to using a superclass will work. See: C# polymorphism simple question
You can create a list to contain all the existing lists you have. You can then iterate over the list to create the buttons. If you wish to maintain a label for each list you could use a dictionary with the key as the label text and list as the value.
The proposed solution aside, do take into account the comments given from Sayse.
Hello i´m coding in C# and have a problem.
I have two Forms (Form1 and Form2), and the first form (Form1) contains a listbox and some labels like this:
Name: label1
Phone: label2
City: label3
And so on
And the second form (Form2) is a form were the user can fill in Name, Phone, city...
And when the user Press OK The name will only show up in the listbox in Form1, but i want to make a method when the user presses a name in the listbox the other information that the user typed in shall beacome visible in the labels.
So if the user opens Form2 and types in:
Name: John
Phone: 0011223344
City: New York
And then press ok the name John will beacome visible in the listbox but when the user selects John from the listbox the lables will show:
Name: John
Phone: 0011223344
City: New York.
Hope you can help me, thanks.
I'm assuming that your using strings to populate the ListBox here (you don't actually tell us how you are passing the data). Instead of passing a string from Form2 back to Form1, pass a data object:
class Person
{
string Name { get; set; }
string PhoneNumber { get; set; } // perhaps not best as a string
string City { get; set; }
}
Now expose a "Person" property from Form2:
class Form2
{
public Person Person
{
get { return new Person() { Name = txtName.Text, PhoneNumber = txtPhone.Text, City = txtCity.Text }; }
}
}
So, in Form1 you can use that property like so:
using( Form2 frm = new Form2() )
{
if( frm.ShowDialog() == DialogResult.OK )
{
Person p = frm.Person;
list.Items.Add( p.Name );
lblName.Text = p.Name;
lblPhone.Text = p.PhoneNumber;
lblName.City = p.City;
}
}
you have to write the code to update the lables in the SelectedIndexChanged event of the list box.May be some thing like this:
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
form2.label1.text = getname(listBox1.SelectedItem.ToString());
form2.label2.text = getPhone(listBox1.SelectedItem.ToString());
form2.label3.text = getCity(listBox1.SelectedItem.ToString());
}
I'm not 100% certain I understand the layout on your form1, but it sounds like maybe you want some databinding and that might make your life easier.
If you had a class, say called Person, that had a Name, Phone, City property. Then rather than using a listbox, use a datagrid on your main form (styling it appropriately), and it can be bound to a List.
Then when you can use your labels to add new Person's to the datasource, but you can also select a person in the datagrid, and have your labels bounds to the columns, e.g.
this.lblName.Text = form1.datagrid.SelectedRows[0].Cell["Name"];
Doesn't directly answer your question but maybe a slightly nicer approach.
When the user clicks OK from Form2, you will have to retrieve the data they just entered and store it somewhere in Form1.
My advice would be to have a struct type called Person or something containing Name, Phone and City fields. Also, override the ToString method, and from that, simply return the Name field. Then you can add Person objects to your ListBox. Only the name will appear in the ListBox, and the SelectedItem property of the ListBox will return a Person struct, which will give you access all the information you need for the selected item.
Now all that's left is to handle the SelectedIndexChanged event, from which you can populate the labels with the data from the SelectedItem property.